Java NIO入门到放弃系列之Buffer

Buffer是什么

和它的名字一样,Buffer是一个缓冲区,本质上是一块可以写入数据。可以读取数据的内存。在NIO中,Buffer用于和NIO中的各种channel交互。数据可以从channel中读入Buffer,然后从Buffer中写入另一个channel。

Buffer中几个重要的概念

  • capacity:容量,指定之后不可变动

  • limit:限制,第一个不应该读取或写入数据的索引,初始时和capacity在一个位置。

  • position:位置,下一个可读取或写入的位置,初始为0,不能为负,且不能大于limit

举几个例子来说明Buffer运作方式

  1. 初始时,即刚分配

    1
    ByteBuffer byteBuffer = ByteBuffer.allocate(48);

    其状态图如下:

Alt

  1. 当写入几个值时

    1
    2
    3
    4
    byteBuffer.put((byte)1);
    byteBuffer.put((byte)2);
    byteBuffer.put((byte)3);
    byteBuffer.put((byte)4);

    状态如下

    Alt

  2. 当写入了数据进入缓冲区时,就可以读取数据,在上面说过,数据写入和读取的下一个位置是position,可是现在position在索引为4(数组下标从0开始,这里缓冲区是一个byte数组)如何读取呢。这里就要介绍一下Buffer缓冲区有两种状态,即读和写,在读的状态下,不能做写入操作,同样在写状态下也不能做读操作,否则缓冲区会数据会被破坏。在buffer中提供了flip()来切换读写状态。当调用flip()函数,缓冲区状态由写入变成读取。

    Alt

    可以看到,limit移动到position位置,而position移动到索引0位置。可以从Buffer源码中看到这个操作。

    1
    2
    3
    4
    5
    6
    public final Buffer flip() {
    limit = position;
    position = 0;
    mark = -1;
    return this;
    }
  3. 此时进入读状态开始读取数据,如下图

    Alt

  4. 当读取到一半时,这个时候想写数据怎么办,我们知道flip()函数可以切换读写状态,但是position会从0开始,也就是说我们没有读完的数据会被后来写入的数据覆盖。所以这个时候不能用flip()函数来切换,ByteBuffer提供了另外一个函数compact(),该函数会将未读完的数据复制到索引0位置开始,将position和limit之间的数据依次复制,然后limit移动到capacity位置。如下图

    Alt

以ByteBuffer为例写一个Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TestBuffer {
public static void main(String[] args) {
ByteBuffer byteBuffer = ByteBuffer.allocate(48);
byteBuffer.put((byte)1);
byteBuffer.put((byte)2);
byteBuffer.put((byte)3);
byteBuffer.put((byte)4);
byteBuffer.flip(); //写入四个值后切换为读取状态
System.out.println("第一次读取:" + byteBuffer.get());
byteBuffer.compact(); //读了部分后调用compact整理缓冲区的数据
byteBuffer.put((byte)5);
//byteBuffer.put((byte)6);
byteBuffer.flip();
for (int i=0; i< 4; i++) {
System.out.println("第二次读取:" + byteBuffer.get());
}
}
}

运行结果:

1
2
3
4
5
第一次读取:1
第二次读取:2
第二次读取:3
第二次读取:4
第二次读取:5