NIO介绍
什么是NIO呢?区别于BIO有什么优点呢?
nio写作new io 或 none blocking io,它是面向缓冲区,结合选择器(多路复用器)的非阻塞io。区别于传统io,更加高效、适用高并发读写环境。
结构组成
那 nio的组件有哪些呢?nio的组件包含buffer、channel、selector.
buffer缓冲区
本质上是一块内存,用来存储数据,缓冲区是数组类型,用于存储不同数据类型的数据。根据使用的场景不同,主要包含有一下几种
创建的方式及类别:直接缓冲区和非直接缓冲区
- 直接缓冲区:allocateDirect()创建的是直接缓冲区
- 非直接缓冲区:allocate()创建的是非直接缓冲区
ByteBuffer btf=ByteBuffer.allocate(1024);
ByteBuffer btf1 = ByteBuffer.allocateDirect(1024);
那直接缓冲区和非直接缓冲区有什么区别呢?非直接缓冲区是建立在jvm内存中,实际的读取需要在操作系统和jvm之间进行数据拷贝;直接缓冲区是建立在物理内存中,避免中间拷贝的多余环节。
*优缺点对比:*直接缓冲区避免了拷贝环节,效率更高;缺点是占用资源多,磁盘的写入操作不受控制
*适用场景:*数据本身存活时间长,或者数据量较大的操作建议使用直接缓冲区
那缓冲区怎么使用呢?
让我们先熟识下他的几个核心属性:position、limit、capacity、mark、clear
- position:表示缓冲区中正在操作数据的位置
- limit(界限):表示缓冲区中可操作数据的大小(limit后的数据不能进行操作)
- capacity:这个是表示缓冲区的最大容量
- mark:用来标记当前position,可以使用reset()恢复到标记的mark位置
- clear:清空状态(不是清空数据)
来看看下面这个小栗子:
public void test1() {
String str="abcde";
ByteBuffer btf=ByteBuffer.allocate(1024);//创建缓冲区
System.out.println("---------------allocate()----------------");
System.out.println(btf.position());//位置
System.out.println(btf.limit());//界限
System.out.println(btf.capacity());//容量
btf.put(str.getBytes());//存放数据
System.out.println("---------------put()----------------");
System.out.println(btf.position());//位置
System.out.println(btf.limit());//界限
System.out.println(btf.capacity());//容量
btf.flip();//切换为读模式
System.out.println("---------------flip()----------------");
System.out.println(btf.position());//位置
System.out.println(btf.limit());//界限
System.out.println(btf.capacity());//容量
byte[] bts=new byte[btf.limit()];
btf.get(bts, 0, bts.length);//读取所有字节数
System.out.println(new String(bts));
System.out.println("---------------get()----------------");
System.out.println(btf.position());//位置
System.out.println(btf.limit());//界限
System.out.println(btf.capacity());//容量
btf.clear();//清除所有模式
System.out.println("---------------clear()----------------");
System.out.println(btf.position());//位置
System.out.println(btf.limit());//界限
System.out.println(btf.capacity());//容量
}
运行后的结果
相信到这里大家应该大致了解什么是缓冲区了
channel信息通道
通道,简而言之就是用于连接点与点,架起信息传输的桥梁。通道根据使用情况的不同,主要有以下几类
- FileChannel:文件通道,用于文件的读和写
- DatagramChannel:用于 UDP 连接的接收和发送
- SocketChannel:把它理解为 TCP 连接通道,简单理解就是 TCP 客户端
- ServerSocketChannel:TCP 对应的服务端,用于监听某个端口进来的请求
下面我们来看看通道的使用方式:
在jdk1.7中NIO.2针对各个通道提供了静态方法open()
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(host, port));
ServerSocketChannel ssc = ServerSocketChannel.open();
DatagramChannel dc = DatagramChannel.open( );
FileInputStream fis = new FileInputStream("xx.jpg");
fis.getChannel();
//使用通道完成文件的复制(非直接缓冲区)
@Test
public void test1() {
FileInputStream fis = null;
FileOutputStream fos = null;
//获取通道
FileChannel fisChannel = null;
FileChannel fosChannel = null;
try {
fis = new FileInputStream("1.jpg");
fos = new FileOutputStream("2.jpg");
fisChannel = fis.getChannel();
fosChannel = fos.getChannel();
ByteBuffer btf = ByteBuffer.allocate(1024);
//将通道中数据存到缓冲区中
while (fisChannel.read(btf) != -1) {
btf.flip();//切换为读模式
fosChannel.write(btf);
btf.clear();//清空缓冲区
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fisChannel != null) {
try {
fisChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fosChannel != null) {
try {
fosChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
selector选择器
selector选择器也可称作为多路复用(选择)器。
传统的架构是一个请求对应一个链接,一个链接对应一个线程处理,部分线程的读写操作是无效的,在线程累计的过程中会造成资源的浪费,不适合高并发环境
多路选择器:服务不会对每个请求都创建线程,请求通过channel通道注册到selector连接器上,当轮询到请求有读写操作的时候才会创建线程,实现请求资源的统一管理,避免资源浪费
选择器的使用:
- 首先,需要开一一个选择器
Selector selector = Selector.open();- 将通道注册到选择器上
// 将通道设置为非阻塞模式,因为默认都是阻塞模式的
channel.configureBlocking(false);
// 注册
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
SelectionKey:针对不同使用场景需要选择不同的事件类型,SelectionKey.OP_READ(可读)、SelectionKey.OP_WRITE(可写)、SelectionKey.OP_ACCEPT(接受tcp请求)、SelectionKey.OP_CONNECT(建立tcp链接)
举个栗子:
public void server() throws IOException {
//1、获取通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2、绑定
serverSocketChannel.bind(new InetSocketAddress(9898));
//3.切换为非阻塞模式
serverSocketChannel.configureBlocking(false);
//4、创建选择器
Selector selector = Selector.open();
//5、注册通道到选择器中
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//5、查询准备就绪的通道
while (selector.select() > 0) {
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey next = iterator.next();
//6、判断是否是接受事件类型
if (next.isAcceptable()) {
SocketChannel socketChannel = serverSocketChannel.accept();
//7、设置为非阻塞
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (next.isReadable()) {
SocketChannel sChannel = (SocketChannel) next.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
while (sChannel.read(byteBuffer) > 0) {
byteBuffer.flip();
System.out.print(new String(byteBuffer.array(), 0, byteBuffer.limit()));
byteBuffer.clear();
}
}
//移除类型key
iterator.remove();
}
}
}
public void client() throws IOException {
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
FileInputStream fis = new FileInputStream("1.jpg");
fis.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//取消阻塞模式
socketChannel.configureBlocking(false);
Scanner sc = new Scanner(System.in);
while (sc.hasNext()) {
String str = sc.next();
byteBuffer.put(((new Date()).toString() + "\n" + str + "\n").getBytes());
byteBuffer.flip();
socketChannel.write(byteBuffer);
byteBuffer.clear();
}
socketChannel.close();
}