Java学习之NIO相关

文章目录
  1. Java NIO学习小结
    1. I. 基本概念
      1. 1. 同步/非同步IO
        1. a. 同步IO
        2. b. 异步IO
        3. c. 区别
      2. 2. 阻塞/非阻塞IO
        1. a. 阻塞IO
        2. b. 非阻塞IO
      3. 3. 五种IO模型
        1. a. 阻塞IO模型
        2. b. 非阻塞IO模型
        3. c. 多路复用IO模型
        4. d. 信号驱动IO模型
        5. e. 异步IO模型
        6. f. 说明
    2. II. NIO
      1. 1. 基础知识
        1. a. Channel
        2. b. Buffer
        3. c. selector
    3. III. NIO与IO对比
      1. 1. Java NIO提供了与标准IO不同的IO工作方式:
      2. 2. 使用场景
      3. 3. 区别
    4. IV. 其他
      1. 参考
      2. 个人博客: Z+|blog
      3. 声明
      4. 扫描关注

Java NIO学习小结

前面一篇主要学习了下IO的流式操作,接下来就是重头戏了,NIO,又称为New IO

当然也是得抱着问题来学习这个东西了,希望可以通过本文,可以学习到:

  • 什么是NIO
  • NIO相比较与IO有什么特点
  • 同步,非同步,阻塞,非阻塞是什么鬼
  • 几种IO模型

I. 基本概念

首先理解下什么是同步IO,非同步IO,什么是阻塞IO,非阻塞IO,它们两对的主要区别是什么;其次就是五种IO模型

1. 同步/非同步IO

a. 同步IO

同步,主要是多个线程的执行中,对彼此的执行结果有依赖,即某个线程的执行,必须要求他依赖的线程二执行完毕

同步IO,表示在发起IO操作之后,如果数据没有准备就绪,就需要用户线程轮询的去询问是否准备好,只有准备好之后,将数据从内核拷贝到用户线程

b. 异步IO

异步,指多个任务可以并发的执行,他们比吃执行结果,是否执行完毕对其他都没有影响

异步IO,用户线程发起IO操作,该用户线程可以继续执行其他的事情;剩下的数据是否准备完毕,准备完毕之后从内核拷贝到用户都有内核自动完成

c. 区别

同步IO和异步IO的主要区别就在于:

  • 用户线程发起IO操作之后,是否可以干其他的事情(同步IO需要轮询判断数据是否准备就绪;异步IO不需关心)
  • 数据从内核拷贝到用户线程
  • 同步IO会阻塞用户线程;异步IO不会

2. 阻塞/非阻塞IO

a. 阻塞IO

阻塞,指在执行过程中,没有获得预期的结果,就一直阻塞等待获取到结果

阻塞IO,在发起IO请求之后,若数据没有准备好,就一直阻塞等待数据准备完毕

b. 非阻塞IO

非阻塞,表示在执行过程中,若某个条件未满足,则直接返回个标识,它继续去干其他的事情

非阻塞IO,在发起IO请求之后,若数据没有准备好,就返回一个对应标识,它继续干其他的事情

3. 五种IO模型

a. 阻塞IO模型

最传统的IO模型,在读写数据时,未准备就绪,则阻塞用户线程,释放CPU资源,当数据准备就绪之后,内核将数据拷贝到用户线程,用户线程取消阻塞状态

b. 非阻塞IO模型

在读写数据时,直接返回结果,如果没有准备好,则自己实现逻辑,轮询的去判断是否准备就绪

当准备完毕之后,再次轮组发起IO请求,就可以将数据拷贝到用户线程

这个过程中,虽然没有释放CPU资源,但是轮询的判断是非常消耗性能的

c. 多路复用IO模型

Java NIO实际上就是多路复用IO。

在多路复用IO模型中,会有一个线程不断去轮询多个socket的状态,只有当socket真正有读写事件时,才真正调用实际的IO读写操作。因为在多路复用IO模型中,只需要使用一个线程就可以管理多个socket,系统不需要建立新的进程或者线程,也不必维护这些线程和进程,并且只有在真正有socket读写事件进行时,才会使用IO资源,所以它大大减少了资源占用

在Java NIO中,是通过selector.select()去查询每个通道是否有到达事件,如果没有事件,则一直阻塞在那里,因此这种方式会导致用户线程的阻塞。

d. 信号驱动IO模型

在发起IO请求时,注册一个信号驱动钩子,然后自己干自己的事情

当数据准备就绪之后,发送一个信号给用户线程,然后用户线程执行自己注册的钩子,在内部实现真实的IO操作

e. 异步IO模型

