Java NIO 缓冲区高级特性(二)
上一篇介绍了缓冲区的基本属性,本文将详细讲解缓冲区的压缩、标记等高级特性,以及现代Java NIO缓冲区的最佳实践。
1. 缓冲区压缩(Compact)
1.1 compact() 方法
compact() 方法是 ByteBuffer 中的一个重要方法,用于处理部分读取后的数据:
1 2 3
| public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer> { public abstract ByteBuffer compact(); }
|
1.2 压缩原理
当从缓冲区中读取部分数据后,compact() 方法会:
- 将未读取的数据(从 position 到 limit 之间的数据)移动到缓冲区的起始位置
- 将 position 设置为未读取数据的长度
- 将 limit 设置为 capacity
- 清除 mark 标记
1.3 压缩示例
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 40 41 42 43 44 45
| import java.nio.ByteBuffer;
public class BufferCompactExample { public static void main(String[] args) { ByteBuffer buffer = ByteBuffer.allocate(10); buffer.put((byte) 'H'); buffer.put((byte) 'e'); buffer.put((byte) 'l'); buffer.put((byte) 'l'); buffer.put((byte) 'o'); buffer.put((byte) ' '); buffer.put((byte) 'W'); buffer.put((byte) 'o'); buffer.put((byte) 'r'); buffer.put((byte) 'l'); buffer.flip(); for (int i = 0; i < 5; i++) { System.out.print((char) buffer.get()); } System.out.println(); buffer.compact(); buffer.put((byte) 'd'); buffer.put((byte) '!'); buffer.flip(); while (buffer.hasRemaining()) { System.out.print((char) buffer.get()); } System.out.println(); } }
|
1.4 压缩的应用场景
- 数据处理管道:当需要处理部分数据后继续添加新数据时
- 网络通信:处理不完整的网络数据包
- 文件 I/O:处理大文件时的分块读写
2. 缓冲区标记(Mark)
2.1 标记相关方法
mark():设置当前 position 为标记位置
reset():将 position 恢复到标记位置
clear():清除标记
2.2 标记示例
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
| import java.nio.ByteBuffer;
public class BufferMarkExample { public static void main(String[] args) { ByteBuffer buffer = ByteBuffer.allocate(10); buffer.put((byte) 'H'); buffer.put((byte) 'e'); buffer.put((byte) 'l'); buffer.put((byte) 'l'); buffer.put((byte) 'o'); buffer.flip(); System.out.print((char) buffer.get()); System.out.print((char) buffer.get()); buffer.mark(); System.out.print((char) buffer.get()); System.out.print((char) buffer.get()); buffer.reset(); while (buffer.hasRemaining()) { System.out.print((char) buffer.get()); } System.out.println(); } }
|
2.3 标记的注意事项
- 标记是可选的,不是所有操作都需要标记
- 调用
reset() 前必须先调用 mark(),否则会抛出 InvalidMarkException
clear()、compact() 和 flip() 等操作会清除标记
3. 缓冲区比较与排序
3.1 比较方法
1
| public abstract int compareTo(ByteBuffer that);
|
比较规则:
- 比较剩余字节的数量
- 比较第一个不同的字节
- 如果一个缓冲区是另一个的前缀,则较短的缓冲区被认为较小
3.2 比较示例
1 2 3 4 5 6 7 8 9 10 11 12 13
| import java.nio.ByteBuffer;
public class BufferCompareExample { public static void main(String[] args) { ByteBuffer buffer1 = ByteBuffer.wrap("Hello".getBytes()); ByteBuffer buffer2 = ByteBuffer.wrap("Hello".getBytes()); ByteBuffer buffer3 = ByteBuffer.wrap("World".getBytes()); System.out.println("buffer1.compareTo(buffer2): " + buffer1.compareTo(buffer2)); System.out.println("buffer1.compareTo(buffer3): " + buffer1.compareTo(buffer3)); System.out.println("buffer3.compareTo(buffer1): " + buffer3.compareTo(buffer1)); } }
|
4. 缓冲区视图
4.1 视图类型
asCharBuffer():创建字符缓冲区视图
asShortBuffer():创建短整型缓冲区视图
asIntBuffer():创建整型缓冲区视图
asLongBuffer():创建长整型缓冲区视图
asFloatBuffer():创建浮点型缓冲区视图
asDoubleBuffer():创建双精度浮点型缓冲区视图
4.2 视图示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import java.nio.ByteBuffer; import java.nio.IntBuffer;
public class BufferViewExample { public static void main(String[] args) { ByteBuffer byteBuffer = ByteBuffer.allocate(16); for (int i = 0; i < 4; i++) { byteBuffer.putInt(i * 10); } byteBuffer.flip(); IntBuffer intBuffer = byteBuffer.asIntBuffer(); while (intBuffer.hasRemaining()) { System.out.println(intBuffer.get()); } } }
|
5. 直接缓冲区与非直接缓冲区
5.1 直接缓冲区
1 2 3 4 5
| ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);
boolean isDirect = directBuffer.isDirect();
|
5.2 直接缓冲区的优势
- 性能优势:在 I/O 操作时避免了数据在用户空间和内核空间之间的复制
- 适用于:频繁的 I/O 操作,如网络通信和文件 I/O
5.3 直接缓冲区的劣势
- 创建成本高:分配和释放直接缓冲区的成本较高
- 内存管理:不受 Java 垃圾回收器直接管理
6. 缓冲区最佳实践
6.1 选择合适的缓冲区类型
| 场景 |
推荐缓冲区类型 |
原因 |
| 网络通信 |
直接 ByteBuffer |
减少数据复制,提高性能 |
| 内存数据处理 |
非直接 ByteBuffer |
创建成本低,适合频繁创建和销毁 |
| 字符数据 |
CharBuffer |
专门用于字符处理 |
| 数值数据 |
IntBuffer, LongBuffer 等 |
类型安全,避免类型转换错误 |
6.2 性能优化技巧
- 重用缓冲区:避免频繁创建和销毁缓冲区
- 合理设置容量:根据实际需求设置合适的缓冲区大小
- 使用直接缓冲区:对于 I/O 密集型操作
- 正确使用 flip():在读写切换时使用
- 使用 compact():在部分读取后继续写入时使用
6.3 常见错误及解决方案
| 错误 |
原因 |
解决方案 |
| BufferUnderflowException |
读取超过 limit 位置 |
使用 hasRemaining() 检查 |
| BufferOverflowException |
写入超过 capacity |
检查剩余空间或扩容 |
| InvalidMarkException |
调用 reset() 前未调用 mark() |
确保先调用 mark() |
| ReadOnlyBufferException |
尝试写入只读缓冲区 |
使用可写缓冲区 |
7. 现代 Java NIO 缓冲区使用示例
7.1 网络通信示例
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
| import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel;
public class NioServer { public static void main(String[] args) throws IOException { ServerSocketChannel serverSocket = ServerSocketChannel.open(); serverSocket.bind(new InetSocketAddress(8080)); System.out.println("Server started on port 8080"); while (true) { SocketChannel client = serverSocket.accept(); System.out.println("Client connected: " + client.getRemoteAddress()); ByteBuffer buffer = ByteBuffer.allocateDirect(1024); while (client.read(buffer) > 0) { buffer.flip(); while (buffer.hasRemaining()) { System.out.print((char) buffer.get()); } buffer.clear(); } client.close(); } } }
|
7.2 文件 I/O 示例
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.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.file.Paths; import java.nio.file.StandardOpenOption;
public class FileIOExample { public static void main(String[] args) throws IOException { try (FileChannel fileChannel = FileChannel.open( Paths.get("example.txt"), StandardOpenOption.CREATE, StandardOpenOption.WRITE)) { ByteBuffer buffer = ByteBuffer.allocate(1024); buffer.put("Hello, NIO!".getBytes()); buffer.flip(); fileChannel.write(buffer); } try (FileChannel fileChannel = FileChannel.open( Paths.get("example.txt"), StandardOpenOption.READ)) { ByteBuffer buffer = ByteBuffer.allocate(1024); while (fileChannel.read(buffer) > 0) { buffer.flip(); while (buffer.hasRemaining()) { System.out.print((char) buffer.get()); } buffer.clear(); } } } }
|
8. 缓冲区与现代 Java 特性
8.1 与 Stream API 结合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import java.nio.ByteBuffer; import java.util.stream.IntStream;
public class BufferStreamExample { public static void main(String[] args) { ByteBuffer buffer = ByteBuffer.allocate(10); IntStream.range(0, 10).forEach(i -> buffer.put((byte) (i + 65))); buffer.flip(); while (buffer.hasRemaining()) { System.out.print((char) buffer.get()); } System.out.println(); } }
|
8.2 与 CompletableFuture 结合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import java.nio.ByteBuffer; import java.util.concurrent.CompletableFuture;
public class BufferAsyncExample { public static void main(String[] args) { CompletableFuture.supplyAsync(() -> { ByteBuffer buffer = ByteBuffer.allocate(10); for (int i = 0; i < 10; i++) { buffer.put((byte) (i + 65)); } return buffer; }).thenAccept(buffer -> { buffer.flip(); while (buffer.hasRemaining()) { System.out.print((char) buffer.get()); } System.out.println(); }).join(); } }
|
9. 参考资料