I am writing a StringOutputStream class in Java because I need the pre-Base64-encoded data from an OutputStream to go to a String. I need this as a String because I am going to be putting it into a W3C XML Document.
Everything would be fine, but I'm dealing with (relatively) large objects. The resulting object turns out to be about 25 MB (before String representation). I am running this as an Applet, so I have 66 MB of heap space which gets exhausted quite quickly.
I have tried a few methods so far:
- Append the received byte to a String object (using
strObj.concat((byte) b)andstrObj += new String((byte) b)) with and without buffering - Add the received byte to a
StringBuffer - Add the byte to a byte array, then when the string is wanted, convert that byte array to a String
Number one works until about 11 MB, when the old String and the new String use up too much space when concat-ing.
Number two was a total failure, it only gets to about 7 MB.
Number three was (perhaps?) the best, it stores the whole stream, but when trying to get the String it, unsurprisingly, fails.
How would I make this work? Is it possible?
I think I have the space available to hold the resulting String, but it's the copying that is the problem (since you need source and destination for a traditional copy). I know Strings are immutable, but is there any way to append some characters onto the end?
Here's my three examples:
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;
}
}
A few notes about the code: obviously, you rename the methods you want to use to write() and toString(). Also, the byte array is (currently) statically allocated, but that would be changed if I go that route (and is not used during the other methods).
Edit 1: More information on my overall problem:
This is part of a larger application that takes data, signs it, and uploads it to a server. I have to read in a file, take the SHA-1 hash of it, encrypt it, and then construct an XML document (with a few other things in it, such as the time). Then that XML document must be signed (via XML DSig, aka javax.xml.crypto.dsig.XMLSignatureFactory) and uploaded back to the server.
The files to be signed are anywhere from 1KB to about 50 MB.
There are a few problems:
- The current Java implementation of XML DSig does not parse and XML streams, just w3c Nodes. (I also cannot find any other implementations that do)
- My boss wants this to not require minimal client-side installation, so that's why an Applet was chosen (it is a signed applet, so it can access anything on the client).