网站公告

本次更新:没啥更新的

“钱塘江上潮信起,今日方知我是我”

Skip to content

Buffer ​

字数: 0 字 时长: 0 分钟

Buffer 是 Java NIO 中用于存储数据的缓冲区。

在传统 IO 中,数据通常直接通过 Stream 进行读写;而在 NIO 中,数据一般先进入 Buffer,再通过 Channel 进行传输。

可以简单理解为:

text
传统 IO

数据
 ↓
Stream
 ↓
程序

NIO:

text
数据
 ↓
Channel
 ↓
Buffer
 ↓
程序

因此,NIO 中经常会看到这样的组合:

text
Channel + Buffer

其中:

text
Channel
    ↓
负责传输数据

Buffer
    ↓
负责存储数据

1. Buffer 基本结构 ​

Buffer 是一个抽象类,常见的实现类有:

text
Buffer
├── ByteBuffer
├── CharBuffer
├── ShortBuffer
├── IntBuffer
├── LongBuffer
├── FloatBuffer
└── DoubleBuffer

实际开发中最常用的是:

java
ByteBuffer

因为文件、网络等底层 IO 最终处理的通常都是字节数据。

创建一个 ByteBuffer:

java
ByteBuffer buffer =
        ByteBuffer.allocate(1024);

表示创建一个容量为 1024 字节的缓冲区。


2. Buffer 的三个核心属性 ​

学习 Buffer,最重要的就是理解三个属性:

text
capacity
position
limit

它们决定了缓冲区当前可以读写的数据范围。

2.1 capacity ​

capacity 表示缓冲区的容量。

例如:

java
ByteBuffer buffer =
        ByteBuffer.allocate(1024);

那么:

text
capacity = 1024

Buffer 创建以后,capacity 不会发生变化。


2.2 position ​

position 表示当前正在操作的位置。

刚创建 Buffer 时:

text
position = 0

每写入一个数据:

text
position + 1

例如:

java
ByteBuffer buffer =
        ByteBuffer.allocate(10);

buffer.put((byte) 1);
buffer.put((byte) 2);
buffer.put((byte) 3);

此时:

text
capacity = 10
position = 3
limit    = 10

可以理解为:

text
0   1   2   3   4   5   6   7   8   9
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ 1 │ 2 │ 3 │   │   │   │   │   │   │   │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
            ↑
         position

2.3 limit ​

limit 表示当前 Buffer 中允许操作的边界。

写模式下,刚创建 Buffer:

text
position = 0
limit    = capacity

例如:

text
capacity = 10
position = 0
limit    = 10

写入 3 个字节:

text
capacity = 10
position = 3
limit    = 10

当准备读取数据时,需要调用:

java
buffer.flip();

调用以后:

text
limit = 原来的 position
position = 0

也就是:

text
flip() 前

position = 3
limit    = 10


flip() 后

position = 0
limit    = 3

此时读取范围就是:

text
0 ~ 2

也就是刚才真正写入的 3 个字节。


3. Buffer 的读写过程 ​

Buffer 最典型的使用过程是:

text
写入 Buffer
    ↓
flip()
    ↓
读取 Buffer
    ↓
clear()
    ↓
继续写入

代码:

java
ByteBuffer buffer =
        ByteBuffer.allocate(1024);

// 写入数据
buffer.put(
        "kerwim".getBytes()
);

// 切换为读模式
buffer.flip();

// 读取数据
while (
        buffer.hasRemaining()
) {

    byte b =
            buffer.get();

    System.out.print(
            (char) b
    );
}

// 清空状态,准备下一次写入
buffer.clear();

整个状态变化可以简单理解为:

text
创建

position = 0
limit    = capacity
        ↓
      put()
        ↓
position 不断增加
        ↓
      flip()
        ↓
limit = position
position = 0
        ↓
      get()
        ↓
position 不断增加
        ↓
      clear()
        ↓
position = 0
limit = capacity

4. flip() ​

flip() 是 Buffer 中非常重要的方法。

作用:

将 Buffer 从写模式切换为读模式。

例如:

java
ByteBuffer buffer =
        ByteBuffer.allocate(10);

buffer.put((byte) 1);
buffer.put((byte) 2);
buffer.put((byte) 3);

写完以后:

text
position = 3
limit    = 10
capacity = 10

调用:

java
buffer.flip();

以后:

text
position = 0
limit    = 3
capacity = 10

这样读取时就只会读取真正写入的:

text
3 个字节

5. clear() ​

读取完成以后,如果还想继续使用 Buffer,可以调用:

java
buffer.clear();

clear() 会把:

text
position = 0
limit    = capacity

也就是重新切换为写模式。

需要注意:

clear() 并不会真正把 Buffer 中的数据删除,只是重置 position 和 limit,原来的数据之后会被新的数据覆盖。

例如:

text
clear() 前

position = 3
limit    = 3
capacity = 10


clear() 后

position = 0
limit    = 10
capacity = 10

6. rewind() ​

rewind() 的作用是:

重新读取 Buffer 中的数据。

调用:

java
buffer.rewind();

以后:

