Netty从0到1系列之Buffer【下】

三、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; // 1MB
        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++) {
            // 创建堆内存Buffer
            ByteBuffer buffer = ByteBuffer.allocate(size);
            // 写入数据
            for (int j = 0; j < size; j++) {
                buffer.put((byte) (j % 256));
            }
            // 读取数据
            buffer.flip();
            while (buffer.hasRemaining()) {
                buffer.get();
            }

            // 等待GC,模拟真实环境
            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++) {
            // 创建直接内存Buffer
            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 {
                // 使用反射调用Cleaner的clean方法
                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 的核心是一个数组和三个位置指针:

// Buffer 的简化内部结构
public abstract class Buffer {
    // 三个核心属性
    private int position = 0;    // 下一个要读写的位置
    private int limit;           // 可以读写的位置上限
    private int capacity;        // 缓冲区总容量
    
    // 标记位置,用于reset()操作
    private int mark = -1;
    
    // 包装的数组(对于堆缓冲区)
    final Object array;
    final int arrayOffset;
    
    // 地址(对于直接缓冲区)
    final long address;
}

// ByteBuffer 的堆内存实现
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;  // 通过ix计算索引
        return this;
    }
    
    // 索引计算方法
    protected int ix(int i) {
        return i + offset;
    }
}

3.8.2 直接内存与堆内存的区别

