三、Java NIO Buffer: 数据操作的基石
3.7 示例代码
3.7.1 直接内存Buffer与堆内存Buffer对比
package cn.tcmeta.bytebuffer;
import java.nio.ByteBuffer;
public class DirectVsHeapBuffer {
public static void main(String[] args) {
final int SIZE = 1024 * 1024;
final int ITERATIONS = 1000;
System.out.println("测试堆内存Buffer...");
long heapTime = testHeapBuffer(SIZE, ITERATIONS);
System.out.println("测试直接内存Buffer...");
long directTime = testDirectBuffer(SIZE, ITERATIONS);
System.out.println("\n=== 性能对比 ===");
System.out.println("堆内存Buffer耗时: " + heapTime + "ms");
System.out.println("直接内存Buffer耗时: " + directTime + "ms");
System.out.println("性能差异: " + (heapTime - directTime) + "ms");
}
private static long testHeapBuffer(int size, int iterations) {
long startTime = System.currentTimeMillis();
for (int i = 0; i < iterations; i++) {
ByteBuffer buffer = ByteBuffer.allocate(size);
for (int j = 0; j < size; j++) {
buffer.put((byte) (j % 256));
}
buffer.flip();
while (buffer.hasRemaining()) {
buffer.get();
}
if (i % 100 == 0) {
System.gc();
}
}
return System.currentTimeMillis() - startTime;
}
private static long testDirectBuffer(int size, int iterations) {
long startTime = System.currentTimeMillis();
for (int i = 0; i < iterations; i++) {
ByteBuffer buffer = ByteBuffer.allocateDirect(size);
for (int j = 0; j < size; j++) {
buffer.put((byte) (j % 256));
}
buffer.flip();
while (buffer.hasRemaining()) {
buffer.get();
}
if (i % 100 == 0) {
cleanDirectBuffer(buffer);
}
}
return System.currentTimeMillis() - startTime;
}
private static void cleanDirectBuffer(ByteBuffer buffer) {
if (buffer.isDirect()) {
try {
Object cleaner = buffer.getClass().getMethod("cleaner").invoke(buffer);
if (cleaner != null) {
cleaner.getClass().getMethod("clean").invoke(cleaner);
}
} catch (Exception e) {
}
}
}
}
测试堆内存Buffer...
测试直接内存Buffer...
=== 性能对比 ===
堆内存Buffer耗时: 1324ms
直接内存Buffer耗时: 1224ms
性能差异: 100ms
3.8 Buffer的底层实现原理
3.8.1 Buffer的内存结构
Buffer 的核心是一个数组和三个位置指针:
public abstract class Buffer {
private int position = 0;
private int limit;
private int capacity;
private int mark = -1;
final Object array;
final int arrayOffset;
final long address;
}
class HeapByteBuffer extends ByteBuffer {
protected final byte[] hb;
protected final int offset;
HeapByteBuffer(int cap, int lim) {
super(-1, 0, lim, cap, new byte[cap], 0);
hb = new byte[cap];
offset = 0;
}
@Override
public ByteBuffer put(byte x) {
hb[ix(nextPutIndex())] = x;
return this;
}
protected int ix(int i) {
return i + offset;
}
}
3.8.2 直接内存与堆内存的区别
class DirectByteBuffer extends MappedByteBuffer implements DirectBuffer {
protected long address;
protected final Cleaner cleaner;
DirectByteBuffer(int cap) {
super(-1, 0, cap, cap);
address = unsafe.allocateMemory(cap);
cleaner = Cleaner.create(this, new Deallocator(address, cap));
}
private static class Deallocator implements Runnable {
private long address;
private long size;
Deallocator(long address, long size) {
this.address = address;
this.size = size;
}
public void run() {
if (address != 0) {
unsafe.freeMemory(address);
address = 0;
}
}
}
}
3.9 实践与最佳实践
3.9.1 Buffer 使用最佳实践
- Buffer池化实践
- 高性能文件读取
- 网络编程中的Buffer使用
- 避免常见的Buffer陷阱
package cn.tcmeta.bytebuffer;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class BufferBestPractices {
public static class BufferPool {
private final ByteBuffer[] pool;
private final int bufferSize;
private int available;
public BufferPool(int poolSize, int bufferSize) {
this.pool = new ByteBuffer[poolSize];
this.bufferSize = bufferSize;
this.available = poolSize;
for (int i = 0; i < poolSize; i++) {
pool[i] = ByteBuffer.allocateDirect(bufferSize);
}
}
public synchronized ByteBuffer acquire() throws InterruptedException {
while (available == 0) {
wait();
}
available--;
return pool[available].clear();
}
public synchronized void release(ByteBuffer buffer) {
if (available < pool.length) {
pool[available] = buffer;
available++;
notify();
}
}
}
public static void readFileWithBuffer(String filePath) throws Exception {
try (FileChannel channel = FileChannel.open(Paths.get(filePath), StandardOpenOption.READ)) {
ByteBuffer buffer = ByteBuffer.allocateDirect(8192);
while (channel.read(buffer) != -1) {
buffer.flip();
processBufferData(buffer);
buffer.compact();
}
buffer.flip();
while (buffer.hasRemaining()) {
processBufferData(buffer);
}
}
}
private static void processBufferData(ByteBuffer buffer) {
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
System.out.println("处理数据: " + new String(data).trim());
}
public static class NetworkBufferHandler {
private ByteBuffer readBuffer;
private ByteBuffer writeBuffer;
public NetworkBufferHandler(int bufferSize) {
readBuffer = ByteBuffer.allocate(bufferSize);
writeBuffer = ByteBuffer.allocate(bufferSize);
}
public void handleRead() {
readBuffer.flip();
try {
while (readBuffer.hasRemaining()) {
byte b = readBuffer.get();
processByte(b);
}
} finally {
readBuffer.compact();
}
}
public void prepareWrite(byte[] data) {
writeBuffer.clear();
writeBuffer.put(data);
writeBuffer.flip();
}
private void processByte(byte b) {
}
}
public static void avoidCommonPitfalls() {
ByteBuffer buffer = ByteBuffer.allocate(100);
buffer.put("Hello".getBytes());
buffer.put("Hello".getBytes());
buffer.flip();
buffer.clear();
buffer.compact();
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);
}
public static void main(String[] args) {
System.out.println("Buffer 最佳实践示例");
BufferPool pool = new BufferPool(10, 4096);
try {
ByteBuffer buffer = pool.acquire();
try {
buffer.put("测试数据".getBytes());
buffer.flip();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
System.out.println("处理的数据: " + new String(data));
} finally {
pool.release(buffer);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
3.9.2 高级Buffer技巧
- 视图Buffer, 在不同的数据类型间转换
- 字节序处理
- 批量操作
- 只读Buffer
package cn.tcmeta.bytebuffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.CharBuffer;
import java.nio.DoubleBuffer;
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.nio.ShortBuffer;
public class AdvancedBufferTechniques {
public static void demonstrateViewBuffers() {
System.out.println("=== 视图Buffer示例 ===");
ByteBuffer byteBuffer = ByteBuffer.allocate(32);
byteBuffer.putInt(123);
byteBuffer.putDouble(45.67);
byteBuffer.putChar('A');
byteBuffer.flip();
IntBuffer intBuffer = byteBuffer.asIntBuffer();
DoubleBuffer doubleBuffer = byteBuffer.asDoubleBuffer();
CharBuffer charBuffer = byteBuffer.asCharBuffer();
System.out.println("Int视图: " + intBuffer.get());
System.out.println("Double视图: " + doubleBuffer.get());
System.out.println("Char视图: " + charBuffer.get());
}
public static void demonstrateByteOrder() {
System.out.println("\n=== 字节序示例 ===");
ByteBuffer buffer = ByteBuffer.allocate(4);
System.out.println("默认字节序: " + buffer.order());
buffer.order(ByteOrder.LITTLE_ENDIAN);
buffer.putInt(0x12345678);
buffer.flip();
for (int i = 0; i < 4; i++) {
System.out.printf("%02X ", buffer.get());
}
System.out.println();
}
public static void demonstrateBulkOperations() {
System.out.println("\n=== 批量操作示例 ===");
ByteBuffer buffer = ByteBuffer.allocate(100);
byte[] data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
buffer.put(data);
buffer.flip();
byte[] result = new byte[buffer.remaining()];
buffer.get(result);
System.out.print("批量获取的数据: ");
for (byte b : result) {
System.out.print(b + " ");
}
System.out.println();
}
public static void demonstrateReadOnlyBuffer() {
System.out.println("\n=== 只读Buffer示例 ===");
ByteBuffer original = ByteBuffer.allocate(10);
original.put("Hello".getBytes());
original.flip();
ByteBuffer readOnly = original.asReadOnlyBuffer();
System.out.print("只读Buffer内容: ");
while (readOnly.hasRemaining()) {
System.out.print((char) readOnly.get());
}
System.out.println();
try {
readOnly.put((byte) 'X');
} catch (Exception e) {
System.out.println("正确捕获异常: " + e.getClass().getSimpleName());
}
}
public static void demonstrateMemoryMappedBuffer() throws Exception {
System.out.println("\n=== 内存映射Buffer示例 ===");
System.out.println("内存映射文件提供了一种将文件直接映射到内存的机制,");
System.out.println("可以通过MappedByteBuffer直接操作文件内容,");
System.out.println("避免了系统调用的开销,极大提高了IO性能。");
}
public static void main(String[] args) throws Exception {
demonstrateViewBuffers();
demonstrateByteOrder();
demonstrateBulkOperations();
demonstrateReadOnlyBuffer();
demonstrateMemoryMappedBuffer();
}
}
3.10 最佳实践与常见陷阱
3.10.1 ✅ 推荐实践
| 实践 | 说明 |
|---|
| 优先使用 DirectBuffer 进行 I/O | 减少内存拷贝 |
| 合理设置 Buffer 大小 | 避免过小(频繁 flip)或过大(内存浪费) |
使用 hasRemaining() 判断是否可读写 | 安全读写 |
及时调用 flip() 切换模式 | 核心操作,不可遗漏 |
使用 try-finally 释放 DirectBuffer | 防止内存泄漏 |
3.10.2 ⚠️ 常见错误
buffer.put("data".getBytes());
buffer.put("data".getBytes());
buffer.flip();
channel.write(buffer);
3.11 Buffer 的优缺点总结
3.11.1 ✅ 优点
| 优点 | 说明 |
|---|
| 高性能 | 块式读写,减少系统调用 |
| 内存控制 | 明确的容量与边界 |
| 零拷贝支持 | DirectBuffer 和 MappedByteBuffer |
| 线程安全 | 单线程操作,无并发问题(通常) |
| 类型丰富 | 支持多种基本类型 |
3.11.2 ❌ 缺点
| 缺点 | 说明 |
|---|
| 状态管理复杂 | position/limit/flip() 易出错 |
| API 繁琐 | 相比流式 IO 更复杂 |
| 堆外内存泄漏风险 | DirectBuffer 不受 GC 直接管理 |
| 学习成本高 | 需理解读写模式切换 |
3.12 总结:Buffer 的核心价值
| 维度 | 说明 |
|---|
| 核心角色 | NIO 的“数据容器” |
| 设计思想 | 块式 I/O 取代流式 I/O |
| 核心能力 | 统一的数据读写接口,支持高效 I/O |
| 适用场景 | 文件读写、网络通信、大文件处理 |
| NIO 地位 | 与 Channel、Selector 并列为三大基石 |
3.13 一句话总结
Buffer 是 Java NIO 的“心脏”
- 它通过容量、位置、上限、标记四要素精确控制数据流,结合
flip() 等操作实现高效的读写切换 - 为高性能 I/O 提供了坚实基础。掌握
Buffer,是掌握 NIO 的第一步。