Java NIO使用和总结

来源:互联网 发布:淘宝店铺被永久查封 编辑:程序博客网 时间:2024/06/06 04:20

Java NIO使用和总结

0.前言

NIO即New IO,是在java io机制的基础上增加的内容。这篇主要学习和使用它的用法, 主要的学习来自并发编程网http://ifeve.com/java-nio-all/

1.Channel(通道)

Channel其实就是对流进行了改进,使得既可以读也可以写,而一般意义上的流通常是单向的,这就是Channel的产生。下面看一下它的用法:

/**1.通道基本用法 * channel类型 * FileChannel 文件中读写数据 * DatagramChannel UDP中读写数据 * SocketChannel TCP中读写数据 * ServerSocketChannel 监听TCP连接信息 */
public static void main(String[] args) throws Exception {/*文件输入流*/FileInputStream filInputStream = new FileInputStream(new File("channel1.txt"));/*从中获取通道*/FileChannel channel1 = filInputStream.getChannel();/*分配48字节capacity的ByteBuffer*/ByteBuffer ByteBuffer = ByteBuffer.allocate(6);/*写模式初始*///System.out.println("Channel1.main()position:"+ByteBuffer.position());//System.out.println("Channel1.main()limit:"+ByteBuffer.limit());//System.out.println("Channel1.main()capacity:"+ByteBuffer.capacity());/*把数据从channel中读取到ByteBuffer*/int ByteRead = channel1.read(ByteBuffer);while(-1 != ByteRead){/*写模式结束*///System.out.println("Channel1.main()position:"+ByteBuffer.position());//System.out.println("Channel1.main()limit:"+ByteBuffer.limit());//System.out.println("Channel1.main()capacity:"+ByteBuffer.capacity());System.out.println("Channel1-ByteRead-wri:"+ByteRead);/*反转buffer,其实就是从写模式切换到读模式,后面详述*/ByteBuffer.flip();/*读模式开始*///System.out.println("Channel1.main()position:"+ByteBuffer.position());//System.out.println("Channel1.main()limit:"+ByteBuffer.limit());//System.out.println("Channel1.main()capacity:"+ByteBuffer.capacity());while(ByteBuffer.hasRemaining()){System.out.println("Channel1-ByteRead-get:"+ByteBuffer.get());}/*读模式结束*///System.out.println("Channel1.main()position:"+ByteBuffer.position());//System.out.println("Channel1.main()limit:"+ByteBuffer.limit());//System.out.println("Channel1.main()capacity:"+ByteBuffer.capacity());ByteBuffer.clear();ByteRead = channel1.read(ByteBuffer);}}

2.Buffer

之前我们对流进行操作时,通常会用一个byte或者一个byte数组来接受,但是我们需要更多的功能,比如记录读到哪一个位置,这个时候操作数组很难满足,所以就有了Buffer,下面看一下它的用法:

/**2.buffer基本用法 * Buffer的capacity,position和limit * capacity:不管是读模式,还是写模式,都代表缓冲区的长度 * limit: * 在写模式下,limit其实就是capacity * 在读模式下,limit代表已经写入元素的数量 * position: * 在写模式下,从零开始不管增加,最大值是capacity,position的值就代表已经写入的数据数量 * 在读模式下,也是从零开始,按照写入的顺序把数据读取出来,最大值是limit,position的值就代表已经写入的数据数量 */

上图的箭头方法分别表示数据的写入和读取的方向。写模式下,position从上往下,读模式下,position也是从上往下。  实际上就是position原本指向3,变成读模式后,就指向1了,如何测试buffer的位置,以及limit和capacity,只要把上面代码的注释去掉即可。 另外buffer还有一些其他的方法:

/**@Description * clear()和compact方法、 * clear():如果调用的是clear()方法,position将被设回0,limit被设置成 capacity的值 * compact():compact()方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。 * limit属性依然像clear()方法一样,设置成capacity。现在Buffer准备好写数据了,但是不会覆盖未读的数据。 *//**@Description * 此外,还有其他一些方法 * mark(),rewind(),reset(),equals()和compareTo()方法 */

3.Scatter和Gather的思想

/**3.Scatter/Gather(分散/聚集) *  Scattering Reads是指数据从一个channel读取到多个buffer中 *  Gathering Writes是指数据从多个buffer写入到同一个channel */

看一下这种思想的用法:

public static void main(String[] args) throws Exception {/*文件输入流*/FileInputStream filInputStream = new FileInputStream(new File("channel1.txt"));filInputStream.available();/*从中获取通道*/FileChannel channel1 = filInputStream.getChannel();/*分配48字节capacity的ByteBuffer*/ByteBuffer bufferHeader = ByteBuffer.allocate(8);ByteBuffer bufferBody = ByteBuffer.allocate(8);ByteBuffer[] bufferArray = {bufferHeader,bufferBody};/*把数据从channel中读取到ByteBuffer*/channel1.read(bufferArray);System.out.println("bufferHeader:"+bufferHeader);System.out.println("bufferBody:"+bufferBody);bufferHeader.flip();while(bufferHeader.hasRemaining()){System.out.println("Channel1.main():"+bufferHeader.get());}ByteBuffer bufferHeader2 = ByteBuffer.allocate(8);ByteBuffer bufferBody2 = ByteBuffer.allocate(8);bufferHeader2.put("a".getBytes());bufferBody2.put("b".getBytes());/*注意切换成读模式,否则channel2中得不到正确的信息*/bufferHeader2.flip();bufferBody2.flip();System.out.println("bufferHeader2:"+bufferHeader2);System.out.println("bufferBody2:"+bufferBody2);ByteBuffer[] bufferArray2 = {bufferHeader2,bufferBody2};/*文件输入流*/FileOutputStream fileOutputStream = new FileOutputStream(new File("channel2.txt"));/*从中获取通道*/FileChannel channel2 = fileOutputStream.getChannel();channel2.write(bufferArray2);}

4.通道之间的数据传输

/**4.通道之间数据传输 * 在Java NIO中,如果两个通道中有一个是FileChannel,那你可以直接将数据 * 从一个channel(译者注:channel中文常译作通道)传输到另外一个channel。 * */
看一下它们的用法:
public static void main(String[] args) throws IOException {/*测试transferFrom方法*/RandomAccessFile fromFile = new RandomAccessFile("channel1.txt", "rw");FileChannel      fromChannel = fromFile.getChannel();RandomAccessFile toFile = new RandomAccessFile("channel2.txt", "rw");FileChannel      toChannel = toFile.getChannel();long position = 0;long count = fromChannel.size();toChannel.transferFrom(fromChannel, position,count);System.out.println("Channel1.main()");}
public static void main(String[] args) throws IOException {/*测试transferTo方法*/RandomAccessFile fromFile = new RandomAccessFile("channel1.txt", "rw");FileChannel      fromChannel = fromFile.getChannel();RandomAccessFile toFile = new RandomAccessFile("channel2.txt", "rw");FileChannel      toChannel = toFile.getChannel();long position = 0;long count = fromChannel.size();fromChannel.transferTo(position, count, toChannel);System.out.println("Channel1.main()");}

5.关于几种流传输速度的比较

传统的流在数据传输方面,可以设置自定义的数组大小,因为一个一个字节的传输肯定是最慢的;下面的测试数据大小为60M

1)数组大小为1024(通常应用中设置的值)时,传输测试:

