必须显式调用 flip() 才能从写模式安全切换到读模式,否则 get() 会从写入末尾位置开始读,跳过已写数据或触发 bufferunderflowexception。

必须显式调用 flip() 才能从写模式安全切换到读模式,否则 get() 会读到错误位置甚至抛 BufferUnderflowException
为什么写完不 flip 就读会出错
ByteBuffer 没有“自动读写识别”机制,它只靠 position 和 limit 两个指针决定当前可操作范围。写入后 position 停在数据末尾,limit 还是容量值;若直接读,get() 会从这个“末尾位置”开始取,结果不是乱码就是越界异常。
- 写入
"abc"后:position = 3,limit = capacity(比如 1024) - 不
flip()直接get():从索引 3 开始读,前 3 字节(abc)永远被跳过 - 若
position == limit时还强行get(),立刻触发BufferUnderflowException
flip()、clear()、compact() 该选哪个
三者都重置指针,但语义和适用场景完全不同,混用会导致数据覆盖或丢失:
-
flip():仅用于“写完即读”场景,把limit设为当前position,position归零 —— 这是读取刚写入数据的**唯一正确入口** -
clear():读完后想**完全重用缓冲区**(丢弃所有旧数据),它把position = 0、limit = capacity,但**不擦除内存内容**,只是逻辑清空 -
compact():读了一半还想继续写新数据(比如网络分包场景),它把未读数据移到开头,position指向其后,避免覆盖残留数据
示例:接收 TCP 流时,一次 channel.read(buf) 可能只读到半个包,这时必须用 compact() 而非 clear(),否则后半包就丢了。
HeapByteBuffer 和 DirectByteBuffer 的实际取舍
性能差异真实存在,但选择不能只看“快慢”,得结合使用模式:
-
ByteBuffer.allocate(1024)返回HeapByteBuffer:分配快、GC 可回收,适合短生命周期、小批量数据(如 HTTP header 解析) -
ByteBuffer.allocateDirect(1024)返回DirectByteBuffer:分配慢、不走 GC,但与 Channel 交互时省去 JVM 堆拷贝,适合长期持有、高频 IO 场景(如 Netty 的 I/O buffer) - 注意:
DirectByteBuffer的内存由Cleaner异步回收,若大量创建又不及时释放,可能触发OutOfMemoryError: Direct buffer memory
读写类型不匹配是静默陷阱
ByteBuffer 是字节容器,但提供 putInt()、getInt() 等类型方法 —— 它们按固定字节数(如 int 占 4 字节)操作,并依赖当前 byteOrder。一旦写入和读取类型不一致,结果必然错乱:
- 用
putInt(0x12345678)写入,再用getShort()读:只取前 2 字节,得到0x1234或0x3412(取决于大小端) - 跨平台通信时,务必统一调用
order(ByteOrder.BIG_ENDIAN)或order(ByteOrder.LITTLE_ENDIAN) - 调试时可用
buf.array()(仅HeapByteBuffer)查看底层字节数组,验证实际布局
真正容易被忽略的是:flip() 不重置 byteOrder,也不校验类型对齐;所有类型读写都基于当前 position,错一位,后面全崩。











