将大型流复制到String - Java

时间:2010-06-09 16:25:27

标签: java memory string stream

我在Java中编写StringOutputStream类,因为我需要来自OutputStream的pre-Base64编码数据才能转到String。我需要将它作为String,因为我将把它放入W3C XML Document

一切都会好的,但我正在处理(相对)大型物体。结果对象结果大约是25 MB(在String表示之前)。我将它作为Applet运行,因此我有66 MB的堆空间很快就会耗尽。

到目前为止,我尝试了一些方法:

  1. 将收到的字节附加到String对象(使用strObj.concat((byte) b)strObj += new String((byte) b)),无论是否缓冲
  2. 将收到的字节添加到StringBuffer
  3. 将字节添加到字节数组中,然后在需要字符串时,将该字节数组转换为字符串
  4. 第一个工作直到大约11 MB,当旧的String和新的String在连接时占用太多空间。

    第二个是完全失败,它只有大约7 MB。

    第三个(也许是?)是最好的,它存储了整个流,但是当试图获取String时,毫不奇怪,它失败了。

    我将如何使这项工作?有可能吗?

    我认为我有可用于保存结果字符串的空间,但这是问题的复制(因为您需要传统副本的源和目标)。我知道字符串是不可变的,但有没有办法将一些字符附加到最后?

    以下是我的三个例子:

    package com.myorg.SigningServer.Util.Security;
    
    import java.io.IOException;
    import java.io.OutputStream;
    import java.util.Arrays;
    
    import com.technicolor.SigningServer.Applet.SigningApplet;
    
    public class StringOutputStream extends OutputStream {
    
    byte[] array = new byte[1024*1024*22];
    StringBuffer sb = new StringBuffer();
    String output = "";
    int prevByte = -1;
    long numBytes = 0;
    
    int bufferPos = 0;
    int bufferSize = 512*1024;
    byte[] buffer = new byte[bufferSize];
    
    public void write2(int b) throws IOException {
        sb.append((byte) b);
    }
    
    
    public void write3(int b) throws IOException {
        array[(int) numBytes] = (byte) b;
        numBytes++;
    }
    
    public void write1(int b) throws IOException {
        numBytes++;
        bufferPos++;
        buffer[bufferPos] = (byte) b;
        if(bufferPos == bufferSize-1)
        {
            bufferPos = 0;
            System.gc();
            System.out.println("Generating string "+numBytes+"; String length "+output.length());
            output = output.concat(new String(buffer));
            System.gc();
        }
    }
    
    public void flush1() {
        output = output.concat(new String(Arrays.copyOf(buffer, bufferPos)));
        bufferPos = 0;
        System.gc();
    }
    
    public String toString2()
    {
        return sb.toString();
    }
    
    public String toString3()
    {
        return new String(array);
    }
    
    public String toString1()
    {
        return output;
    }
    }
    

    关于代码的一些注意事项:显然,您要重命名要用于write()和toString()的方法。此外,字节数组(当前)是静态分配的,但是如果我去那条路线就会改变(并且在其他方​​法中不使用)。

    编辑1: 有关我整体问题的更多信息:

    这是一个更大的应用程序的一部分,它接收数据,签名并将其上传到服务器。我必须读取一个文件,获取它的SHA-1哈希值,加密它,然后构造一个XML文档(其中包含一些其他内容,例如时间)。然后必须签署该XML文档(通过XML DSig,又名javax.xml.crypto.dsig.XMLSignatureFactory)并上传回服务器。

    要签名的文件大小在1KB到大约50 MB之间。

    有一些问题:

    1. XML DSig的当前Java实现不解析和XML流,只是w3c节点。 (我也找不到其他任何实现)
    2. 我的老板希望这不需要最小的客户端安装,这就是选择Applet的原因(它是一个签名的applet,因此它可以访问客户端上的任何内容)。

2 个答案:

答案 0 :(得分:3)

当您将生成的String放在XML文档中时,我建议使用流式XML API。然后整个过程可以流式传输,您不需要在内存中保留大量数据。

XML文档发生了什么?作为一个applet,我只能想象一些选择 - 写入沙箱中的文件,或者流回原始服务器。如果您使用流式XML,则可以在您通过流式传输数据时将数据发送到最终位置来完成此操作。

例如,您可以将字符数据流式传输到StringOutputStream中的SAX ContentHandler,而不是将数据存储在缓冲区中。

编辑1:

鉴于最大文件大小为50MB,我认为你推动的applet有点太过分了,除非你能保证它们配置的内存大小是最大文件大小的3-4倍(例如在Windows上使用java控制面板插件)。 )登录applet并不是非常安全 - 反向工程很容易,并且获取私钥使得签名不可靠。如果applet始终使用相同的密钥,您是否可以移动签名服务器端?无论如何都要上传文件,这样可以避免所有内存问题。该计划是:

  • applet将原始文件上传到服务器
  • 服务器从文件
  • 创建XML
  • 服务器签署XML
  • 服务器将签名的XML转发给applet发送它的时间。如果是您自己的服务器/ webapps之一,则该文件已可供使用。

答案 1 :(得分:0)

感谢mdma,我意识到我真的需要一种方法来传输它,而不是存储在内存中。

这就是我所做的:applet现在像以前一样加密数据,但是使用PKCS7(使用BouncyCastle的CMSSignedDataStreamGenerator)对其进行签名。数据通过网络流式传输,无需将其存储在客户端的计算机上。

然后将PKCS7签名生成的分离的PKCS7签名放在XML中。然后使用XML DSig对XML进行签名,并单独上载到服务器。