public static void main(String[] args) throws IOException {/*1.传统的输入输出流,一次读取1024字节*/FileInputStream fileInputStream1 = new FileInputStream(new File("E:\\Majintao\\software1.rar"));FileOutputStream fileOutputStream1 = new FileOutputStream(new File("E:\\Majintao\\software2.rar"));Date dateFirst1 = new Date();int readByte1 = 0;byte[] byte1 = new byte[1024];while(-1 != (readByte1 = fileInputStream1.read(byte1))){fileOutputStream1.write(byte1,0,readByte1);}fileInputStream1.close();fileOutputStream1.close();Date dateLast1 = new Date();Long timeDue1 = dateLast1.getTime()-dateFirst1.getTime();System.out.println("传统流传输时间:"+timeDue1);}
传统流传输时间:794
2)缓冲流,实际上是对流增加了一层缓冲,传输测试:
/*2.使用缓冲流来传输*/FileInputStream fileInputStream2 = new FileInputStream(new File("E:\\Majintao\\software1.rar"));FileOutputStream fileOutputStream2 = new FileOutputStream(new File("E:\\Majintao\\software3.rar"));BufferedInputStream bufferedInputStream1 = new BufferedInputStream(fileInputStream2);BufferedOutputStream bufferedOutputStream1 = new BufferedOutputStream(fileOutputStream2);Date dateFirst2 = new Date();int readByte2 = 0;byte[] byte2 = new byte[1024];while(-1 != (readByte2 = bufferedInputStream1.read(byte2))){bufferedOutputStream1.write(byte2,0,readByte2);}bufferedInputStream1.close();bufferedOutputStream1.close();fileInputStream2.close();fileOutputStream2.close();Date dateLast2 = new Date();Long timeDue2 = dateLast2.getTime()-dateFirst2.getTime();System.out.println("缓冲流传输时间:"+timeDue2);
缓冲流传输时间:189

