URLConnection 使用流的問題

原文鏈接:http://www.mzone.cc

URLConnection 使用流的問題

HttpURLConnection con = (HttpURLConnection)new URL("url").openConnection();

//若不進行下面兩種設置 會使用PosterOutputStream extends ByteArrayOutputStream將所有加到內存中輸出。如果傳輸數據過大, 會導致OutOfMemoryError。 HttpURLConnection#getOutputStream0()

con.setChunkedStreamingMode(塊的大小);//未知流長度,不使用本地緩存

//con.setFixedLengthStreamingMode(輸出流的固定長度);//已知流長度,不使用本地緩存

ps:使用Chunked需要服務器支持。

本文來源於鐵木箱子的博客http://www.mzone.cc

最近在用applet寫文件上傳控件的時候發現使用URLConnection來對服務器進行流式輸出時的一些問題.我們通常要對服務器上的某個地址進行寫流操作,那麼我們一般的做法就是: 

 


 
  1. URLConnection con = new URL("/test.do").openConnection();

  2. con.setDoOutput(true); // 允許輸出流,默認是false

這樣我們就獲取一個到/test.do地址的HTTP連接了,我們打印con的class後發現其實是:sun.net.www.protocol.http.HttpURLConnection這個類,我們在寫大數據流到服務器上時就會發現總是會出現OutOfMemoryError的錯誤,當然不同的機器上出現錯誤所對應的文件輸出流的大小是不一樣的.這個主要就是取決於本機的JVM的內存空間的大小了.出現OutOfMemoryError錯誤的主要原因就是:sun公司實現的HttpURLConnection的輸出流是首先在本地內存進行緩存,然後再一次性輸出的(在close操作時).我們可以追蹤到sun自己的HttpURLConnection使用的OutputStream是sun.net.www.http.PosterOutputStream這個類,我們查看這個類的源碼就會發現它是繼承自ByteArrayOutputStream的,而且基本上這個類沒有做任何事情,大家可以參看其源碼.而我們的ByteArrayOutputStream則是每次將要輸出的內容複製到一個byte數組中,從而導致的結果是將整個輸出流全部存儲在內存中,這樣當我們輸出流一大的時候就會出現內存不夠用的情況.請看ByteArrayOutputStream的部分源碼: 

 


 
  1. public synchronized void write(int i)

  2. {

  3. int j = count + 1;

  4. if(j > buf.length) {

  5. byte abyte0[] = new byte[Math.max(buf.length << 1, j)];

  6. System.arraycopy(buf, 0, abyte0, 0, count);

  7. buf = abyte0;

  8. }

  9. buf[count] = (byte)i;

  10. count = j;

  11. }

  12.  
  13. public synchronized void write(byte abyte0[], int i, int j)

  14. {

  15. if(i < 0 || i > abyte0.length || j < 0 || i + j > abyte0.length || i + j < 0) throw new IndexOutOfBoundsException();

  16. if(j == 0) return;

  17. int k = count + j;

  18. if(k > buf.length) {

  19. byte abyte1[] = new byte[Math.max(buf.length << 1, k)];

  20. System.arraycopy(buf, 0, abyte1, 0, count);

  21. buf = abyte1;

  22. }

  23. System.arraycopy(abyte0, i, buf, count, j);

  24. count = k;

  25. }

我們可以看到它是使用System.arraycopy的功能來將所有的輸出流存放在一個數組中的.因此,在使用HttpURLConnection進行流式輸出的時候如果輸出流比較大,那麼就該考慮使用其他方式了(當然,修改JVM的堆棧空間是一種方法,但是不可取). 

 

      這是我們直接使用java.net.HttpURLConnection類的相關方法來進行輸出文件流,我們查看sun提供的HttpURLConnection的源碼,會發現其默認是採用上面提高的PosterOutputStream類來進行緩衝輸出的,即首先將所有的文件流在本地內存中進行緩存,等到輸出結束執行close的時候一次性輸出到服務器端.同時我們看到sun的HttpURLConnection中的getOutputStream()中有如下代碼: 


 
  1. if(streaming()) {

  2. if(fixedContentLength != -1)

  3. strOutputStream = new StreamingOutputStream(ps, fixedContentLength);

  4. else if(chunkLength != -1)

  5. strOutputStream = new StreamingOutputStream(new ChunkedOutputStream(ps, chunkLength), -1);

  6. return strOutputStream;

  7. }

其中strmeanming()方法是用來判斷是否是流式的輸出,其代碼爲:return fixedContentLength != -1 || chunkLength != -1;它的判斷方法就是如果設置了輸出流的固定長度或是設置了塊的長度,那麼將採用流式輸出.因此,我們可以在輸出的時候可以設置其長度來達到流式輸出這樣的效果.另外,StreamingOutputString類是sun提供的HttpURLConnection的內部類,繼承自FilterOutputStream,而非ByteArrayOutputStream,所以不會在本地內存中進行緩存. 

 

      而jdk中的HttpURLConnection並沒有提供設置流的固定長度或塊輸出的長度方法,所以我們需要顯示的將new URL(“url”).openConnection()返回的URLConnection轉換成sun的HttpURLConnection,從而我們就可以很方便的使用setFixedLengthStreamingMode方法來設置流的固定長度,那麼也就會採用流式的輸出了.那麼也就不會出現OutOfMemoryError的錯誤了.另外,ChunkedOutputStream也是不會在本地進行緩存的,它是使用固定大小的數組來緩存輸出流,等緩存滿的時候就自動的調用基礎流進行輸出,這個主要是用在無法確定輸出流的具體長度但是又不想在本地進行緩存時用到.同理,我們通過設置setChunkedStreamingMode就可以達到這樣的效果,三種方式的代碼如下: 

第1種方式:使用直接輸出流的方式(已知輸出流的長度):


 
  1. import sun.net.www.protocol.http.HttpURLConnection;

  2. ......

  3. HttpURLConnection con = (HttpURLConnection)new URL("url").openConnection();

  4. con.setFixedLengthStreamingMode(輸出流的固定長度);

  5. ......

第2種方式:使用直接輸出流的方式(未知輸出流的長度): 

 


 
  1. import sun.net.www.protocol.http.HttpURLConnection;

  2. ......

  3. HttpURLConnection con = (HttpURLConnection)new URL("url").openConnection();

  4. con.setChunkedStreamingMode(塊的大小);

  5. ......

第3種方式:本地緩存後一次性輸出: 


 
  1. import java.net.HttpURLConnection;

  2. ......

  3. HttpURLConnection con = (HttpURLConnection)new URL("url").openConnection();

  4. ......


 通過設置直接輸出後,我傳送文件的大小爲200M的時候也不會出現OutOfMemoryError的錯誤,而之前使用第3種方式進行文件流輸出的時候不到40M就出現了OutOfMemoryError的錯誤了. 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章