什么是NIO?
NIO 全程 java non-blocking IO,是指jdk1.4 提供的新api(NEW IO)
NIO与IO的区别
NIO 特点:非阻塞,面向缓冲区
IO 特点:阻塞式,面向流
阻塞与非阻塞
java io 是阻塞式的,当一个线程调用read 或者write方法后开始阻塞,直到读取到数据或者写入数据完成,该线程一直处于阻塞状态不能做其他事情。
java nio 通过选择器实现非阻塞式IO,通过一个专门的选择器线程来监视 读请求或者写请求是否已经具备完整条件。
非阻塞读:如某个线程发送一个读请求,则它只能获取到当前可用的数据,如果没有可用的数据直接返回,而不是阻塞在这里,直到数据变得可读取。在数据变得可读取之前该线程是可以继续处理其他任务的。
非阻塞写:如某个线程发送写请求,需要写一些数据到通道中,但不需要等到将所有数据都完全写入,该线程可以同时做其他任务。
线程通常将非阻塞的IO的空闲时间用来执行其他通道的IO任务,所以一个单独的线程可以管理多个输入和输出通道。
NIO重要属性
Channel
通道:channel 是在rt.jar java.util.nio 包下定义的,表示io源与目标打开的链接。
通道不能直接传输数据只能配合缓冲区使用。
通道类似于流 ,但是通道是双向的,即可以将通道指向的目标源数据读入到缓冲区,也可以将缓冲区数据写入到链接通道的目标源。通道好比是铁路,缓冲区为火车,火车载人,通过铁路来运输。
主要方法
将通道指向的目标源读到dst缓冲区中
将数据写入目标源:
java.nio.channels.SeekableByteChannel#read(ByteBuffer dst)
向通道指向的目标源写入数据
java.nio.channels.SeekableByteChannel#write(ByteBuffer dst)
Buffer
缓冲区是存储数据的数组
重要属性
1 capacity:缓冲区容量大小,一旦创建不可改变
2 limit:缓冲区可用数据大小,也就是缓冲区最后一个可操作数据的下标,该下标之后的缓冲区数据不可操作。小于等于capacity
3 position:当前操作的位置。小于等于limit
4 mark:上一次标记的索引位置
重要方法
- 1.向缓冲区写入数组
put(byte[] src, int offset, int length)
src:要写入的数据
offset:写入数据src的开始下标(从src的offset位置开始)
length:写入数据的长度
将src数组中下标位置从offset开始到长度为length的数据防在缓冲区中。
注意: offset指从数组的offset下标开始获取数据
- 2.将缓冲区数据读到数组中
get(byte[] dst, int offset, int length)
src:dst数组开始下标
length:读取数据的长度
将缓冲区中的数据读取到byte数组中,数组从索引为 offset位置开始存入length长度的数据
注意: offset指数组的offset下标开始
3.切换读模式
filp()
缓冲区从写模式切换到读模式。此时position = 0,limit = 写入数据的最大长度(最后一次写操作的position),mark=-1 标记位清空4.重读
rewind()
缓冲区的数据需要再次读取
position=0,limit不变,mark=-1
5.清除缓冲区
clear()
清除缓冲区,此时将所有属性恢复,position=0,limit=capacity,但是缓冲区数据没有删除。这些数据成为被遗忘的数据。(可以通过设置position于limit来)6.标记
mark()
标记当前位置 mark = position7.重置
reset()
position=mark
Selector
选择器 ,它是Java NIO核心组件中的一个,用于检查一个或多个NIO Channel(通道)的状态是否处于可读、可写。
使用Selector的好处在于: 使用更少的线程来就可以来处理通道了, 相比使用多个线程,避免了线程上下文切换带来的开销。
示例代码:
Selector selector = Selector.open(); channel.configureBlocking(false); SelectionKey key = channel.register(selector, Selectionkey.OP_READ);
channel.configureBlocking(false);
将通道设置为非阻塞式,FileChannel 是阻塞式的不能使用选择器,SocketChannel 网络套接字的通道可以设置为非阻塞式。
register方法的第二个参数是监听事件的类型。选择器可以监听以下四种类型的事件
Connect :链接就 SelectionKey.OP_CONNECT
Accept:接收就绪 SelectionKey.OP_ACCEPT
Read:读就绪 SelectionKey.OP_READ
Write:写就绪 SelectionKey.OP_WRITE
如果需要注册多个事件
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
可以通过以下方法来确定以上事件是否准备就绪,它们都会返回一个布尔类型:
链接就绪:selectionKey.isConnectable();
接收就绪:selectionKey.isAcceptable();
读就绪:selectionKey.isReadable();
写就绪:selectionKey.isWritable();
Set selectedKeys =selector.selectedKeys();
Iterator keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if(key.isAcceptable()) {
// a connection was accepted by a ServerSocketChannel.
} else if (key.isConnectable()) {
// a connection was established with a remote server.
} else if (key.isReadable()) {
// a channel is ready for reading
} else if (key.isWritable()) {
// a channel is ready for writing
}
keyIterator.remove();
}
直接缓冲区与非直接缓冲区
非直接缓冲区
客户端向服务端发送数据,先将数据发送到虚拟机中用户地址中空间的缓存中,然后用户地址空间将数据复制到系统内核地址空间的缓存中,内核地址空间将数据写入到系统的物理磁盘中。
直接缓冲区
直接缓冲区省去了用户地址空间缓存到内核地址空间缓存的复制过程,用物理内存映射文件来代替,直接将数据存入到物理内存中,然后在将数据存入到物理磁盘中。
可以通过Buffer.allocateDirect 方法来创建直接缓冲区。