3)字符流,其实字符流本不应该参与这次的数据比较,因为它的优势是处理字符文件,这里还是把它拿出来测试,传输测试:

/*3.使用字符流*/FileReader fileReader = new FileReader("E:\\Majintao\\software1.rar");FileWriter fileWriter = new FileWriter("E:\\Majintao\\software4.rar");Date dateFirst3 = new Date();char[] char1 = new char[1024];int readByte3 = 0;while(-1 != (readByte3 = fileReader.read(char1))){fileWriter.write(char1);}fileReader.close();fileWriter.close();Date dateLast3 = new Date();Long timeDue3 = dateLast3.getTime()-dateFirst3.getTime();System.out.println("字符流流传输时间:"+timeDue3);
字符流流传输时间:4516
4)自定义接受数组的大小,在使用传统流时,自定义最合适的数组大小,传输测试:

/*4.在上面1的例子上自定义缓冲数组的大小,调整为最合适的大小*/FileInputStream fileInputStream4 = new FileInputStream(new File("E:\\Majintao\\software1.rar"));FileOutputStream fileOutputStream4 = new FileOutputStream(new File("E:\\Majintao\\software5.rar"));Date dateFirst4 = new Date();int readByte4 = 0;byte[] byte4 = new byte[204800];while(-1 != (readByte4 = fileInputStream4.read(byte4))){fileOutputStream4.write(byte4,0,readByte4);}fileInputStream4.close();fileOutputStream4.close();Date dateLast4 = new Date();Long timeDue4 = dateLast4.getTime()-dateFirst4.getTime();System.out.println("传统流传输时间(自定义缓冲数组大小):"+timeDue4);
传统流传输时间(自定义缓冲数组大小):77
5)使用filechannel的transferTo或者transferFrom方法(缺点是只能传输2G以下的数据),传输测试:
/*5.使用fileChannel*/FileInputStream fileInputStream5 = new FileInputStream(new File("E:\\Majintao\\software1.rar"));FileOutputStream fileOutputStream5 = new FileOutputStream(new File("E:\\Majintao\\software6.rar"));FileChannel inFileChannel = fileInputStream5.getChannel();FileChannel outFileChannel = fileOutputStream5.getChannel();Date dateFirst5 = new Date();inFileChannel.transferTo(0,inFileChannel.size(),outFileChannel);fileInputStream5.close();fileOutputStream5.close();inFileChannel.close();outFileChannel.close();Date dateLast5 = new Date();Long timeDue5 = dateLast5.getTime()-dateFirst5.getTime();System.out.println("filechannel传输:"+timeDue5);
filechannel传输:52

可以看到速度比自定义最合适的数组还要快

6)使用filechannel+buffer,传输测试:
/*6.使用fileChannel 通过buffer */FileInputStream fileInputStream6 = new FileInputStream(new File("E:\\Majintao\\software1.rar"));FileOutputStream fileOutputStream6 = new FileOutputStream(new File("E:\\Majintao\\software7.rar"));Date dateFirst6 = new Date();FileChannel inFileChannel6 = fileInputStream6.getChannel();FileChannel outFileChannel6 = fileOutputStream6.getChannel();int readByte6 = 0;ByteBuffer buffer = ByteBuffer.allocate(409600);while(-1 != (readByte6 = inFileChannel6.read(buffer))){buffer.flip();outFileChannel6.write(buffer);buffer.clear();}fileInputStream6.close();fileOutputStream6.close();inFileChannel6.close();outFileChannel6.close();Date dateLast6 = new Date();Long timeDue6 = dateLast6.getTime()-dateFirst6.getTime();System.out.println("filechannel--buffer传输:"+timeDue6);
filechannel--buffer传输:79

可以看到速度等同或者说略慢于自定义最合适数组

PS:来对几种传输做一个比较,同时还会引出两个问题:

