ByteBuffer 各个方法的详解

吴书松
吴书松
发布于 2026-01-21 / 1 阅读
0
0

ByteBuffer 各个方法的详解

ByteBuffer 主要方法详解

一、创建和分配

方法

说明

ByteBuffer.allocate(int capacity)

分配堆内存缓冲区,JVM管理

ByteBuffer.allocateDirect(int capacity)

分配直接内存缓冲区,操作系统管理,性能更高

ByteBuffer.wrap(byte[] array)

将已有字节数组包装为缓冲区

ByteBuffer.wrap(byte[] array, int offset, int length)

包装数组的指定部分

二、缓冲区状态属性

java

// 四个关键属性
capacity: 缓冲区总容量,创建时固定,不可变
position: 当前位置,下一个读写操作的索引
limit: 读写限制,position不能超过limit
mark: 标记位置,可标记后回到此位置

三、读写数据方法

1. 读取方法

方法

说明

get()

读取当前位置的一个字节,position+1

get(byte[] dst)

读取到字节数组

get(byte[] dst, int offset, int length)

读取到数组指定位置

get(int index)

读取指定位置的字节,不改变position

getChar(), getInt(), getLong(), getFloat(), getDouble()

读取基本类型数据

2. 写入方法

方法

说明

put(byte b)

写入一个字节

put(byte[] src)

写入字节数组

put(ByteBuffer src)

写入另一个缓冲区

put(int index, byte b)

在指定位置写入

putChar(), putInt(), 等

写入基本类型

四、位置操作方法

1. 基础操作

java

// 设置位置
position(int newPosition)    // 设置当前位置
limit(int newLimit)          // 设置限制位置

// 查询
remaining()                 // 返回剩余可读/可写字节数:limit - position
hasRemaining()              // 检查是否还有剩余字节

2. 模式切换

java

// 写入模式 → 读取模式
flip():                      // limit=position, position=0, 清除mark

// 重新开始读取
rewind():                    // position=0, 保留limit, 清除mark

// 准备再次写入
clear():                     // position=0, limit=capacity, 清除mark
                            // 不清除数据,只是重置指针

// 读取模式 → 写入模式(保留未读数据)
compact():                   // 将未读数据复制到开头,position=剩余数据量,limit=capacity
                            // 用于处理粘包

五、标记和重置

java

mark():      // 标记当前位置
reset():     // 返回到mark的位置(mark必须已设置)

六、重要转换方法

方法

说明

array()

返回底层字节数组(仅堆缓冲区)

slice()

创建新缓冲区,共享数据,但有自己的position/limit

duplicate()

创建完全独立的缓冲区副本

asReadOnlyBuffer()

创建只读视图

七、实际使用示例

1. 基本读写流程

java

// 写入数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.putInt(100);        // 写入int,position移动4
buffer.put("Hello".getBytes());

// 切换到读取模式
buffer.flip();

// 读取
int num = buffer.getInt();  // 读取int
byte[] strBytes = new byte[5];
buffer.get(strBytes);       // 读取字符串

2. 处理网络数据(粘包拆包)

java

public void decode(ByteBuffer buffer) {
    while (buffer.remaining() >= 4) {  // 确保有长度字段
        buffer.mark();  // 标记当前位置
        
        int length = buffer.getInt();  // 读取消息长度
        if (buffer.remaining() < length) {
            // 数据不完整,重置并等待
            buffer.reset();
            break;
        }
        
        // 读取完整消息
        byte[] data = new byte[length];
        buffer.get(data);
        processMessage(data);
        
        // 如果有剩余数据,继续处理(处理粘包)
        if (buffer.hasRemaining()) {
            // 继续循环处理下一个消息
        }
    }
    
    // 压缩缓冲区,保留未处理数据
    buffer.compact();
}

3. 使用slice处理部分数据

java

ByteBuffer buffer = ByteBuffer.allocate(100);
// ... 填充数据
buffer.flip();

// 处理前10字节
buffer.limit(10);
ByteBuffer header = buffer.slice();  // 创建共享数据的子缓冲区
processHeader(header);

// 处理剩余数据
buffer.position(10);
buffer.limit(buffer.capacity());
ByteBuffer body = buffer.slice();
processBody(body);

八、内存管理和性能提示

  1. 直接缓冲区 vs 堆缓冲区

    • 直接缓冲区:I/O操作更快,适合网络编程

    • 堆缓冲区:创建和回收更快,适合小数据

  2. 避免频繁分配

    • 使用clear()compact()重用缓冲区

    • 对于已知最大消息大小,预分配足够缓冲区

  3. 批量操作

    • 使用get(byte[])比循环get()更高效

    • 使用slice()避免数据复制

九、常见陷阱

  1. 忘记flip():写入后直接读取会得到空数据

  2. position超过limit:会导致BufferUnderflowException

  3. 直接缓冲区无array():调用array()会抛出UnsupportedOperationException

  4. 未检查remaining():可能导致读取不完整数据

这些方法在您的协议解析代码中都非常重要,特别是compact()slice()remaining()等方法对于处理网络数据流中的拆包粘包问题至关重要。


评论