最近工做中遇到了一個問題,進而引起了對 java io 技術的探索,在此將心得記錄下來。java
給定一個網絡上圖片的地址(好比:「http://pic.3h3.com/up/2016-9/2016913161637875970.jpg」),經過URL轉化成InputStream(IO)進行讀取,以後經過NIO的方式寫出到指定文件中。數組
代碼以下:網絡
public void down(String urlString) throws Exception{ // 構造URL URL url = new URL(urlString); // 打開鏈接 URLConnection con = url.openConnection(); // 輸入流 InputStream is = con.getInputStream(); BufferedInputStream in = new BufferedInputStream(is); File file = new File("f:/test.jpg"); //作實驗用,暫且寫到這個文件 // 指定要寫入文件的緩衝輸出字節流 FileChannel osChannel = new FileOutputStream(file).getChannel(); byte[] bb = new byte[1024];// 用來存儲每次讀取到的字節數組 while (in.read(bb)!=-1) { osChannel.write(ByteBuffer.wrap(bb)); } osChannel.close();// 關閉流 in.close(); }
去 F 盤查看,發現已生成了 test.jpg 文件。本覺得搞定了,但是打開該文件,發現圖片黑乎乎一片(或者有重影),這是怎麼回事?優化
好吧,我要認可本身 IO、NIO 方面戰五渣,因而先喝了杯水冷靜了下……this
咳咳,言歸正傳。url
我先查看了 NIO 方面網上的不少資料,未果。spa
後來發現壞文件比好文件要大些(幾KB的樣子),因而思考,會不會由於這裏:線程
osChannel.write(ByteBuffer.wrap(bb));
由於NIO是非阻塞的,有沒有多是FileChannel內部新起的線程,多個線程向文件中寫入,紊亂了……code
(關於NIO的非阻塞理,這裏解錯了。它的非阻塞應該和 selector 什麼的有關,感興趣的讀者本身找資料研究下。)圖片
修改代碼以下:
while (in.read(bb)!=-1) { osChannel.write(ByteBuffer.wrap(bb)); Thread.currentThread().sleep(30); //加入當前線程沉睡 }
修改後生成的圖片沒問題了。
又試着去掉sleep() 方法,改爲同步控制:
while (in.read(bb)!=-1) { synchronized (bb) { //這裏改爲this、osChannel什麼的都不起做用 osChannel.write(ByteBuffer.wrap(bb)); } //Thread.currentThread().sleep(30); //加入當前線程沉睡 }
沒錯,如你所見,不起做用。
一番波折後,想到了打印循環次數,比較sleep()引起的不一樣。
int i=0; while (in.read(bb)!=-1) { osChannel.write(ByteBuffer.wrap(bb)); i++; System.out.println("i:"+i); //Thread.currentThread().sleep(30); //調用sleep,與不調用,打印i的次數是不一樣的,也就表示:循環次數不一樣 }
如註釋中聲明,sleep()的調用會引起循環次數的變化。
這就奇怪了,BufferedInputStream的read方法應該是阻塞的,循環次數應該相同纔對。
修改代碼,打印更多的信息:
int i=0; int n=0; while ((n=in.read(bb))!=-1) { //osChannel.write(ByteBuffer.wrap(bb)); 先注掉這裏和這兒沒啥關係 i++; System.out.println("n:"+n+",i:"+i); //Thread.currentThread().sleep(30); //調用sleep,與不調用,打印i的次數是不一樣的 }
控制檯輸出:
分析結果會發現:
length爲1024的字節數組,在IO讀取文件時,有時會沒有讀滿,就進入下一循環了。
加入sleep() 方法後,byte[] 就都會讀滿。
沒讀滿的狀況下,調用ByteBuffer.wrap(byte[] b) 方法,植入的數組會有不少補入的「0」,進而影響了對文件的write操做。
終於找到了緣由,那麼解決方案也簡單:
public void down(String urlString) throws Exception{ // 構造URL URL url = new URL(urlString); // 打開鏈接 URLConnection con = url.openConnection(); // 輸入流 InputStream is = con.getInputStream(); BufferedInputStream in = new BufferedInputStream(is); File file = new File("f:/test.jpg"); // 指定要寫入文件的緩衝輸出字節流 FileChannel osChannel = new FileOutputStream(file).getChannel(); byte[] bb = new byte[1024];// 用來存儲每次讀取到的字節數組 int i=0; int n=0; while ((n=in.read(bb))!=-1) { /*if(n!=1024){ byte[] temp = Arrays.copyOf(bb, n); osChannel.write(ByteBuffer.wrap(temp)); }else{ osChannel.write(ByteBuffer.wrap(bb)); }*/ //樓主本來弱弱的實現,廢棄,該爲下面 osChannel.write(ByteBuffer.wrap(bb,0,n)); //評論1樓 「顯峯哥」指點 i++; System.out.println("n:"+n+",i:"+i); //Thread.currentThread().sleep(30); } osChannel.close();// 關閉流 in.close(); }
其實NIO出現後, IO的內部實現已經用NIO複寫、優化過了,通常狀況下,沒有必要刻意的去追求以NIO的方式去操做文件。(大文件的內存映射除外)
因此仍是IO讀入對IO輸出,NIO讀入對NIO輸出吧。
不過想研究技術的話,仍是要多折騰,多探索!