• Java NIO的工作方式


    NIO的工作机制

    为了了解NIO,我们先看一下NIO的相关类图,如下图所示:

    上图中有两个关键类Channel和Selector,他们是Java NIO的核心。举个例子,我们把Channel比作高铁,则Selector就是高铁的调度系统,负责监控每列高铁的运行状态,是出站还是在路上,也就是说Selector可以轮询Channel的状态。还有一个Buffer类,可以将它比作高铁上的座位,至于是一等座还是二等座我们不得而知。了解了上面的例子,我们具体看一下NIO是如何工作的,下面是一段典型的NIO代码:

    public void selector() throws IOException {
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            Selector selector = Selector.open();
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            //设置非阻塞方式
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.socket().bind(new InetSocketAddress(80));
            //注册监听事件
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            while(true){
                Set<SelectionKey> keys = selector.selectedKeys();
                Iterator<SelectionKey> its = keys.iterator();
                while(its.hasNext()){
                    SelectionKey selectionKey = its.next();
                    if((selectionKey.readyOps() & SelectionKey.OP_ACCEPT) ==SelectionKey.OP_ACCEPT){
                        ServerSocketChannel channel = (ServerSocketChannel) selectionKey.channel();
                        //接收到请求
                        SocketChannel socketChannel = channel.accept();
                        socketChannel.configureBlocking(false);
                        socketChannel.register(selector,SelectionKey.OP_READ);
                        its.remove();
                    }else if((selectionKey.readyOps() & SelectionKey.OP_READ) ==SelectionKey.OP_READ){
                        SocketChannel sc = (SocketChannel) selectionKey.channel();
                        while(true){
                            buffer.clear();
                            int n = sc.read(buffer);
                            if(n<=0){
                                break;
                            }
                            buffer.flip();
                        }
                        its.remove();
                    }
                }
            }
        }
    

    调用Selector的静态方法创建一个Selector,创建一个服务端的Channel绑定到Socket对象上,把这个通信信道注册到选择器上并设置为非阻塞模式。然后调用Selector的selectedKeys方法检查注册到选择器上的所有通信信道是否有感兴趣的事件发生。如果有事件发生则返回所有的SelectionKey,通过SelectionKey的channel方法可以取得通信信道,从而读取通信数据。

    上图描述了基于NIO的Socket请求处理过程。Selector可以同时监听一组通信信道上的IO状态,前提是这些通信信道已经注册到Selector上。Selector可以调用select方法检查通信信道上的IO是否已经准备好,如果监听的所有通信信道上没有状态变化,那么select方法会阻塞或则超时返回0。如果有多个通信信道有数据,则把这些数据分配到对应的Buffer中。所以关键的地方是,有一个线程处理所有链接的数据交互,每个链接的数据交互都不是阻塞的,所以可以同事处理大量的请求。

    Buffer的工作方式

    可以把Buffer理解为一组基本数据类型的元素列表 ,它通过几个变量保存这些数据的当前位置状态,也就是有四个索引。

    • capacity:缓存区数据的总长度
    • position:下一个要操作的元素的位置
    • limit:缓存区中不可操作的下一个元素的位置limit<=capacity
    • mark:用于记录当前position的前一个位置或则默认是0

    他们的关系如下图:

    我们通过ByteBuffer.allocate(11)创建一个11个字节的数组缓存区,出事状态下position为0,capacity和limit都是默认长度,如上图。

    当我们向Buffer中添加五个元素后,position的值变为5,而limit和capacity的值不变,如下图:

    当我们调用buffer.flip()方法后,position变为0,limit变为5,capacity不变,如下图:这时候底层操作系统就可以从Buffer中读取5个字节并发射出去。

    当下一次写数据之前,调用clear方法,缓冲区的索引状态又回到初始状态。当我们调用mark()方法的时候,他将记录当前position的前一个位置,调用reset后,position将恢复mark记录下的位置。

    有一点需要说明,通过Channel获取的IO数据首先要经过操作系统的Socket缓冲区,再将数据复制到Buffer中,这个操作系统缓冲区就是TCP关联的RECEQ和SENDQ队列,从操作系统缓冲区到用户缓冲区的数据复制比较耗性能,BUffer提供了一种直接操作操作系统缓冲区的方式ByteBuffer.allocateDirect,这个方法返回的DirectByteBuffer就是关联的底层操作系统缓冲区,他通过Native代码操作非JVM堆的内存空间,每次的创建和释放都调用System.gc,容易引起JVM内存泄露问题。

    NIO数据访问方式

    NIO提供过了比传统的文件访问方式更好的文件访问方式

    • FileChannel.transferFrom,FileChannel.transferTo
    • FileChannel.map

    FileChannel.transferXXX

    FileChannel.transferXXX相比传统方法可以减少从内核到用户空间的复制,数据直接在内核中移动,在Linux中使用的是sendfile系统调用。

    FileChannel.map

    FileChannel.map按照一定的大小块把文件映射为内存区域,程序访问内存区域的时候,直接访问文件系统,这种方式省去了从内核空间到用户空间的复制的损耗,适合对大文件的只读操作。但是这种方式与操作系统的底层实现有关。

  • 相关阅读:
    弹性计算双周刊 第24期
    【阿里云新品发布·周刊】第8期:数字化风暴已经来临!云+区块链,如何颠覆未来科技?
    洞见数据库前沿 阿里云数据库最强阵容 DTCC 2019 八大亮点抢先看
    开发者招聘节 | 2019阿里巴巴技术面试题分享(陆续放出)
    bzoj1856: [Scoi2010]字符串
    bzoj1257: [CQOI2007]余数之和sum
    bzoj1088: [SCOI2005]扫雷Mine
    noip2015 运输计划
    noip2015 子串
    noip2015 斗地主
  • 原文地址:https://www.cnblogs.com/senlinyang/p/8253223.html
Copyright © 2020-2023  润新知