面试官:什么是NIO?NIO的原理是什么机制?

面试官:什么是NIO?NIO的原理是什么机制?

NIO和IO到底有什么区别?有什么关系?

首先说一下核心区别:

  1. NIO是以块的方式处理数据,但是IO是以最基础的字节流的形式去写入和读出的。所以在效率上的话,肯定是NIO效率, * d比IO效率会高出很多。
  2. NIO不在是和IO一样用OutputStream和InputStream 输入流的形式来进行处理数据的,但是又是基于这种流的形式,而是采用了通道和缓冲区的形式来进行处理数据的。
  3. 还有一点就是NIO的通道是可以双向的,但是IO中的流只能是单向的。
  4. 还有就是NIO的缓冲区(其实也就是一个字节数组)还可以进行分片,可以建立只读缓s a o e Q q W i A冲区、直接缓冲区和间接缓冲区,只读缓冲区很明显就是字面意思,直接缓冲区是为加快 I/O 速度,而以一种特殊的方式分配其内存的缓冲X j _ 6 |区。
  5. 补充一点:NIO比传统的BIO核心区别就是,NIO采用的是多路2 J J z 0 { 4复用的IO模型,普通的IO用的, L { V c @ q是阻塞的IO模型,两个之间的效率肯定是多路复用效率更高

先了解一下什么是通道,什么是缓冲区的概念

通道是个什么意思?

  • 通道是对原 I/O 包中的流的模拟。到任何目的地(或来自任何地方)的所有数据都必须通过一个 Chanl _ q s E n Knel 对象(通道)。一个 Buffer 实质上是一个容器对象。发, @ = 9 Q N 3送给一8 ! | ; 0a b s h Z通道的所有对象都必须首先放到Q A # n ; F M y缓冲区中;同样地,从通道中读取的任何数据都要读到缓冲区中。Channel是一个f u 1 t S对象,可以通过它读取和写入数据。拿 NIO 与原来的 I/O 做个比较,通道就像是流f f U Y T
  • 正如前面提到的,所有数据都通过 Buffer 对象来处理。您永远不会将字节直接写入通道中{ F % F v c,相反,您是将数据写入包含一个或者多个字节的缓冲区。同样,您不会直接从通道中读取字节,而是将数据从= - C J 2 -通道读入缓冲区,再从缓冲区获取这个字节。

缓冲区是什么意思:X * : & { d W

  • BuffS g w + } xer 是一个对象, 它包含一些要写入或者h Y 4 2 L刚读出的数据。在 NIO 中加入 Buffer 对象,体现了Q % ~ e c m X ! e新库与原 I/O 的一个重要区别。在面向流的 I/O 中,您将数据直接写入或者将数据+ Z i L F , - N S直接读到 Stream 对象中
  • 在 NIO 库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的。在写入数据时,它是写入到缓冲区中的。任何时候访问 NIO 中; 3 (的数据,您都是将它放到缓冲区中。
  • 缓冲区实质上是一个数组。通常它是一个字节数组,但是也可以使用其6 | 3他种类的数组。但是一个缓冲区不 仅仅 是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程

缓冲区的类型:

ByteBuffeX ` MrCharBufferShortBufferIntBufferLongBufferFloatBufferDoubleBuffer

NIO的底层工作原理

先来了解一下buffer的工作机制:

  • capacity 缓冲区数组的总长f k F c
  • position 下一个要操作的数据元素的位置
  • limit 缓冲区数组中不可操作的下一个元素( V | u + W , T的位置,limit<=capacity
  • m+ n O Iark 用于记录当前 position 的前一个位置或者默认是 0

1.这一步其实是当我们刚` 1 Q _ v E开始初始化这个buffer数组的时候,开始默认是这样的

2、但是当你往buffer数组中开始写入的时候几个字节的时候就会变成下面的图,position会移动你数据的结束的下一个位置,这个时候你需要把buffer中的数据写到channel管道中,所以此时我们就需要用这个buffer.flip();方法,

3、D 7当你调用完2中的方法时,这个时候就会变成下面的图了,这样的话其实就可以知道你刚刚写到buffer中的数据是在position---->limit之间,然后下一步调用clear()D P U 5 t -

4、这时底层操作系统就可以从缓冲区中正确读取这 5 个字3 % y节数据发送出去了6 L }。在下一g & 2 &次写数据之前我们再调一下 c* * - 1lear() 方法。缓冲区的索引状态又回到初始位置。(其实这一步有点像IO中的把转运字节数组 char[] buf = new char[1024]; 不足1024字节的部分给强制刷新出去的意思)

补充:

1、这里还要说明一J / K s 0 ) D &下 maM [ z T 2 % nrk,当我们调用 mark() 时,它将记录当前 position 的前一个位置,当我们调用 r, n {esetK 1 9 k 时,positi_ . $ 9 @on 将恢复 mark 记录下来的值

2.clear()方法会:清空整个缓冲区。position将被设回0,limit被设置成 capacity的值(这个个人的理. [ K解就是当你在flip()方法的基础上已经记住你写入了= j = 5 t ) m多少字节M B l k + @ :数据,直接把position到limit之间的也就是你写入已, H P # q % y 4经记住的数据给“复制”到管道中)

3.当你把缓冲区的数局写入到管道中的时候,你. y ] J v a z h 8需要调用flip()方法将Buffer从写模式切换到读模式,调用flip()方法会将position设回0,并X ] }将l, e imit设N 8 @ M | k置成之前position的Z Z p ) * 6 v值。buf.flip();(其实我个人理解的就相当于先记住缓冲区缓冲了多少数据)

NIO 工作代码示例

public void selector() tb U M Zhrows IOException {
//先给缓冲区申请内存空间
  p T A B V l A;      ByteBuffer buffer = ByteBuffer.all7 l Eocat- 3 ^ ye(1024);
  &nbsq d c p;  //打开Selector为了它可以轮询每个 Channel 的状态
 x S _ 1 4 ( J;       Selector selector = Selector.open();
      r 6 B 9 N L U&nbsI # Y | ] h x Tp; ServerSo = ~ i 9cketChannel ssc S A T f a S x;= ServerSocketChannel.open();l H h W P T j
       - q + y y r ssc.configureBlocking(false);//设置为非 a z e w i阻塞方式
        ssc.socket(, A Z *).bind(new InetSocketAddresW P Us(8080));
        ~ + b;ssc.register(selector, SelectionKey.OP_ACCEPT);//注册监听的事件
 &nH k g s I @bsp;      while (true) {
            Set selectedKeys = selector.selectedKeys();//取得所有key集合
  &o } l U P a Fnbspn r 6 + t p ? };        & L r mnbsp;Iterator it&nbq P D 5sp;= selectedKeys.iterator();
            - m / j j u P g IwhS F yile (it.hasNext()) {
           &nbsF x j ) Cp;    SelectionKey ke6 A # g ay&` u 5nbsp;= (SelectionKey) it.next();
           Q z s &nv { 3 Zbsp;   if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKeyC u X d.OP_ACCEPT) {
            &nbsc j r u Z T & Kp;       ServerSocketChi m 3an_ t r Q Qnel ssChannel = (ServerSocketChannel) key.channel();
    &n[ @ V 6 j 5 ? l obsp;            SocketChannel sc = ssChannel.acce1 @ G : Q : + X Mpt();//接受到服务端的请求
                 X 2 ) + P u B l   sc.configureBlocking(fal. y 0 S # C e Y se);
& V S a j v , inbsp;w } H ) 6 5 V  &w . 9 P = #nbsp;&nbse / } U 9 9 8p; P b k x P $ &nG % L K A w fbsp;            sc.r& ^ z ( . A V 1egister(selector, S, * ! KelectionK} $ V yey.OP_READ);
     ] Z J $ T J 1;       &nb4 / Wsp;  W b W 0 / $;     it.remove();
  &nbsm c Q f =p;    &0 r H 1 U J ) vnbsp;        } else if h x # @
       W A c j - = # T f     } q B ` f t x };    ((key.readyOps() & SelectionKey.OP_READ)r 8 ( 3 H s X W Q == SelectionKey.OP_READ) {
       &n` h f ] Cbsp;  &nbsR x O &p;&nbsS * _ A 0 ] Sp;&4 & & | nnbsp;       SocketChannel&nb@ O M K g csp;sc&w I r = @nbsp;= (SocketChannel) keyU ! H U.channel();
        f } ; ! ; |     &nb1 + ` msp; &nbE Y ) 6 S D Wsp;    while&nA p @bsp;(true) {
     &nb/ C D Jsp;    &f N z J _nbsp;      &no U N w e z H . 8bsp;   Z @ L S   buffer.clear();
&nB ` @ ! Q a | nbsp;    & , Y A M | e d enbsp;     T - D K ^ 8 y +;          &o J 2nbj U r z ? gs+ : . } n ` 4 2p;  int&nc F k {bsp;n = sc.read(buffer);//读取数据
    &nbv m ( x zspt H -;  &nb[ r 5 a 8sp;           &nbsg g v ?p;    if (n <= 0) {
    &3 = i `nbsp( } } ~ ] o 1;    M k t g;&n t = 4 F ) U dbsp;     &F x 2 `nbsp;           &nq 2 X D : m %bsp;break;
  &nb$ s [ Y 0 9 N 1 Csp;                     }
  Q ; p - ` a 6 N ~       &n; s LbsK / y 3 1 } Kp;&nX X =bsp;       : ] L & W w )      buffer.flip();
       i @ ,      &n+ u T 3 l obsp;      }
    &nb3 ; s ~ S / & :sp; } { 8 t;      t f l 3 q  P C # y % a;      it.remove^ P c O();
    X w & X ( |;            }
            }
&n~ S u p C dbsp;       }
}

最后给大家看g ` m 2 ] D D一下整体的NIO的示意图

NIO和Netty的工作{ ` t模型对比?

(1)NIO的工作流程步骤:

  1. 首先是先创建ServerSocketChannel 对象,和S P M Y { O真正处理业务的线程池
  2. 然后给刚刚创建的ServerSocketChannel 对象进行绑定一个对应的端口,然后设置为非阻塞
  3. 然后创建Selector对象并打开,然后把这Selector对象注册到S9 A ` F # , AerverSockP D = etChaT f o D 9 8nnel 中,并? j M b设置好监听的事件,监听 SelectionKey.OP_ACCEP1 + _ K v h A A :T
  4. 接着就是Selector对b 2 # P 8象进行死循环监听每一个Channel通道的事件,循环执行 SelectoI C } b a f L Gr.select() 方法,轮询就绪的 Channel
  5. 从Selector中获取所有的SelectorKey(这个就可以看成是不同的事件),如果SelectorKey是处于 OP_ACCEPT 状态,说明是新的客户端接入,调用 ServerSocketChannel.accept 接收新的客户端。
  6. 然后对这个把这个接受的新客户端的Channe1 . Q - s S Z 9l通道注册到{ D : FServerSocketC$ # l ? % J Mhannel上,并且把之前的OP_ACCEPT 状态改为SelectionKey.OP_READ读取事件状态,并且设置为非阻塞的,% o T r `然后把当前的这个SelectorKey给移除掉,说明这个事件完成了
  7. 如果第5e a h t V k L步的时候过来的事件不是OP_ACCEPT 状态,那就是OP_Rf r k ^ w Y K 2 yEa 0 `AD读取数据的事件状态,然后调用本文章的上C ! 2 M Q P D Y J面的那个读取数R m 6 `据的机制就可以了

(2)Netty的工作流程步骤:

  1. 创建 NIO 线程组 EventLoopGroup 和 ServerBootstrap。
  2. 设置 ServerBootstrap[ u 的属性:线程组、SO_BACKLOG 选项,设置 NioServerSocketChannel 为 Channel,设置业务处] 4 t 8 g r理 Handler
  3. 绑定端口,启动服务器程序。
  4. 在业务处理 TimeServerHandler 中W 6 W S 1,读取客户端发送的数据,并给出响应

(3)两者之间的区别:

  1. OP_ACCEPT 的处理被简化,因为对于 accept 操作的处理在不同业务上都是一致的U S ( n M # y
  2. 在 NIO 中需要自己构建 ByteBufi Y ) f 2 @fer 从 Channel 中读取数据,而 Netty 中数据是直接读取完成存放在 ByteBuf 中的。相当于x * s省略了用I _ t G y [ r C户进程从内核中复制数据的过程。
  3. 在 Netty 中,我们看到有使用一个解码器 FixedLengthFrameDecoder,可以用于处理定H ~ W e ?长消息的问题,能够解决 TCP 粘包读半包问题,十分方便。

Java知音,专I ( A /注于JavS | h qa实用文章推送,不容错过!

来源:https://blog.csdn.net/qq_36520235/

上一篇

越来越不像本人的明星,杨子姗变网红脸,吴昕似柳岩,舒畅失灵气

下一篇

战争电影不止有《血战钢锯岭》,还有这几部

你也可能喜欢

  • 暂无相关文章!

发表评论

您的电子邮件地址不会被公开。 必填项已用 * 标注

提示:点击验证后方可评论!

插入图片
返回顶部