Buffer
字数: 0 字 时长: 0 分钟
Buffer 是 Java NIO 中用于存储数据的缓冲区。
在传统 IO 中,数据通常直接通过 Stream 进行读写;而在 NIO 中,数据一般先进入 Buffer,再通过 Channel 进行传输。
可以简单理解为:
传统 IO
数据
↓
Stream
↓
程序NIO:
数据
↓
Channel
↓
Buffer
↓
程序因此,NIO 中经常会看到这样的组合:
Channel + Buffer其中:
Channel
↓
负责传输数据
Buffer
↓
负责存储数据1. Buffer 基本结构
Buffer 是一个抽象类,常见的实现类有:
Buffer
├── ByteBuffer
├── CharBuffer
├── ShortBuffer
├── IntBuffer
├── LongBuffer
├── FloatBuffer
└── DoubleBuffer实际开发中最常用的是:
ByteBuffer因为文件、网络等底层 IO 最终处理的通常都是字节数据。
创建一个 ByteBuffer:
ByteBuffer buffer =
ByteBuffer.allocate(1024);表示创建一个容量为 1024 字节的缓冲区。
2. Buffer 的三个核心属性
学习 Buffer,最重要的就是理解三个属性:
capacity
position
limit它们决定了缓冲区当前可以读写的数据范围。
2.1 capacity
capacity 表示缓冲区的容量。
例如:
ByteBuffer buffer =
ByteBuffer.allocate(1024);那么:
capacity = 1024Buffer 创建以后,capacity 不会发生变化。
2.2 position
position 表示当前正在操作的位置。
刚创建 Buffer 时:
position = 0每写入一个数据:
position + 1例如:
ByteBuffer buffer =
ByteBuffer.allocate(10);
buffer.put((byte) 1);
buffer.put((byte) 2);
buffer.put((byte) 3);此时:
capacity = 10
position = 3
limit = 10可以理解为:
0 1 2 3 4 5 6 7 8 9
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ 1 │ 2 │ 3 │ │ │ │ │ │ │ │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
↑
position2.3 limit
limit 表示当前 Buffer 中允许操作的边界。
写模式下,刚创建 Buffer:
position = 0
limit = capacity例如:
capacity = 10
position = 0
limit = 10写入 3 个字节:
capacity = 10
position = 3
limit = 10当准备读取数据时,需要调用:
buffer.flip();调用以后:
limit = 原来的 position
position = 0也就是:
flip() 前
position = 3
limit = 10
flip() 后
position = 0
limit = 3此时读取范围就是:
0 ~ 2也就是刚才真正写入的 3 个字节。
3. Buffer 的读写过程
Buffer 最典型的使用过程是:
写入 Buffer
↓
flip()
↓
读取 Buffer
↓
clear()
↓
继续写入代码:
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();整个状态变化可以简单理解为:
创建
position = 0
limit = capacity
↓
put()
↓
position 不断增加
↓
flip()
↓
limit = position
position = 0
↓
get()
↓
position 不断增加
↓
clear()
↓
position = 0
limit = capacity4. flip()
flip() 是 Buffer 中非常重要的方法。
作用:
将 Buffer 从写模式切换为读模式。
例如:
ByteBuffer buffer =
ByteBuffer.allocate(10);
buffer.put((byte) 1);
buffer.put((byte) 2);
buffer.put((byte) 3);写完以后:
position = 3
limit = 10
capacity = 10调用:
buffer.flip();以后:
position = 0
limit = 3
capacity = 10这样读取时就只会读取真正写入的:
3 个字节5. clear()
读取完成以后,如果还想继续使用 Buffer,可以调用:
buffer.clear();clear() 会把:
position = 0
limit = capacity也就是重新切换为写模式。
需要注意:
clear()并不会真正把 Buffer 中的数据删除,只是重置position和limit,原来的数据之后会被新的数据覆盖。
例如:
clear() 前
position = 3
limit = 3
capacity = 10
clear() 后
position = 0
limit = 10
capacity = 106. rewind()
rewind() 的作用是:
重新读取 Buffer 中的数据。
调用:
buffer.rewind();以后:
position = 0
limit 不变例如:
buffer.flip();
// 第一次读取
while (
buffer.hasRemaining()
) {
System.out.print(
(char) buffer.get()
);
}
// 回到开头
buffer.rewind();
// 再读取一次
while (
buffer.hasRemaining()
) {
System.out.print(
(char) buffer.get()
);
}可以理解为:
flip()
↓
准备第一次读
rewind()
↓
回到开头重新读7. compact()
compact() 主要用于:
Buffer 中还有一部分数据没有读取完,但又想继续写入新数据。
它会把:
未读取的数据移动到 Buffer 前面,然后把 position 移动到这些数据之后,剩余空间继续用于写数据。
可以简单理解为:
Buffer
已读数据 | 未读数据 | 空闲空间
compact()
↓
未读数据 | 空闲空间
↑
position与 clear() 的区别:
clear()
↓
直接认为整个 Buffer 都可以重新写
compact()
↓
保留还没有读取的数据
然后继续写8. 常用方法
Buffer 常用方法:
| 方法 | 作用 |
|---|---|
capacity() | 获取容量 |
position() | 获取当前位置 |
limit() | 获取操作边界 |
flip() | 从写模式切换为读模式 |
clear() | 清空状态,准备重新写入 |
rewind() | position 回到 0,重新读取 |
remaining() | 返回剩余可以操作的数据数量 |
hasRemaining() | 判断是否还有剩余数据 |
mark() | 标记当前 position |
reset() | position 回到 mark 位置 |
其中:
remaining()本质上就是:
limit - position例如:
position = 3
limit = 10
remaining = 79. mark 和 reset
mark() 可以记录当前 position:
buffer.mark();之后 position 即使继续变化,也可以调用:
buffer.reset();回到之前标记的位置。
例如:
buffer.get();
buffer.get();
// 标记当前位置
buffer.mark();
buffer.get();
buffer.get();
// 回到 mark 的位置
buffer.reset();可以理解为:
position
↓
mark()
↓
继续读取
↓
reset()
↓
回到之前的位置10. ByteBuffer
NIO 中最常用的 Buffer 就是:
ByteBuffer创建方式主要有两种。
10.1 allocate()
ByteBuffer buffer =
ByteBuffer.allocate(1024);创建一个容量为 1024 字节的 Buffer。
10.2 wrap()
也可以直接包装一个现有的字节数组:
byte[] bytes =
"kerwim".getBytes();
ByteBuffer buffer =
ByteBuffer.wrap(bytes);这时 Buffer 中直接包含这个字节数组的数据。
11. 堆内 Buffer 和直接 Buffer
ByteBuffer 主要有两种分配方式:
ByteBuffer.allocate()和:
ByteBuffer.allocateDirect()11.1 allocate
ByteBuffer buffer =
ByteBuffer.allocate(1024);创建的是普通 Buffer,数据位于 JVM 堆内存中。
JVM Heap
↓
ByteBuffer11.2 allocateDirect
ByteBuffer buffer =
ByteBuffer.allocateDirect(1024);创建的是直接缓冲区(Direct Buffer)。
数据位于 JVM 堆外的直接内存区域。
JVM Heap
↓
DirectByteBuffer
↓
直接内存直接缓冲区可以减少某些 IO 场景下 JVM 堆和本地内存之间的数据复制,但它的创建和释放成本也相对更高。
因此不能简单理解为:
DirectByteBuffer 一定更快具体还是取决于使用场景。
12. Buffer 与 Channel
Buffer 本身只是存储数据,真正的数据传输由 Channel 完成。
例如从 FileChannel 中读取数据:
FileChannel channel =
new FileInputStream(
"test.txt"
).getChannel();
ByteBuffer buffer =
ByteBuffer.allocate(1024);
channel.read(buffer);数据流向:
文件
↓
FileChannel
↓
ByteBuffer写入文件:
buffer.flip();
channel.write(buffer);数据流向:
ByteBuffer
↓
FileChannel
↓
文件所以学习 NIO 时,可以记住:
Channel
↓
负责传输
Buffer
↓
负责存储13. 小结
学习 Buffer 最重要的是掌握:
capacity
position
limit三个核心属性。
以及:
flip()
clear()
rewind()
compact()几个核心方法。
整个 Buffer 最核心的状态变化:
写数据
↓
position 增加
↓
flip()
↓
position = 0
limit = 原 position
↓
读数据
↓
position 增加
↓
clear()
↓
position = 0
limit = capacity
↓
继续写数据一句话理解:
Buffer 本质上就是一块带有 position、limit、capacity 状态管理的数据存储区域,NIO 通过 Channel 在 Buffer 中读写数据。