从之前的常见的IO模型文章中,可以看出java nio是属于第三种,io多路复用的一种。
前提条件
NIO的实现,是基于底层的选择器的系统调用。NIO的选择器,需要底层操作系统提供支持。所以说一定要在操作系统层面适配才行
组成部分
通道(Channel)
在网络的连接当中,还有两种流,输入流和输出流,输入流和输出流都可以都是从通道开始
Selector选择器
什么是选择器?
它一个IO事件的查询器。通过选择器,一个线程可以查询多个通道的IO事件的就绪状态。
实现IO多路复用,从具体的开发层面来说,首先把通道注册到选择器中,然后通过选择器内部的机制,可以查询(select)这些注册的通道是否有已经就绪的IO事件(例如可读、可写、网络连接完成等)。
一个选择器只需要一个线程进行监控,换句话说,我们可以很简单地使用一个线程,通过选择器去管理多个通道。这是非常高效的,这种高效来自于Java的选择器组件Selector,以及其背后的操作系统底层的IO多路复用的支持。
缓冲区(Buffer)
NIO的Buffer(缓冲区)本质上是一个内存块,既可以写入数据,也可以从中读取数据。NIO的Buffer类,是一个抽象类,位于java.nio包中,其内部是一个内存块(数组)。
Buffer类是一个非线程安全类。
分类
Buffer类是一个抽象类,对应于Java的主要数据类型,在NIO中有8种缓冲区类,分别如下:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer、MappedByteBuffer。
前7种Buffer类型,覆盖了能在IO中传输的所有的Java基本数据类型。第8种类型MappedByteBuffer是专门用于内存映射的一种ByteBuffer类型。
实际上,使用最多的还是ByteBuffer二进制字节缓冲区类型
属性
public abstract class Buffer {
// Invariants: mark <= position <= limit <= capacity
//可以将当前的position临时存入mark中;需要的时候,可以再从mark标记恢复到position位置。
private int mark = -1;
//读写位置
private int position = 0;
//读写的限制
private int limit;
//容量
private int capacity;
}
capacity属性
Buffer类的capacity属性,表示内部容量的大小。一旦写入的对象数量超过了capacity容量,缓冲区就满了,不能再写入了。
Buffer类的capacity属性一旦初始化,就不能再改变。原因是什么呢?Buffer类的对象在初始化时,会按照capacity分配内部的内存。在内存分配好之后,它的大小当然就不能改变了。
再强调一下,capacity容量不是指内存块byte[]数组的字节的数量。capacity容量指的是写入的数据对象的数量。
前面讲到,Buffer类是一个抽象类,Java不能直接用来新建对象。使用的时候,必须使用Buffer的某个子类,例如使用DoubleBuffer,则写入的数据是double类型,如果其capacity是100,那么我们最多可以写入100个double数据。
position属性
表示当前的位置。position属性与缓冲区的读写模式有关。在不同的模式下,position属性的值是不同的。当缓冲区进行读写的模式改变时,position会进行调整。
在写入模式下,position的值变化规则如下:
- 在刚进入到写模式时,position值为0,表示当前的写入位置为从头开始。
- 每当一个数据写到缓冲区之后,position会向后移动到下一个可写的位置。
- 初始的position值为0,最大可写值position为limit-1。当position值达到limit时,缓冲区就已经无空间可写了。
读模式下,position的值变化规则如下: - 当缓冲区刚开始进入到读模式时,position会被重置为0。
- 当从缓冲区读取时,也是从position位置开始读。读取数据后,position向前移动到下一个可读的位置。
- position最大的值为最大可读上限limit,当position达到limit时,表明缓冲区已经无数据可读。
limit属性
示读写的最大上限。limit属性,也与缓冲区的读写模式有关。在不同的模式下,limit的值的含义是不同的。
- 在写模式下,limit属性值的含义为可以写入的数据最大上限。在刚进入到写模式时,limit的值会被设置成缓冲区的capacity容量值,表示可以一直将缓冲区的容量写满。
- 在读模式下,limit的值含义为最多能从缓冲区中读取到多少数据。
一般来说,是先写入再读取。当缓冲区写入完成后,就可以开始从Buffer读取数据,可以使用flip翻转方法,这时,limit的值也会进行非常大的调整。
模式转换
常用方法
flip
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
从上面代码来看,非常简单的逻辑,就是让position为0,limit为position,mark重新等于-1,也就是删除了刚才临时存的位置的值
allocate
public static IntBuffer allocate(int capacity) {
if (capacity < 0)
throw new IllegalArgumentException();
return new HeapIntBuffer(capacity, capacity);
}
这个方法的作用就是分配内存
get方法
public abstract int get();
需要注意的是,在position和limit相等之后,是不能再获取的,这样会产生BufferUnderflowException异常
这里强调一下,在读完之后,是否可以立即进行写入模式呢?不能。现在还处于读取模式,我们必须调用Buffer.clear()或Buffer.compact(),即清空或者压缩缓冲区,才能变成写入模式,让其重新可写
rewind方法
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
从上面代码可以看出这个方法的简单逻辑,即position为0,临时标记重新为初始值,limit不变
mark方法
public final Buffer mark() {
mark = position;
return this;
}
Buffer.mark()方法的作用是将当前position的值保存起来,放在mark属性中,让mark属性记住这个临时位置;之后,可以调用Buffer.reset()方法将mark的值恢复到position中。
reset方法
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
clear方法
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
在读取模式下,调用clear()方法将缓冲区切换为写入模式。此方法会将position清零,limit设置为capacity最大容量值,可以一直写入,直到缓冲区写满
buffer使用步骤
- 使用创建子类实例对象的allocate()方法,创建一个Buffer类的实例对象。
- 调用put方法,将数据写入到缓冲区中。
- 写入完成后,在开始读取数据前,调用Buffer.flip()方法,将缓冲区转换为读模式。
- 调用get方法,从缓冲区中读取数据。
- 读取完成后,调用Buffer.clear() 或Buffer.compact()方法,将缓冲区转换为写入模式。
代码测试
package com.example.demo;
import java.nio.IntBuffer;
public class people {
public static void main(String[] args) {
IntBuffer intBuffer = IntBuffer.allocate(20);
System.out.println("position:"+intBuffer.position());
System.out.println("limit:"+intBuffer.limit());
System.out.println("capacity:"+intBuffer.capacity());
System.out.println("----------------------------------");
for (int i = 0; i < 5; i++) {
intBuffer.put(i);
}
System.out.println("position:"+intBuffer.position());
System.out.println("limit:"+intBuffer.limit());
System.out.println("capacity:"+intBuffer.capacity());
System.out.println("----------------------------------");
intBuffer.flip();
System.out.println("position:"+intBuffer.position());
System.out.println("limit:"+intBuffer.limit());
System.out.println("capacity:"+intBuffer.capacity());
}
}
执行结果是:
参考:
- 《Netty、Redis、Zookeeper高并发实战》