异步IO模型才是最理想的IO模型,在异步IO模型中,当用户线程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从内核的角度,当它受到一个asynchronous read之后,它会立刻返回,说明read请求已经成功发起了,因此不会对用户线程产生任何block。然后,内核会等待数据准备完成,然后将数据拷贝到用户线程,当这一切都完成之后,内核会给用户线程发送一个信号,告诉它read操作完成了。也就说用户线程完全不需要实际的整个IO操作是如何进行的,只需要先发起一个请求,当接收内核返回的成功信号时表示IO操作已经完成,可以直接去使用数据了

f. 说明

  • 前面四种都属于同步IO(在内核进行数据拷贝都会引起用户线程阻塞),只有最后一个是异步IO
  • 异步IO和信号驱动IO的主要区别在于具体的数据处理上

II. NIO

为了解决传统IO的阻塞问题引入的,主要原理如下:

  • 一个专门的线程来处理所有的 IO 事件,并负责分发
  • 事件驱动机制
  • 线程通讯通过 wait/notify 等方式通讯,较少线程切换

1. 基础知识

NIO新定义了三个基本角色:Channel, Buffer, Selector

a. Channel

类似IO中的流,但又有不同

  • 支持读写(而流是单向的)
  • 与Buffer进行交互(即写入到buffer,从buffer中读取)
  • 支持异步

常见的Channel有四种

  • FileChannel : 文件,不支持非阻塞方式
  • DatagramChannel:UDP网络数据
  • SocketChannel:TCP读写网络数据
  • ServerSocketChannel:可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。

b. Buffer

缓冲区,主要有8种:

  • ByteBuffer
  • CharBuffer
  • FloatBuffer
  • DoubleBuffer
  • IntBuffer
  • ShortBuffer
  • LongBuffer
  • MappedByteBuffer

管道的读写是需要借助Buffer来实现的,一般buffer的读写流程:

  • 创建Buffer
    • ByteBuffer buf = ByteBuffer.allocate(48);
  • 写入数据到Buffer
    • 从Channel写到Buffer: channel.read(buf);
    • 直接塞入buffer: buf.put(12);
  • flip() 切换读写模式
    • 将写模式切换到读模式
  • 从Buffer读取数据
    • 从Buffer读数据到Channel:channel.write(buf);
    • 直接读取数据: buf.get()
  • 清空缓存区:clear() 或 compact()
    • clear 清空,但是数据并未清除,会覆盖
    • compact 将所有未读的数据拷贝到Buffer起始处

额外方法:

  • Buffer.rewind()
    • 将position设回0,所以你可以重读Buffer中的所有数据
    • limit保持不变,仍然表示能从Buffer中读取多少个元素(byte、char等)
  • Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用
  • Buffer.reset()方法,恢复到Buffer.mark()标记时的position

c. selector

是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件

  • 创建
    • Selector selector = Selector.open();
  • 注册通道
    • 设置通道为非阻塞 channel.configureBlocking(false);
    • 注册: SelectionKey key = channel.register(selector,Selectionkey.OP_READ);
  • 监听事件
    • selector.select();当注册的事件到达时,方法返回;否则,该方法会一直阻塞
    • 迭代:selector.selectedKeys().iterator()
    • 获取通道: SelectionKey#channel
    • 通过缓冲区读写数据

III. NIO与IO对比

1. Java NIO提供了与标准IO不同的IO工作方式:

  • Channels and Buffers(通道和缓冲区):标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。
  • Asynchronous IO(异步IO):Java NIO可以让你异步的使用IO,例如:当线程从通道读取数据到缓冲区时,线程还是可以进行其他事情。当数据被写入到缓冲区时,线程可以继续处理它。从缓冲区写入通道也类似。
  • Selectors(选择器):Java NIO引入了选择器的概念,选择器用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个的线程可以监听多个数据通道。

2. 使用场景

NIO

  • 优势在于一个线程管理多个通道;但是数据的处理将会变得复杂;
  • 如果需要管理同时打开的成千上万个连接,这些连接每次只是发送少量的数据,采用这种;

传统IO

  • 适用于一个线程管理一个通道的情况;因为其中的流数据的读取是阻塞的
  • 如果需要管理同时打开不太多的连接,这些连接会发送大量的数据;

3. 区别

  • IO是面向流的,NIO是面向缓冲区的
    • Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方;
    • NIO则能前后移动流中的数据,因为是面向缓冲区的
  • IO流是阻塞的,NIO流是不阻塞的
    • Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了
    • Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取
    • 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。
  • 选择器
    • Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道
    • 这些通道里已经有可以处理的输入,或者选择已准备写入的通道
    • 这种选择机制,使得一个单独的线程很容易来管理多个通道。

IV. 其他

参考

个人博客: Z+|blog

基于hexo + github pages搭建的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

声明

尽信书则不如,已上内容,纯属一家之言,因本人能力一般,见识有限,如发现bug或者有更好的建议,随时欢迎批评指正

扫描关注

QrCode

# IO, Java

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×