最近工做中遇到了一个问题,进而引起了对 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输出吧。
不过想研究技术的话,仍是要多折腾,多探索!