美文网首页
【Java高级】Java NIO详解

【Java高级】Java NIO详解

作者: 大栗几 | 来源:发表于2020-06-20 21:50 被阅读0次

    本文为原创文章,转载请注明出处
    查看[Java]系列内容请点击:https://www.jianshu.com/nb/45938443

    Java NIO就是Java New IO的缩写,关于老的Java IO的进阶说明请移步:【Java高级】Java IO进阶

    Java NIO主要依靠三个类:ChannelBufferSelector实现了一系列高效的异步阻塞IO模型。与同步IO相比,其具有高效、简单和异步的特点,使得程序可以在一个线程内管理多个输入、输出。

    他们之间的关系可以描述为:

    • BufferChannel的底层存储结构,是按照字节、数字等存储的
    • Channel是一种通道,是对于文件的一种抽象,它使得我们可以访问诸如内存映射、文件加锁机制和文件间数据的快速传递等操作系统特性
    • Selector轮询Channel被注册的的事件,一旦Channel有事件通知,就处理发生在Channel上的事件

    Buffer底层

    Buffer是一个抽象类,实现了它的类包括ByteBuffer IntBuffer CharBuffer等,分别以byte int char等类型的数组作为存储单元。在NIO中,数据只能写到Buffer中,也读取的数据也只能存储在Buffer中。

    Buffer的底层使用数组来进行存储,除了数组以外,使用三个指针来控制其读写内容:

    • capacity: 容量,就是数组的大小
    • position: 初始为0,下一次读写的位置指针(数组下标)
    • limit: 读写的极限位置,可被读写的数据区数组下标(比如数据大小为20,其中存储了10条数据,那么在读的时候limit=9)

    此外,还有一个mark字段,与输入流类似,可以在当前位置打标签,然后通过reset将读取位置position设置到mark,即可实现重复数据读取。

    在进行Buffer的使用之前需要先申请数组的大小,使用allocate方法来申请缓冲区数据大小。然后通过对这个数组的反复读写来实现数据的缓存功能

    Buffer常用的一些方法如下:

    • public Buffer flip():读写模式转换,从读模式转换到写模式或者从写模式转换到读模式
    • public Buffer mark(): 在当前位置打标记
    • public Buffer reset(): 将position设置成mark位置
    • public Buffer clear(): 清空数据(实际是指针初始化为初始值)
    • public Buffer rewind(): position复位到0,可实现数据重复读取
    • public T compact(): 整理,将数据移动到从数据头开始

    Channel

    Channel是一种文件通道,通过Channel可以获取文件的各种属性等,它与上篇中介绍的流库很类似,但是Channel只能通过与Buffer交互来进行数据访问。同时,通道是双向的、可异步读写的。操作文件的是FileChannel

    下面代码演示了FileChannelByteBuffer交互处理数据:

    public class Test {
    
        public static void main(String[] args) throws Exception {
            writeData();
            readData();
        }
    
        // 写数据
        public static void writeData() throws Exception {
            FileChannel channel = FileChannel.open(Path.of("D:\\data.txt"), StandardOpenOption.WRITE); // 写模式
            ByteBuffer buffer = ByteBuffer.allocate(100);
            buffer.put("this is a test buffer.".getBytes());
            buffer.flip(); // 将buffer变为读模式,方便channel读
            channel.write(buffer); // 从buffer读数据
            channel.close();
        }
    
        // 读数据
        public static void readData() throws Exception {
            FileChannel channel = FileChannel.open(Path.of("D:\\data.txt"), StandardOpenOption.READ); // 读模式
            ByteBuffer buffer = ByteBuffer.allocate(100);
    
            int readSize = -1;
            do {
                readSize = channel.read(buffer); // 从channel读到buffer,这里buffer是写模式
                System.out.println("\nread size: " + readSize);
                buffer.flip(); // 转换为读模式
    
                // 取buffer中的数据
                while (buffer.hasRemaining()) System.out.print("/" + (char) buffer.get());
    
                buffer.clear();
            } while (readSize != -1);
    
            channel.close();
        }
    }
    

    输出结果:

    read size: 22
    /t/h/i/s/ /i/s/ /a/ /t/e/s/t/ /b/u/f/f/e/r/.
    read size: -1
    

    Selector

    Selector通过【注册-轮询】的机制来使得程序允许以异步的方式获取输入输出。一个Selector中可以注册多个Channel,然后Selector就不断轮询这些Channel,一旦事件发生,在Selector里面就可以手动获取到。

    首先,创建一个Selector

    Selector selector = Selector.open();
    

    其次,将Channel设置为非阻塞模式:

    channel.configureBlocking(false);
    

    注意的是,只有继承自SelectableChannel或者AbstractSelectableChannel的类才可以设置为非阻塞模式,才能使用Selector,我们上面使用的FileChannel不可以使用Selector,而SocketChannel SocketServerChannel等可以使用Selector

    接着,可以为Selector注册感兴趣的事件:

    SelectionKey key = channel.register(selector, SelectionKey.OP_WRITE);
    

    第一个参数表示要注册的选择器,第二个参数表示感兴趣的事件,事件有多种。

    SelectionKey中有一系列的方法来表示该通道是否就绪,是否可读、可写等,在实际使用过程中,也是使用SelectionKey来查询通道的状态。

    AIO

    AIO是一种除了注册事件以外,还注册了事件处理器的模型,可以初步理解为使用了观察者模式的NIO,这样就不用使用循环来不断轮询Selector状态了。

    具体的实现可以自行查阅

    相关文章

      网友评论

          本文标题:【Java高级】Java NIO详解

          本文链接:https://www.haomeiwen.com/subject/imdgxktx.html