NIO:同步非阻塞
NIO模型是基于IO复用来实现的,在Java 1.4中被纳入JDK中。在NIO中有几个核心的对象:缓冲区(Buffer)、通道(Channel)、选择器(Selector)
Buffer
在NIO中,所有的缓冲区类型都继承于抽象类Buffer,最常用的就是ByteBuffer。对于Java中的基本数据类型,基本都有一个具体的Buffer类型与之相对应。更直接的说,我觉得Buffer就是一个数组
Buffer中有三个重要的参数:位置(Position)、容量(Capacity)、上限(Limit)
参数 | 写模式 | 读模式 |
位置(Position) | 当前缓冲区的位置,将从Position的下一个位置写数据 | 当前缓冲区读取的位置,将从此位置之后,读取数据 |
容量(Capacity) | 缓冲区的总容量上限 | 缓冲区的总容量上限 |
上限(Limit) | 缓冲区的实际上限,它总是小于等于容量。通常情况下,和容量相等 | 代表可读取的总容量,和上次写入的数据量相等 |
Buffer的常见方法如下:
flip():写模式转换为读模式
rewind():将position重置为0,一般用于重复读
clear()
compact():将未读取的数据拷贝到buffer的头部
mark():reset():mark可以标记一个位置,reset可以重置到该位置
Buffer基础操作代码演示如下:
public class Test0226 {
public static void main(String[] args) {
//分配8个长度的int数组
IntBuffer buffer=IntBuffer.allocate(8);
//Capacity 数组的容量 长度
for(int i=0;i<buffer.capacity();++i){
int j=(i+1);
//将此数值写入到缓冲区的当前位置,当前位置递增
buffer.put(j);
}
//重设此缓冲区,将限制设置为当前位置,然后将当前位置设置为0
buffer.flip();
//查看在当前位置和限制位置之间是否还有元素
while (buffer.hasRemaining()){
//读取此缓冲区当前位置的整数,然后当前位置递增
int j=buffer.get();
System.out.println(j+" ");
}
}
}
缓冲区存取数据的流程:
存数据时Position++,当停止数据读取的时候,调用flip(),此时limit=position,position=0
读数据时Position++,一直读取到limit,clear()清空Buffer,准备再次被写入(position变成0,limit变成capacity)
Channel
通道是一个对象,通过它可以读取和写入数据,所有的数据都是通过Buffer对象来处理,不是直接将字节写入通道中,而是将数据写入包含一个或者多个字节的缓冲区中;同样的,不会直接从通道中读取字节,而是将数据从通道中读入缓冲区,再从缓冲区获取字节。
使用NIO读取数据代码演示如下:
public class Test0226{
public static void main(String[] args) throws IOException {
FileInputStream inputStream=new FileInputStream("c:\\test.txt");
//获取通道
FileChannel channel=inputStream.getChannel();
//创建缓冲区
ByteBuffer buffer=ByteBuffer.allocate(1024);
//读取数据到缓冲区
channel.read(buffer);
buffer.flip();
while (buffer.remaining()>0){
byte bytes=buffer.get();
System.out.println((char)bytes);
}
inputStream.close();
}
}
Selector
1、选择器Selector
Selector选择器类管理着一个被注册的通道集合的信息和他们的就绪状态。通道和选择器是一起被注册的,并且使用选择器来更新通道的就绪状态。
2、可选择通道SelectorChannel
SelectableChannel这个抽象类提供了实现通道的可选择性所需要的公共方法。它是所有支持就绪检查的通道类的父类。SelectableChannel可以被注册到Selector对象上,同时可以指定对那个选择器而言,哪种操作是感兴趣的。一个通道可以被注册到多个选择器上,但对每个选择器而言只能被注册一次。
3、选择键SelectionKey
选择键封装了特定的通道与特定的选择器的注册关系。选择键对象被SelectableChannel.register()返回并提供一个表示这种注册关系的标记。选择键包含了两个比特集(以整数的形式进行编码),指示了该注册关系所关心的通道操作,以及通道已经准备好的操作。
创建Selector
Selector对象是通过调用静态工厂方法open()来实例化的,如下:
Selector Selector = Selector.open();
将Channel注册到Selector
要实现Selector管理Channel,需要将channel注册到相应的Selector上,如下:
channel.configureBlocking(false);
SelectionKey key= channel.register(selector,SelectionKey,OP_READ);
NIO服务端通信序列图:
步骤:(server)
1、先创建ServerSocketChannel实例、并且绑定端口
2、创建selector实例
3、将ServerSocketChannel实例注册到选择器上,并监听accept事件
4、有事件发生(accept),就获取客户端的socketChannel的连接,将连接实例注册到选择器上
5、(IO操作)通过selector监听读事件,就进行相应的读事件,写事件类似,也需要监听--》Buffer
6、关闭ServerSocketChannel实例
NIO客户端通信序列图:
步骤:(Client)
1、创建SocketChannel实例
2、创建selector选择器
3、连接服务器
4、将SocketChannel注册到selector选择器
5、直接发送数据,通过selector选择器监听读事件
6、关闭SocketChannel实例