1. IO与NIO的区别:为什么需要NIO?
1.1 java IO中的socket连接
在最初的java版本中,对于接受socket连接的服务器,其基本的处理步骤是:
1、实例化一个ServerSocket对象;
2、将实例化后的Serversocket对象绑定到某个端口上;
3、使用accept( )方法监听通过此端口的socket连接,这是accept( )方法会一直阻塞直到有连接进入;
4、收到socket连接之后,accept( )方法返回一个Socket对象,与客户端建立连接进行通信;
5、等待客户端或者服务器断开连接,返回到第3步等待下一次的连接。
下图截自《Hadoop技术内幕:深入理解Hadoop Common和HDFS架构设计与实现原理》,表示最初的客户端与服务器端的连接。
在 java 最初的 IO 中,OutputStream和InputStream没有提供异步的的IO读写,并且服务器端的accept( ) 方法是阻塞的。这意味着在客户端中需要采用一个线程对应一个客户端连接的策略来保证服务器可以同时接受来自多个客户端的连接。这就会导致大量的系统开销和负担。这种阻塞的通信模式也正是NIO所要解决的主要问题!
1.2. NIO与IO的区别
NIO | IO |
---|---|
非阻塞 | 阻塞 |
面向缓冲(buffer) | 面向流(stream) |
有选择器 | 无选择器 |
2. NIO的三个主要组件
NIO实现的一个重要的功能就是非阻塞的通信,为此NIO引入了Selector(选择器)和Channel(通道)的概念,并且使用新的抽象结构:Buffer(缓冲区)来达到更高读写效率。
2.1 Buffer
Buffer是一个有固定容量的容器,其本质是一块被包装成了java对象的内存,并且提供一些方法操纵和访问这块内存。Buffer有读模式和写模式两种模式。Buffer类及其子类的类实现关系如下;
2.1.1 Buffer的四个主要概念
上图为jdk1.8 中java.nio.Buffer.java 中的部分源码,可以看到在Buffer源码的开始部分,定义了四个变量:
- capacity: buffer 的容量,一旦一个buffer被创建完成,它的capacity的值就不可被修改;
- limit: buffer区域中第一个不能被读取或者写入的位置,可以使用java.nio.Buffer中的public final Buffer limit(int newLimit)方法自定义limit的位置。在读模式下limit表示可以读取到多少的数据,在写模式下limit与capacity的值保持一致,当buffer由写模式切换到读模式的时候,limit值会由capacity的值切换为写模式下的position的值;
- position: buffer中下一个要被读取或者写入的位置。每当读取或者写入一个数据之后,position的值会自动向后移一位
- mark: 在buffer的某个位置设置的标记。
上述四个变量彼此之间必须遵守下面的大小关系:
mark <= position <= limit <= capacity
2.1.2 Buffer的基本使用方法
- Buffer分配
ByteBuffer buf = ByteBuffer.allocate(64); //创建一个可以容纳64个字节的缓冲区
CharBuffer buf = CharBuffer.allocate(64); //创建一个可以容纳64个char类型的缓冲区
IntBuffer buf = IntBuffer.allocate(64); //创建一个可以容纳64个Int类型的缓冲区
- 从Buffer读取 / 写入数据
从Buffer中读取或者写入数据有两种方式:
- 从Channel 读取 / 写入到Buffer
- 通过Buffer的 put()方法写到Buffer里 / get()方法从Buffer中读取
int bytesRead = inChannel.read(buf); //从Channel写入到Buffer
buf.put(127); //put()方法写到Buffer中
int bytesWritten = inChannel.write(buf); /