BufferUnderflowException 是越界读取已发生的信号,非防护手段;安全读取需在调用 getInt()、getLong() 等前校验 remaining() 是否足够,并显式 flip()、慎用 slice()/duplicate(),推荐封装带校验的工具方法。

BufferUnderflowException 不是读取安全的“防护手段”,而是越界读取已发生的明确信号。它说明你调用 get()、getInt() 等方法时,缓冲区当前 position 已 ≥ limit,没数据可读了。真正保障安全的关键,在于读取前主动判断、提前拦截。
读取前必须校验 remaining() 是否足够
每次调用固定长度读取方法前,都要确认缓冲区剩余空间满足要求:
-
buffer.remaining() >= 4—— 才能安全调用getInt() -
buffer.remaining() >= 8—— 才能安全调用getLong() -
buffer.remaining() >= len—— 才能安全调用get(byte[], 0, len)
硬编码或依赖外部长度字段(如协议头里的 payload 长度)时,尤其要防该值被篡改或解析错误。校验失败应立即抛出语义清晰的异常,比如 IllegalArgumentException 或自定义的 DataCorruptionException,而不是等 BufferUnderflowException 冒出来再处理。
避免依赖 flip() 或 hasRemaining() 的误判
常见误区是认为“刚从 Channel 读完数据,就能直接读”。但 SocketChannel.read(buffer) 返回的是实际字节数,不自动更新 limit。必须显式调用 buffer.flip(),把 limit 设为当前 position、position 归零,后续 hasRemaining() 和 remaining() 才反映真实可读字节数。
调试时建议打印状态:System.out.printf("pos=%d, lim=%d, cap=%d%n", buf.position(), buf.limit(), buf.capacity());
慎用 duplicate() 和 slice() 后的缓冲区
这两个操作返回的新缓冲区共享底层数组,但 position 和 limit 是独立继承的,极易导致“以为能读,其实不能”:
-
slice()的capacity()等于原缓冲区调用时的remaining(),且position总是 0;若原缓冲区未flip()就 slice,可能得到空缓冲区 -
duplicate()复制的是当前完整状态,包括已走到末尾的position;对副本直接get()会立刻抛异常
使用前务必检查新缓冲区的 remaining(),不要默认它“可用”。
封装带校验的工具方法更可靠
重复写校验逻辑易遗漏。推荐封装成静态工具方法,统一管控:
- 返回
Optional<Integer>表示读取可能失败 - 抛出自定义异常(如
InsufficientDataException),比原生BufferUnderflowException更具业务含义 - 对批量读取(如按长度读 payload),在校验通过后才分配目标数组并执行
get()
这样既减少样板代码,又让边界检查成为强制环节,而不是靠异常兜底。

