/** *在速度的比较上  *最快的是transferFrom和transferTo方法(示例5) *然后是channel-buffer-channel(示例6)和自定义byte(示例4) *接着是缓冲流(示例2) *接着是(示例1),通常我们没有调整缓冲数组大小,直接设置为1024; *接着是字符流(示例3) *下面有两个问题 *1)为什么channel会比较快 *因为底层是C操作 *2)在自定义缓冲数组时(示例4),如何设置最合适的大小?为什么我们通常设置为1024 *这个问题暂时不明白,后面研究时并且补充上;这里提供一个网上的答案: *每次文件读写是以簇为单位,每簇都要消耗时间,如果byte数大于一簇,肯定要多花时间。 不过现在电脑最小的簇也是4K,你的这两个对象没有差别。 *如果你的io流是针对网络,那么就不是簇,而是一个包的大小。有些包的载量比1K小,可能会有些差别。 */

6.同步(阻塞)与异步(非阻塞)

/**6.NIO中--------------关于同步(阻塞),异步(非阻塞),在数据读取和进程推进这两个方面的应用场景 *由于非阻塞,所以线程可以做其他事情,但是这个读写需要监听,你当然可以设置一段时间就尝试读取一次, *但是现在系统中提供了监听的方法,selector; 并且由于理论上可以设置监听多个,因为多个channel同时 *变为可读写的几率并不高,这样在一个线程中相比在多个线程中监听多个channel相比,减少了线程上下文 *切换的开销。  select与非阻塞channel搭配更好,因为在监听到多个channel就绪时,在处理过程中,可能在 *其中一个channel中阻塞,所以这也说明了NIO适合用在"多而小"的情况下,"多"指的是连接数据多,"小"指的 *是一次传输的数据量小。 *与Selector一起使用时,Channel必须处于非阻塞模式下。这意味着不能将FileChannel与Selector一起使用, *因为FileChannel不能切换到非阻塞模式,而套接字通道都可以,意思就是Selector适用于网络编程,后面再讲 */

7.channel方法拾遗

/**7.channel方法拾遗 * close()方法 * position()方法,获得(设置)当前位置 * size()方法 返回所关联文件的大小 * truncate()方法 截取一个文件 */

8.Pipe 管道

/**8.pipe()操作,用于在线程间数据传输 * 多线程管道 是单向的 * sinkChannel 数据写入其中 * sourceChannel 从中读取数据 * 设置完成sinkChannel和sourceChannel之后开启多线程的操作   */
使用示例如下,其中channel1.txt内容为:12345678
public static void main(String[] args) throws IOException {Pipe pipe = Pipe.open();SinkChannel sinkChannel = pipe.sink();Pipe.SourceChannel sourceChannel = pipe.source();Thread thread1 = new Thread(new Runnable() {public void run() {/*文件输入流*/try {FileInputStream filInputStream = null;filInputStream = new FileInputStream(new File("channel1.txt"));/*从中获取通道*/FileChannel channel1 = filInputStream.getChannel();ByteBuffer buf = ByteBuffer.allocate(8);buf.clear();channel1.read(buf);buf.flip();while(buf.hasRemaining()) {sinkChannel.write(buf);}} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}System.out.println("Channel1.main(...).new Runnable() {...}.run()");}});Thread thread2 = new Thread(new Runnable() {public void run() {ByteBuffer buf = ByteBuffer.allocate(8);try {int bytesRead = sourceChannel.read(buf);buf.flip();while(buf.hasRemaining()){System.out.println("Channel1.main():"+buf.get());}} catch (IOException e) {e.printStackTrace();}}});thread1.start();thread2.start();}
结果如下:
Channel1.main(...).new Runnable() {...}.run()Channel1.main():49Channel1.main():50Channel1.main():51Channel1.main():52Channel1.main():53Channel1.main():54Channel1.main():55Channel1.main():56
打印的结果就是12345678的ASCII码

9.NIO和IO总结

/**9.NIO相对IO的特点 * IO             NIO * 面向流         面向缓冲(Buffer) * 阻塞IO         非阻塞IO(Channel) * 无  选择器(Selector) *  * buffer的设计不仅仅是对原有的缓冲的封装,同时还支持了非阻塞IO(channel),为什么这么说: * 每次读写操作都先把数据放到Buffer里面,然后多次调用Channel的写方法对数据进 * 行操作,依靠对Buffer的大小来判断数据的完整性,如果是读方法,则根据read的返回值int判断 */

总结:学习和使用了channel的基本用法, 主要是注意它的一些特点,从而合适的应用场景中想到它,不必详记它的语法~  IO的内容有很多,从来没有系统学习过,后面准备复习和总结下IO的用法。

0 0