Java NIO浅读

date
Jun 30, 2020
slug
Java_NIO浅读
status
Published
tags
Java
summary
type
Post
花了半天时间啃完一篇Java NIO教程,整理一下内容

BIO vs NIO

BIO:

单向读取/写入,InputStream,OutputStream,当拿到InputStream/OutputStream对象时,系统已读完内容到内存。

NIO:

双向读取读取/写入,使用Buffer,Channel。Channel可以理解为连接,充Channel读取内容到Buffer后,如需要从Buffer写内容到Channel,需要将buffer翻转,调用buffer.flip()方法。读取需要字节读取,buffer可以设置读取的字节容量大小。
notion image

Channel,Buffer之间的数据传输方式:

  • Buffer从一个或多个Channel中读取内容
notion image
  • Buffer往一个或多个Channel中写入内容
notion image
  • Channel到Channel的数据传输

IO模型

IO分两步,等待可读/写,真正读/写,常用的五种IO模型:
notion image

IO复用、异步IO区别

非阻塞IO(NIO)有IO复用、信号驱动IO(AIO)两种实现方式,它们真正读写的过程是阻塞的,而异步IO整个IO过程都不阻塞的。NIO关心的是“可以读写了”,AIO关心的是“读写完了”。

NIO Server

NIO Server用的是IO多路复用模型,所有的Channel连接时向Selector注册感兴趣的事件,selector另起一个线程循环检查可读写的Channel,当读写就绪时调用对应的Handler处理,由于读写是memory copy,性能很高,NIO Server只需少量的线程便可以处理大量的连接。

NIO Server需要自己处理TCP拆包粘包问题

由于从Channel中读取信息是按字节读取,不能保证每次读取都能包含一段完整数据报,对于不完整的数据报内容需要先缓存起来,等待下一次读取的时候组成数据报。
notion image
一个MessageReader对应一个Channel,MessageReader负责吧Data Block转换成Message,为了缓存不完整的Message,如果一个MessageReader一个缓存空间,假如最大的Message有1MB,那么1,000,000个连接就需要1,000,0000x1MB=1TB内存,那如果最大的消息有16MB呢?这显然是不合理的。使用可调整大小缓存空间可以解决这个问题:
  • Resize by Copy
    • 先分配一块小的缓存空间,当消息到达后发现空间不够用就再开辟一块新的大的缓存空间,把原来的缓存空间内容复制过来,以此类推。这样的好处是缓存的内容在内存地址上是连续的,消息的解析更为方便,坏处是可能会产生大量的Copy操作。
  • Resize By Append
    • 先分配一块小的缓存空间,不够用时在加分配一块缓存空间,两个缓存空间内存地址上不连续,类似链表。这样的好处是可以避免大量的Copy操作,坏处是后面的消息解释处理比较复杂。
  • TLV
    • TLV即是Type, Length, Value,消息TLV值放在消息头部,这样当消息头部到达时MessageReader就知道需要分配多少缓存空间大小了,这样效率更高,内存管理更优秀。由于在消息完全到达前就已经分配好空间大小,如果一个连接的消息体太大可能会消耗服务器内存导致无响应。

什么时候用IO,什么时候用NIO?

面对少量连接的时候使用IO更方便而不会造成太大的性能损失,面对大量连接使用NIO,这样同样的服务器硬件能应对更多的连接。

© Ryan Tang 2021 - 2025