关于内存:将大量流复制到String-Java

Copying a large stream to String - Java

我正在用Java编写StringOutputStream类,因为我需要来自OutputStream的预Base64编码的数据才能到达字符串。我需要将它作为字符串,因为我将把它放入W3C XML Document

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

到目前为止,我已经尝试了几种方法:

  • 在有缓冲和无缓冲的情况下,将接收到的字节追加到String对象(使用strObj.concat((byte) b)strObj += new String((byte) b))
  • 将接收到的字节添加到StringBuffer
  • 将字节添加到字节数组,然后在需要字符串时,将该字节数组转换为字符串
  • 第一个可以工作到大约11 MB,这时旧的String和新的String在连接时会占用过多的空间。

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

    第三个(也许是最好的),它存储了整个流,但是毫无疑问,当尝试获取String时,它失败了。

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

    我认为我有足够的空间容纳结果String,但这是问题所在的复制(因为您需要传统副本的源和目标)。我知道字符串是不可变的,但是有什么方法可以将一些字符附加到末尾?

    这是我的三个示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    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。

    有几个问题:

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

  • 当您将结果字符串放入XML文档时,建议您使用流XML api。这样就可以传输整个过程,而您无需在内存中保留大量数据。

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

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

    编辑1:

    鉴于最大文件大小为50MB,我认为您将applet推得太远了,除非可以保证它们配置的内存是最大文件大小的3-4倍(例如,在Windows上使用java控制面板插件)。 )登录小程序不是很安全-进行逆向工程很容易,并且容易获得使签名不可信的私钥。如果小程序始终使用相同的密钥,那么您是否可以移动签名服务器端?该文件仍在上载,这将避免所有内存问题。该方案是:

    • 小程序将原始文件上传到服务器
    • 服务器从文件创建XML
    • 服务器对XML进行签名
    • 服务器将转发已签名的XML到小程序正在发送的XML。如果是您自己的服务器/ Web应用程序之一,则该文件已经可以使用。


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

    这就是我的工作:小程序现在像以前一样加密数据,但是使用PKCS7(使用BouncyCastle \\的CMSSignedDataStreamGenerator)对其进行签名。数据通过网络流传输,而不存储在客户端计算机上。

    然后将由PKCS7签名生成的分离的PKCS7签名放入XML内。然后使用XML DSig对XML进行签名,并将其分别上传到服务器。