2010-06-09 17 views
2

我在Java中編寫了StringOutputStream類,因爲我需要來自OutputStream的pre-Base64編碼數據轉到字符串。我需要這個字符串作爲字符串,因爲我將把它放入W3C XML Document將大型流複製到字符串 - Java

一切都會好的,但我正在處理(相對)較大的對象。生成的對象結果約爲25 MB(在String表示之前)。我將它作爲Applet運行,因此我有66 MB的堆空間,它很快就會耗盡。

我已經嘗試了一些方法至今:

  1. 追加接收的字節到一個String對象(使用strObj.concat((byte) b)strObj += new String((byte) b))有和無緩衝
  2. 接收的字節添加到StringBuffer
  3. 將字節添加到字節數組中,然後當需要字符串時,將該字節數組轉換爲字符串

第一個工作,直到abo ut 11 MB,當舊字符串和新字符串在連接時佔用太多空間時。

第二個是完全失敗,它只能達到7 MB左右。

第三是(也許?)最好的,它存儲整個流,但是當試圖獲取字符串時,它不會令人驚訝地失敗。

我該如何做這項工作?可能嗎?

我想我有可用的空間來容納結果字符串,但它是複製是問題(因爲您需要傳統副本的源和目標)。我知道字符串是不可變的,但是有什麼方法可以將一些字符追加到最後?

這裏是我的三個例子:

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; 
} 
} 

有關代碼的幾個注意事項:很明顯,你命名你想用寫()和toString()方法。此外,字節數組(當前)是靜態分配的,但是如果我轉到該路由(並且在其他方​​法中未使用),則該數組將被更改。

編輯1:我的整體問題 的更多信息:

這是一個較大的應用程序需要的數據,它的標誌,並上傳到服務器的一部分。我必須在文件中讀入數據,然後對其進行SHA-1哈希處理,對其進行加密,然後構造一個XML文檔(以及其他一些內容,例如時間)。然後,必須對XML文檔進行簽名(通過XML DSig,又名javax.xml.crypto.dsig.XMLSignatureFactory)並上載回服務器。

要簽名的文件從1KB到大約50MB。

有幾個問題:

  1. XML DSig的目前的Java實現不解析和XML流,只是W3C節點。(我也找不到任何其他的實現)
  2. 我的老闆希望這不需要最小的客戶端安裝,所以這就是爲什麼選擇一個Applet(它是一個簽名的applet,所以它可以訪問客戶端上的任何東西) 。
+0

你能解釋爲什麼'java.io.ByteArrayOutputStream'或'java.io.StringWriter'不適合你嗎? – Pointy 2010-06-09 16:30:30

+0

ByteArrayOutputStream與方法3完全相同,它會複製所有內容,然後在調用toString時耗盡內存。 StringWriter內存耗用大約10 MB,與方法2大致相同。 – HalfBrian 2010-06-09 16:38:44

+0

您是否可以將更多關於您正在嘗試解決的問題的問題添加到您的問題中?這可能會幫助所有人從盒子外面思考,並從另一個角度解決問題。例如。你專注於22MB的數據 - 正在簽名的數據是否已修復?這是一個硬性上限大小? – mdma 2010-06-09 17:03:20

回答

0

感謝mdma,我意識到我真的需要一種方法來流這個,而不是存儲在內存中。

下面是我所做的:applet現在像以前一樣加密數據,但使用PKCS7(使用BouncyCastle的CMSSignedDataStreamGenerator)對其進行簽名。數據通過網絡進行流式傳輸,無需將其存儲在客戶端的計算機上。

然後將PKCS7簽名生成的分離的PKCS7簽名放入XML中。該XML然後用XML DSig簽名並單獨上傳到服務器。

3

當您將結果字符串放入XML文檔中時,我建議使用流式XML API。然後整個過程可以流式傳輸,並且不需要在內存中保存大量數據。

XML文檔發生了什麼變化?作爲一個小程序,我只能想象幾個選擇 - 寫入沙箱中的文件,或者流回原始服務器。如果您使用流媒體XML,那麼可以在數據通過您的數據流寫入時將數據發送到最終位置。

例如,您可以將字符數據流式傳輸到StringOutputStream中的SAX ContentHandler,而不是將數據存儲在緩衝區中。

編輯1:

鑑於50MB的最大文件大小,我認爲你是推小程序有點過頭了,除非你能保證它們與內存配置CA 3-4倍的最大文件大小(例如,使用Windows上的java控制面板插件)。登錄applet並不是很安全 - 很容易進行反向工程並獲得私鑰,導致簽名不可信。如果小程序總是使用相同的密鑰,那麼您是否不能移動簽名服務器端?無論如何,該文件正在上傳,這將避免所有的內存問題。該方案則是:

  • 小程序上傳原始文件到服務器
  • 服務器創建從文件
  • 服務器簽署XML
  • 的服務器轉發該簽名的XML來whever的XML該小程序正在發送它。如果它是您自己的服務器/ Web應用程序之一,那麼該文件已可供使用。
+0

我仍在研究使用流式XML API,但問題是我需要簽署XML使用XMLSignatureFactory,我還沒有發現如果我可以用流XML API來做到這一點。 – HalfBrian 2010-06-09 16:35:56

+0

正在使用Java Web Start和選項?您可以指定VM啓動時所需的內存量。 – mdma 2010-06-09 16:54:01

+0

不幸的是,這並不是因爲我的老闆希望這不需要任何可下載的應用程序。如果我無法解決這個問題,顯然這將是被放棄的限制,然後我可能會將其作爲桌面應用程序運行。 – HalfBrian 2010-06-09 16:58:13