text
position = 0
limit 不变

例如:

java
buffer.flip();

// 第一次读取
while (
        buffer.hasRemaining()
) {

    System.out.print(
            (char) buffer.get()
    );
}

// 回到开头
buffer.rewind();

// 再读取一次
while (
        buffer.hasRemaining()
) {

    System.out.print(
            (char) buffer.get()
    );
}

可以理解为:

text
flip()
    ↓
准备第一次读

rewind()
    ↓
回到开头重新读

7. compact() ​

compact() 主要用于:

Buffer 中还有一部分数据没有读取完,但又想继续写入新数据。

它会把:

text
未读取的数据

移动到 Buffer 前面,然后把 position 移动到这些数据之后,剩余空间继续用于写数据。

可以简单理解为:

text
Buffer

已读数据 | 未读数据 | 空闲空间

compact()
        ↓

未读数据 | 空闲空间
          ↑
       position

与 clear() 的区别:

text
clear()
    ↓
直接认为整个 Buffer 都可以重新写

compact()
    ↓
保留还没有读取的数据
然后继续写

8. 常用方法 ​

Buffer 常用方法:

方法作用
capacity()获取容量
position()获取当前位置
limit()获取操作边界
flip()从写模式切换为读模式
clear()清空状态,准备重新写入
rewind()position 回到 0,重新读取
remaining()返回剩余可以操作的数据数量
hasRemaining()判断是否还有剩余数据
mark()标记当前 position
reset()position 回到 mark 位置

其中:

java
remaining()

本质上就是:

text
limit - position

例如:

text
position = 3
limit    = 10

remaining = 7

9. mark 和 reset ​

mark() 可以记录当前 position:

java
buffer.mark();

之后 position 即使继续变化,也可以调用:

java
buffer.reset();

回到之前标记的位置。

例如:

java
buffer.get();
buffer.get();

// 标记当前位置
buffer.mark();

buffer.get();
buffer.get();

// 回到 mark 的位置
buffer.reset();

可以理解为:

text
position
    ↓
 mark()
    ↓
继续读取
    ↓
 reset()
    ↓
回到之前的位置

10. ByteBuffer ​

NIO 中最常用的 Buffer 就是:

java
ByteBuffer

创建方式主要有两种。

10.1 allocate() ​

java
ByteBuffer buffer =
        ByteBuffer.allocate(1024);

创建一个容量为 1024 字节的 Buffer。

10.2 wrap() ​

也可以直接包装一个现有的字节数组:

java
byte[] bytes =
        "kerwim".getBytes();

ByteBuffer buffer =
        ByteBuffer.wrap(bytes);

这时 Buffer 中直接包含这个字节数组的数据。


11. 堆内 Buffer 和直接 Buffer ​

ByteBuffer 主要有两种分配方式:

java
ByteBuffer.allocate()

和:

java
ByteBuffer.allocateDirect()

11.1 allocate ​

java
ByteBuffer buffer =
        ByteBuffer.allocate(1024);

创建的是普通 Buffer,数据位于 JVM 堆内存中。

text
JVM Heap
    ↓
ByteBuffer

11.2 allocateDirect ​

java
ByteBuffer buffer =
        ByteBuffer.allocateDirect(1024);

创建的是直接缓冲区(Direct Buffer)。

数据位于 JVM 堆外的直接内存区域。

text
JVM Heap
    ↓
DirectByteBuffer
    ↓
直接内存

直接缓冲区可以减少某些 IO 场景下 JVM 堆和本地内存之间的数据复制,但它的创建和释放成本也相对更高。

因此不能简单理解为:

text
DirectByteBuffer 一定更快

具体还是取决于使用场景。


12. Buffer 与 Channel ​

Buffer 本身只是存储数据,真正的数据传输由 Channel 完成。

例如从 FileChannel 中读取数据:

java
FileChannel channel =
        new FileInputStream(
                "test.txt"
        ).getChannel();

ByteBuffer buffer =
        ByteBuffer.allocate(1024);

channel.read(buffer);

数据流向:

text
文件
 ↓
FileChannel
 ↓
ByteBuffer

写入文件:

java
buffer.flip();

channel.write(buffer);

数据流向:

text
ByteBuffer
 ↓
FileChannel
 ↓
文件

所以学习 NIO 时,可以记住:

text
Channel
    ↓
负责传输

Buffer
    ↓
负责存储

13. 小结 ​

学习 Buffer 最重要的是掌握:

text
capacity
position
limit

三个核心属性。

以及:

text
flip()
clear()
rewind()
compact()

几个核心方法。

整个 Buffer 最核心的状态变化:

text
          写数据
             ↓
      position 增加
             ↓
           flip()
             ↓
       position = 0
       limit = 原 position
             ↓
          读数据
             ↓
      position 增加
             ↓
           clear()
             ↓
       position = 0
       limit = capacity
             ↓
         继续写数据

一句话理解:

Buffer 本质上就是一块带有 position、limit、capacity 状态管理的数据存储区域,NIO 通过 Channel 在 Buffer 中读写数据。

Noctiflorous Garden · MIT License