NIO

来源:互联网 发布:魔乐科技java笔记 编辑:程序博客网 时间:2024/06/04 18:18

传统的IO流都是都是阻塞式的输入、输出。比如在读取输入流中的数据时,如果没有读取到有效数据,程序将会在此处阻塞该线程的执行。
并且传统的IO流都是通过字节的移动来处理的(即使不直接去处理字节流,但底层的实现还是依赖于字节处理),也就是说,面向流的输入/输出系统一次只能处理一个字节,因此面向流的输入输出的效率不高。
从JDK1.4开始,Java提供了一系列改进的输入/输出处理的新功能,这些功能被统称为新IO(New IO,简称NIO)。

NIO它既可以说成“新I/O”,也可以说成非阻塞式I/O。下面是java NIO的工作原理:
1. 由一个专门的线程来处理所有的 IO 事件,并负责分发。
2. 事件驱动机制:事件到的时候触发,而不是同步的去监视事件。
3. 线程通讯:线程之间通过 wait,notify 等方式通讯。保证每次上下文切换都是有意义的。减少无谓的线程切换。

      NIO(New IO,简称NIO),使用内存映射文件的方式来处理输入/输出,将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样来访问文件了(这种方式模拟了操作系统上的虚拟内存的概念)。通过这种方式来进行输入/输出比传统的输入/输出要快得多。
      在NIO中,所有数据都需要通过通道Channel传输。它提供了一个map()方法,通过该方法可以直接将“一块数据”映射到内存中,是面向块的处理。
      发送到Channel中的所有对象都必须首先放到Buffer中,而从Channel中读取的数据也必须先放到Buffer中。Buffer可以被理解为一个容器,它的本质是一个数组。Buffer是一个抽象类,它的常用子类是ByteBuffer,它可以在底层字节数组上进行get/set操作。其他子类比如CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。
这里写图片描述


Channel

Channel与传统的流的区别:
(1) Channel可以直接将指定文件的部分或全部直接映射成Buffer
(2) 程序不能直接访问Channel中的数据,包括读写,Channel只能和Buffer进行交互
     也就是说,如果要从Channel中取得数据,必须先用Buffer从Channel中取出一些数据,然后让程序从Buffer中取出这些数据;
     如果要将程序中的数据写入Channel,先让程序将数据放入Buffer中,程序再将Buffer里的数据写入Channel中

Channel接口的实现类有:
DatagramChannel、FileChannel、Pipe.SinkChannel、Pipe.SourceChannel、SelectableChannel、ServerSocketChannel、ServerSocketChannel、SocketChannel等

所有的 Channel都不可以直接通过构造器来创建,而是通过传统的节点 InputStream、OutputStream 的 getChannel()方法来返回对应的Channel,不同的节点流对象获得的CHannel不一样。比如FileInputStream的 getChannel()返回的是FileChannel,PipedInputStream的getChannel()返回的是Pipe.SinkChannel、Pipe.SourceChannel。

Channel中常用的三类方法:
(1)public abstract MappedByteBuffer map(FileChannel.MapMode mode,long position,long size):将Channel对应的部分或
        全部数据映射成ByteBuffer。
        第一个参数:只读(MapMode.READ_ONLY) 、读/写(MapMode.READ_WRITE) 等模式。
(2)read():从Buffer中读取数据
(3)write():向Buffer中写数据


Buffer

Buffer中三个重要的概念:
(1) 容量 (capacity):缓冲区的容量capacity表示该Buffer的最大数据容量,即最多可以存储多少数据。capacity不可能为负值,创建后不能被改变。
(2) 界限 (limit):第一个不应该被读出或者写入的缓冲区位置索引。也就是说位于limit后面的数据即不可被读,也不可被写。
(3) 位置 (position):用于指明下一个可以被读出的或者写入的缓冲区位置索引。当使用Buffer从Channel中读取数据时,position的值恰好等于已经读到了多少数据。当刚刚新建一个Buffer对象时,其position为0;如果从Channel中读取了2个数据到该Buffer中,则position为2,指向Buffer中第3个位置(索引从0开始)。
这里写图片描述


Buffer的作用就是装入数据,然后输出数据:
(1) 装入数据。开始时的Buffer的position为0,limit为capacity,程序可以通过put()方法向Buffer中放入一些数据(或从Channel中获取一些数据),每放入一些数据,Buffer的position相应地向后移动一些位置。
(2) 装入数据结束后,调用Buffer的flip()方法,该方法将limit设置为position的位置,将position设为0,这就使Buffer的读写指针又移到了开始位置。该方法调用之后,Buffer为输出数据做好准备。
(3) 输出数据。使用Buffer的get()方法取出数据。
(4) 输出数据结束后,Buffer调用clear()方法,clear()方法不是清空数据,而是将position置为0,将limit置为capacity,再次为Buffer装入数据做准备。
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述


