真正理解NIO

前言

高并发量引起的问题

一个使用传统阻塞I/O的系统,如果还是使用传统的一个请求对应一个线程这种模式,一旦有高并发的大量请求,就会有如下问题: 

1、线程不够用, 就算使用了线程池复用线程也无济于事; 

2、阻塞I/O模式下,会有大量的线程被阻塞,一直在等待数据,这个时候的线程被挂起,只能干等,CPU利用率很低,换句话说,系统的吞吐量差; 

3、如果网络I/O堵塞或者有网络抖动或者网络故障等,线程的阻塞时间可能很长。整个系统也变的不可靠;

什么是NIO

java.nio全称java non-blocking IO(实际上是 new io),是指JDK 1.4 及以上版本里提供的新api(New IO) ,为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络。

HTTP2.0使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比HTTP1.1大了好几个数量级。

IO和NIO的区别

原有的 IO 是面向流的、阻塞的,NIO 则是面向块的、非阻塞的。

怎么理解IO是面向流的、阻塞的

java1.4以前的io模型,一连接对一个线程。

原始的IO是面向流的,不存在缓存的概念。Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区

Java IO的各种流是阻塞的,这意味着当一个线程调用read或 write方法时,该线程被阻塞,直到有一些数据被读取,或数据完全写入,该线程在此期间不能再干任何事情了。

怎么理解NIO是面向块的、非阻塞的

NIO是面向缓冲区的。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就增加了处理过程中的灵活性。

Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。

通俗理解:NIO是可以做到用一个线程来处理多个操作的。假设有10000个请求过来,根据实际情况,可以分配50或者100个线程来处理。不像之前的阻塞IO那样,非得分配10000个。



 

NIO的核心实现

在标准IO API中,你可以操作字节流和字符流,但在新IO中,你可以操作通道和缓冲,数据总是从通道被读取到缓冲中或者从缓冲写入到通道中。

NIO核心API :

1)Buffer:它是包含数据且用于读写的线形表结构(字节数组)。其中还提供了一个特殊类用于内存映射文件的I/O操作。一个 Buffer 实质上是一个容器对象。发送给一个通道的所有对象都必须首先放到缓冲区中;同样地,从通道中读取的任何数据都要读到缓冲区中。

2)Charset:它提供Unicode字符串影射到字节序列以及逆影射的操作(编码及解码器)。

3)Channels:通道是对原 I/O 包中的流的模拟。到任何目的地(或来自任何地方)的所有数据都必须通过一个 Channel 对象。包含socket,file和pipe三种管道,它实际上是双向交流的通道(与流的不同之处在)。

4)Selector:它将多元异步I/O 操作集中到一个或多个线程中
 

通道Channel

NIO的通道类似于流,但有些区别如下:

1. 通道可以同时进行读写,而流只能读或者只能写

2. 通道可以实现异步读写数据

3. 通道可以从缓冲读数据,也可以写数据到缓冲: 

缓存Buffer

缓冲区本质上是一个可以写入数据的内存块,然后可以再次读取,该对象提供了一组方法,可以更轻松地使用内存块,使用缓冲区读取和写入数据通常遵循以下四个步骤:

1. 写数据到缓冲区;

2. 调用buffer.flip()方法;

3. 从缓冲区中读取数据;

4. 调用buffer.clear()或buffer.compat()方法;

当向buffer写入数据时,buffer会记录下写了多少数据,一旦要读取数据,需要通过flip()方法将Buffer从写模式切换到读模式,在读模式下可以读取之前写入到buffer的所有数据,一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。

Buffer在与Channel交互时,需要一些标志:

buffer的大小/容量 - Capacity

作为一个内存块,Buffer有一个固定的大小值,用参数capacity表示。

信息末尾的位置 - limit  ,上限,缓冲区中第一个不能被读或写的元素,或者说,缓冲区中现存元素的计数。

当前读/写的位置 - Position​

当写数据到缓冲时,position表示当前待写入的位置,position最大可为capacity – 1;当从缓冲读取数据时,position表示从当前位置读取。

Mark   标记,一个备忘位置

在写模式下,缓冲区的limit表示你最多能往Buffer里写多少数据; 写模式下,limit等于Buffer的capacity,意味着你还能从缓冲区获取多少数据。

下图展示了buffer中三个关键属性capacity,position以及limit在读写模式中的说明:


 
    public void test(File inFile,File outFile) throws IOException{
        
        // 获取源文件和目标文件的输入输出流
        FileInputStream fis = new FileInputStream(inFile);
        FileOutputStream fos = new FileOutputStream(outFile);
        // 获取输入输出通道
        FileChannel fcIn = fis.getChannel();
        FileChannel fcOut = fos.getChannel();
        // 创建缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024 );
        while (true) {
            // clear 方法重设缓冲区,使它可以接受读入的数据
            buffer.clear();
            // 从输入通道中将数据读到缓冲区
            int r = fcIn.read(buffer);
            //
            if (r==-1) {
                break;
            }
            // filp 方法让缓冲区可以将读入的数据写入另一个通道
            buffer.flip();
            // 将这些数据从缓冲区写到输出通道
            fcOut.write(buffer);
        }
        
    }

缓冲区常用的操作

向缓冲区写数据:

    1. 从Channel写到Buffer;

    2. 通过Buffer的put方法写到Buffer中;

从缓冲区读取数据:

    1. 从Buffer中读取数据到Channel;

    2. 通过Buffer的get方法从Buffer中读取数据;

flip方法:

     将Buffer从写模式切换到读模式,将position值重置为0,limit的值设置为之前position的值;

clear方法 vs compact方法:

       clear方法清空缓冲区;compact方法只会清空已读取的数据,而还未读取的数据继续保存在Buffer中;



Selector

一个组件,可以检测多个NIO channel,看看读或者写事件是否就绪。

多个Channel以事件的方式可以注册到同一个Selector,从而达到用一个线程处理多个请求成为可能。


 


作者:萧熏儿
链接:https://www.jianshu.com/p/362b365e1bcc
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
原文地址:https://www.cnblogs.com/yrjns/p/12906824.html