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输出吧。

 

不过想研究技术的话,仍是要多折腾,多探索!

相关文章
相关标签/搜索