当使用get()和put()方法访问Buffer中的数据时,分为绝对和相对两种:
(1) 相对 (Relative):从Buffer的当前position处开始读取或写入,然后将位置(position)的值按处理元素的个数增加
(2) 绝对 (Absolute):直接根据索引向Buffer中读取或写入数据,使用绝对方式访问Buffer里的数据时,并不会影响位置(position)的值

public class BufferTest {    public static void main(String[] args) throws IOException {        // 创建Buffer        CharBuffer charBuffer = CharBuffer.allocate(8);        System.out.println("刚创建Buffer之后的position、limit、capacity分别为:" + charBuffer.position() + " "+ charBuffer.limit()+ " "         + charBuffer.capacity());        System.out.println();        // 放入数据        charBuffer.put('i');        charBuffer.put('y');        System.out.println("放入2个数据");        System.out.println("放入数据后的position、limit、capacity分别为:" + charBuffer.position() + " " + charBuffer.limit() + " "                + charBuffer.capacity());        System.out.println();        // 调用flip()方法(调用完这个方法之后,就为输出数据做准备,position置为0,limit置为position所在的位置)        charBuffer.flip();        System.out.println("调用flip()方法,为读数据做准备。读的范围应该是所写入数据的范围0-position,因此把limit置为position,再把position置为0");        System.out.println("调用flip()方法之后的position、limit、capacity分别为:" + charBuffer.position() + " " + charBuffer.limit()                + " " + charBuffer.capacity());        System.out.println();        // 相对:取出元素,position的值会改变        System.out.println("以相对Relative的方式取值");        System.out.println("取出第一个元素(position=0):" + charBuffer.get());        System.out.println("取出第一个元素之后position变成了:" + charBuffer.position());        System.out.println();        // 调用clear()方法        charBuffer.clear();        System.out.println("调用clear方法,为装入数据做准备,因此position置为0,limit置为capacity");        System.out.println("调用clear()方法之后的position、limit、capacity分别为:" + charBuffer.position() + " "                + charBuffer.limit() + " " + charBuffer.capacity());        System.out.println("执行clear()方法之后,缓冲区的内容并没有被清除,第一个元素为:" + charBuffer.get(1));        System.out.println();        // 放入数据        System.out.println("重新放入两个字符数据");        charBuffer.put('n');        charBuffer.put('m');        System.out.println("放入数据后的position、limit、capacity分别为:" + charBuffer.position() + " " + charBuffer.limit() + " "                + charBuffer.capacity());        System.out.println();        charBuffer.flip();        System.out.println("根据索引取数据,第二个数据为:" + charBuffer.get(1));        System.out.println("根据position取数据:" + charBuffer.get());    }}

控制台输出:

刚创建Buffer之后的position、limit、capacity分别为:0 8 8放入2个数据放入数据后的position、limit、capacity分别为:2 8 8调用flip()方法,为读数据做准备。读的范围应该是所写入数据的范围0-position,因此把limit置为position,再把position置为0调用flip()方法之后的position、limit、capacity分别为:0 2 8以相对Relative的方式取值取出第一个元素(position=0):i取出第一个元素之后position变成了:1调用clear方法,为装入数据做准备,因此position置为0,limit置为capacity调用clear()方法之后的position、limit、capacity分别为:0 8 8执行clear()方法之后,缓冲区的内容并没有被清除,第一个元素为:y重新放入两个字符数据放入数据后的position、limit、capacity分别为:2 8 8根据索引取数据,第二个数据为:m根据position取数据:n

使用NIO复制文件:

public class NioCopyFileTest {    public static void nioCopyFile(String resource,String destination) throws IOException{        //文件输入流        FileInputStream fis = new FileInputStream(resource);        //文件输出流        FileOutputStream fos = new FileOutputStream(destination);        //读文件通道        FileChannel readChannel = fis.getChannel();        //写文件通道        FileChannel writeChannel = fos.getChannel();        //创建一个容量为1024的ByteBuffer对象        ByteBuffer buffer = ByteBuffer.allocate(1024);        while (true) {            buffer.clear();                         //清空buffer中的内容            int len = readChannel.read(buffer);     //读入数据            if(len == -1){                          //读取完毕                break;                                              }            buffer.flip();                          //将写模式转换为读模式。            writeChannel.write(buffer);             //写入数据        }        fis.close();        fos.close();    }    public static void main(String[] args) throws IOException {        NioCopyFileTest.nioCopyFile("D:\\temp_buffer.txt", "c:\\temp_buffer.txt");    }}
原创粉丝点击