// 直接内存Buffer的实现原理
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 {
    
    // 1. 🚀🚀🚀🚀🚀 Buffer 池化实践
    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;
            
            // 初始化Buffer池
            for (int i = 0; i < poolSize; i++) {
                pool[i] = ByteBuffer.allocateDirect(bufferSize);
            }
        }
        
        public synchronized ByteBuffer acquire() throws InterruptedException {
            while (available == 0) {
                wait(); // 等待可用Buffer
            }
            available--;
            return pool[available].clear(); // 返回并清空Buffer
        }
        
        public synchronized void release(ByteBuffer buffer) {
            if (available < pool.length) {
                pool[available] = buffer;
                available++;
                notify(); // 通知等待的线程
            }
        }
    }
    
    // 2. 🐦‍🔥🐦‍🔥🐦‍🔥🐦‍🔥🐦‍🔥🐦‍🔥 高性能文件读取
    public static void readFileWithBuffer(String filePath) throws Exception {
        try (FileChannel channel = FileChannel.open(Paths.get(filePath), StandardOpenOption.READ)) {
            // 使用直接内存Buffer提高IO性能
            ByteBuffer buffer = ByteBuffer.allocateDirect(8192); // 8KB缓冲区
            
            while (channel.read(buffer) != -1) {
                // 切换为读模式
                buffer.flip();
                
                // 处理数据
                processBufferData(buffer);
                
                // 清空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());
    }
    
    // 3. 🍁🍁🍁🍁🍁🍁🍁 网络编程中的Buffer使用
    public static class NetworkBufferHandler {
        private ByteBuffer readBuffer;
        private ByteBuffer writeBuffer;
        
        public NetworkBufferHandler(int bufferSize) {
            // 为读写分别分配Buffer
            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) {
            // 处理字节数据
        }
    }
    
    // 4. ✅✅✅✅✅✅✅ 避免常见的Buffer陷阱
    public static void avoidCommonPitfalls() {
        // 陷阱1:忘记调用flip()
        ByteBuffer buffer = ByteBuffer.allocate(100);
        buffer.put("Hello".getBytes());
        // 忘记调用 buffer.flip() 会导致读取不到数据
        
        // 正确做法
        buffer.put("Hello".getBytes());
        buffer.flip(); // 切换为读模式
        
        // 陷阱2:错误使用clear()和compact()
        buffer.clear(); // 清空整个Buffer
        buffer.compact(); // 只清空已处理的数据,保留未处理数据
        
        // 陷阱3:直接内存泄漏
        ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);
        // 使用后需要适当清理(通常通过GC,但大缓冲区建议手动管理)
    }
    
    public static void main(String[] args) {
        System.out.println("Buffer 最佳实践示例");
        // 创建Buffer池
        BufferPool pool = new BufferPool(10, 4096);
        
        try {
            // 从池中获取Buffer
            ByteBuffer buffer = pool.acquire();
            
            try {
                // 使用Buffer
                buffer.put("测试数据".getBytes());
                buffer.flip();
                
                // 处理数据...
                byte[] data = new byte[buffer.remaining()];
                buffer.get(data);
                System.out.println("处理的数据: " + new String(data));
                
            } finally {
                // 释放Buffer回池中
                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 {
    
    // 1. 📩📩📩📩📩📩📩 视图Buffer - 在不同数据类型间转换
    public static void demonstrateViewBuffers() {
        System.out.println("=== 视图Buffer示例 ===");
        
        // 创建ByteBuffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(32);
        
        // 作为不同数据类型的视图
        byteBuffer.putInt(123);  // 写入int
        byteBuffer.putDouble(45.67);  // 写入double
        byteBuffer.putChar('A');  // 写入char
        
        // 切换为读模式
        byteBuffer.flip();
        
        // 创建不同数据类型的视图
        IntBuffer intBuffer = byteBuffer.asIntBuffer();
        DoubleBuffer doubleBuffer = byteBuffer.asDoubleBuffer();
        CharBuffer charBuffer = byteBuffer.asCharBuffer();
        
        // 注意:视图Buffer共享底层数据,但有自己的position/limit
        System.out.println("Int视图: " + intBuffer.get());
        System.out.println("Double视图: " + doubleBuffer.get());
        System.out.println("Char视图: " + charBuffer.get());
    }
    
    // 2. 💯💯💯💯💯💯💯💯 字节序处理
    public static void demonstrateByteOrder() {
        System.out.println("\n=== 字节序示例 ===");
        
        ByteBuffer buffer = ByteBuffer.allocate(4);
        
        // 默认字节序(通常是BIG_ENDIAN)
        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();
    }
    
    // 3. 🎁🎁🎁🎁🎁🎁🎁 批量操作
    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();
    }
    
    // 4. ✅✅✅✅✅✅✅✅ 只读Buffer
    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();
        
        // 尝试修改只读Buffer会抛出ReadOnlyBufferException
        try {
            readOnly.put((byte) 'X');
        } catch (Exception e) {
            System.out.println("正确捕获异常: " + e.getClass().getSimpleName());
        }
    }
    
    // 5. 内存映射Buffer
    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 ⚠️ 常见错误

// ❌ 错误:忘记 flip()
buffer.put("data".getBytes());
// channel.write(buffer); // ❌ 写不出数据,因为 position=4, limit=10

// ✅ 正确:必须 flip()
buffer.put("data".getBytes());
buffer.flip();
channel.write(buffer); // ✅ 正确

3.11 Buffer 的优缺点总结

3.11.1 ✅ 优点

优点说明
高性能块式读写,减少系统调用
内存控制明确的容量与边界
零拷贝支持DirectBufferMappedByteBuffer
线程安全单线程操作,无并发问题(通常)
类型丰富支持多种基本类型

3.11.2 ❌ 缺点

缺点说明
状态管理复杂position/limit/flip() 易出错
API 繁琐相比流式 IO 更复杂
堆外内存泄漏风险DirectBuffer 不受 GC 直接管理
学习成本高需理解读写模式切换

3.12 总结:Buffer 的核心价值

维度说明
核心角色NIO 的“数据容器”
设计思想块式 I/O 取代流式 I/O
核心能力统一的数据读写接口,支持高效 I/O
适用场景文件读写、网络通信、大文件处理
NIO 地位ChannelSelector 并列为三大基石

3.13 一句话总结

Buffer 是 Java NIO 的“心脏”

  • 它通过容量、位置、上限、标记四要素精确控制数据流,结合 flip() 等操作实现高效的读写切换
  • 为高性能 I/O 提供了坚实基础。掌握 Buffer,是掌握 NIO 的第一步。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值