Java IO 探索

 

最近工做中遇到了一個問題,進而引起了對 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輸出吧。

 

不過想研究技術的話,仍是要多折騰,多探索!

相關文章
相關標籤/搜索