Java NIO 缓冲区概述
因为一直在研究 Apache Mina,看到底层代码后,发现我对 Java NIO 了解甚少,于是开始深入学习 Java NIO。本文是 Java NIO 系列的第一篇,主要介绍 NIO 缓冲区(Buffer)的基本概念和使用方法。
缓冲区的核心属性
Java NIO 缓冲区有四个核心属性,理解这些属性是掌握缓冲区操作的关键:
- capacity:缓冲区可容纳的最大数据量,一旦设置不可改变
- position:当前操作位置,读写操作会改变此值
- limit:缓冲区第一个不能被读写的元素位置
- mark:标记位置,用于后续重置 position
这四个属性的关系是:0 <= mark <= position <= limit <= capacity
缓冲区类型
Java NIO 提供了多种类型的缓冲区,对应不同的数据类型:
| 缓冲区类型 |
对应数据类型 |
| ByteBuffer |
byte |
| CharBuffer |
char |
| ShortBuffer |
short |
| IntBuffer |
int |
| LongBuffer |
long |
| FloatBuffer |
float |
| DoubleBuffer |
double |
缓冲区的基本操作
1. 创建缓冲区
1 2 3 4 5 6 7 8 9
| ByteBuffer buffer = ByteBuffer.allocate(1024);
byte[] array = new byte[1024]; ByteBuffer buffer = ByteBuffer.wrap(array);
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);
|
2. 写入数据
1 2 3 4 5 6 7 8 9 10 11 12 13
| buffer.put((byte)'H'); buffer.put((byte)'e'); buffer.put((byte)'l'); buffer.put((byte)'l'); buffer.put((byte)'o');
buffer.put(0, (byte)'M');
byte[] data = "World".getBytes(); buffer.put(data);
|
3. 翻转缓冲区
当写入完成后,需要将缓冲区从写模式切换到读模式:
4. 读取数据
1 2 3 4 5 6 7 8 9 10 11 12
| while (buffer.hasRemaining()) { byte b = buffer.get(); System.out.print((char) b); }
byte[] dest = new byte[buffer.remaining()]; buffer.get(dest);
buffer.get(0);
|
5. 重置缓冲区
1 2 3 4 5 6 7 8 9 10 11
| buffer.clear();
buffer.rewind();
buffer.mark();
buffer.reset();
|
缓冲区的工作原理
初始状态
创建缓冲区后,初始状态为:
- position = 0
- limit = capacity
- mark = -1(未定义)
写入数据
写入数据时,position 会逐渐增加,直到达到 limit。
翻转操作
调用 flip() 后:
- limit = position(设置可读数据的边界)
- position = 0(从开始位置读取)
- mark = -1(清除标记)
读取数据
读取数据时,position 会逐渐增加,直到达到 limit。
清空操作
调用 clear() 后:
- position = 0
- limit = capacity
- mark = -1
注意:clear() 只是重置了指针,并没有清除数据。
直接缓冲区与非直接缓冲区
非直接缓冲区
- 在 JVM 堆中分配内存
- 读写操作需要在 JVM 堆和 native 内存之间复制数据
- 创建和销毁速度快
- 适合小数据量操作
直接缓冲区
- 在 native 内存中分配内存
- 读写操作直接操作 native 内存,无需复制
- 创建和销毁速度慢
- 适合大数据量操作,特别是需要与通道直接交互的场景
1 2 3 4 5
| ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);
boolean isDirect = buffer.isDirect();
|
缓冲区的最佳实践
- 选择合适的缓冲区类型:根据数据类型选择对应的缓冲区
- 合理设置容量:根据实际需要设置缓冲区容量,避免频繁扩容
- 使用直接缓冲区:对于大数据量操作,使用直接缓冲区提高性能
- 正确管理缓冲区状态:熟练使用 flip(), clear(), rewind() 等方法
- 注意缓冲区边界:使用 hasRemaining() 检查是否还有数据可读
- 避免频繁创建缓冲区:可以复用缓冲区减少GC压力
代码示例
基本读写操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import java.nio.ByteBuffer;
public class BufferExample { public static void main(String[] args) { ByteBuffer buffer = ByteBuffer.allocate(1024); String message = "Hello, Java NIO!"; buffer.put(message.getBytes()); buffer.flip(); byte[] data = new byte[buffer.remaining()]; buffer.get(data); System.out.println(new String(data)); buffer.clear(); String newMessage = "Welcome to NIO Buffer!"; buffer.put(newMessage.getBytes()); buffer.flip(); while (buffer.hasRemaining()) { System.out.print((char) buffer.get()); } } }
|
缓冲区复用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| import java.nio.CharBuffer;
public class BufferFillData { private static int index = 0; private static String[] strings = { "A random string value", "The product of an infinite number of monkeys", "Hey hey we're the Monkees", "Opening act for the Monkees: jiangfuqiang", "Help me! Help me!" }; public static void main(String[] args) { CharBuffer buffer = CharBuffer.allocate(100); while (fillBuffer(buffer)) { buffer.flip(); drainBuffer(buffer); buffer.clear(); } } private static void drainBuffer(CharBuffer buffer) { while (buffer.hasRemaining()) { System.out.print(buffer.get()); } System.out.println(); } private static boolean fillBuffer(CharBuffer buffer) { if (index >= strings.length) { return false; } String string = strings[index++]; for (int i = 0; i < string.length(); i++) { buffer.put(string.charAt(i)); } return true; } }
|
缓冲区的高级操作
1. 压缩操作
当缓冲区中还有未读取的数据时,可以使用 compact() 方法将未读取的数据移到缓冲区开头:
1 2 3 4 5 6 7 8
| buffer.get(data, 0, 5);
buffer.compact();
buffer.put(moreData);
|
2. 批量操作
1 2 3 4 5 6 7 8 9 10 11 12
| byte[] data = new byte[100]; buffer.put(data);
buffer.get(data);
buffer.put(data, 0, 50);
buffer.get(data, 0, 50);
|
3. 缓冲区比较
1 2 3 4 5
| int result = buffer1.compareTo(buffer2);
boolean equal = buffer1.equals(buffer2);
|
总结
Java NIO 缓冲区是 NIO 操作的基础,掌握缓冲区的使用对于理解 NIO 至关重要。本文介绍了缓冲区的核心属性、类型、基本操作和最佳实践,希望能帮助读者更好地理解和使用 Java NIO 缓冲区。
在后续文章中,我们将继续介绍 Java NIO 的通道(Channel)和选择器(Selector),敬请期待!