本篇文章介绍一下java中的Nio中的Buffer,注意不是Netty中的,后面会讲解Netty中的Buffer
什么是Buffer
Buffer本质就是一个可以读写的内存块,它提供了一些方法,可以轻松的往里面写数据和读数据。
Nio中Channel提供了从文件或者网络读取数据的渠道,但是读取和写入数据时,必须经由Buffer
Nio中Buffer有以下几种:
- IntBuffer
- FloatBuffer
- CharBuffer
- DoubleBuffer
- ShortBuffer
- LongBuffer
- ByteBuffer
可以看出八种基本数据类型除了boolean,都有一个对应的Buffer
下面以IntBuffer为例讲解一下Buffer中的方法,然后通过实例展示
public static IntBuffer allocate(int capacity)
创建一个IntBuffer对象。指定这个IntBuffer的容量
//可以放5个数字到这个buffer中 IntBuffer intBuffer = IntBuffer.allocate(5);
public abstract IntBuffer put(int i);
向IntBuffer中放一个整数//创建一个存放5个整数的buffer IntBuffer intBuffer = IntBuffer.allocate(5); //将整数1放到buffer中 intBuffer.put(1);
-
public final Buffer flip() 切换buffer的读写。内部源码实际的逻辑就是通过控制position的值来进行操作的
-
public final boolean hasRemaining() 判断buffer中是否有数据,一般在while循环中当作条件。
例子:
public class BufferTest {
public static void main(String[] args) throws Exception {
IntBuffer intBuffer = IntBuffer.allocate(5);
//intBuffer.put(1);
for (int i = 0; i < intBuffer.capacity(); i++) {
intBuffer.put(i);
}
// 如何从buffer读数据,通过这个方法将buffer切换为模式,然后就可以读数据,
// 如果想往buffer里再写数据,就在执行一次这个方法转换一下就行
intBuffer.flip();
// 如果intBuffer中还有数据
while (intBuffer.hasRemaining()) {
System.out.println(intBuffer.get());
}
}
}
执行结果为:
下面通过源码,来看一下
- buffer中的数据从在那? 在上面的实例中,我们创建了一个int的buffer,可以保存4个数据,那么这4个数据其实就是保存在这个红框的hb数组中的。(8个buffer都会有一个对应的hb)。
- buffer中的4个属性的意思
属性 描述 capacity buffer可以存放的最大数据,创建buffer是确定,然后就不可以在变了 limit 表示缓冲区的当前终点。就是设置以后,读写buffer时不能超过这个limit的位置 position 表示将要读取数据的位置或者写入数据的位置 mark 标记
position为0表示要将数据写入到的位置是下标为0的位置,下一次就会变为1以此类推。但是因为limit为5,则表示当position为4时就不能再添加了。这就是limit的作用。如果现在吧limit设置为1,那么添加完第一数据后,就不能再添加了,即便是我们开始设置的容量capacity为5,也不能再添加数据了,这就是limit的作用。
常用的api解释
Nio还支持通过多个Buffer(即Buffer数组)完成读写操作,也就是Scattering 和Gathering
什么是Scattering?
将数据写入buffer时,可以依次写入,比如一个buffer写满了,就往buffer数组的下一个buffer里写
什么是Gathering
从buffer读数据时,可以采用buffer数组,比如一个buffer读完之后,就从buffer数组的下一个buffer中继续读。
下面通过例子看一下:
public static void main(String[] args) throws Exception {
ServerSocketChannel serverSocketChannel =ServerSocketChannel.open();
InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);
serverSocketChannel.socket().bind(inetSocketAddress);
ByteBuffer[] byteBuffers = new ByteBuffer[2];
byteBuffers[0] = ByteBuffer.allocate(5);
byteBuffers[1] = ByteBuffer.allocate(3);
SocketChannel socketChannel = serverSocketChannel.accept();
int messageLength = 8; //假定从客户端接收8个字节
while (true) {
int byteRead = 0;
while (byteRead < messageLength) {
long l = socketChannel.read(byteBuffers);// 读取的字节数
byteRead += l; // 累计读取的字节数
System.out.println("byteRead=" + byteRead);
// 使用流打印,看看当前buffer的position和limit
Arrays.asList(byteBuffers).stream().map(buffer->"position:" + buffer.position() + "limit:" + buffer.limit()).forEach(System.out::println);
}
//对所有的buffer进行flip
Arrays.asList(byteBuffers).forEach(buffer->buffer.flip());
//将数据读出,显示到客户端
long byteWrite = 0;
while(byteWrite < messageLength) {
long l = socketChannel.write(byteBuffers);
byteWrite += l;
}
//将所有的buffer进行clean
Arrays.asList(byteBuffers).forEach(buffer->buffer.clear());
System.out.println("byteRead=" + byteRead + "byteWrites=" + byteWrite + "messageLength=" + messageLength);
}
}
运行启动程序后,然后通过cmd启动telenet,方式如下:
1.win + R,然后输入cmd
2.打开cmd后,输入telnet 127.0.0.1 7000 连接我们的socket
3.连接成功后,输入helloa六个字符
结果:然后可以发现helloa在填充完第一个数组后,继续想第二个数组中填写。然后从字节数组中读的时候,也是把两个数组都读出来了。