【NIO】3.2 ByteBuffer的原理-AI参考

NIO 中的 Buffer(缓冲区) 是 Java NIO 的核心组件之一,它是一个用于存储特定基本类型数据的容器。所有 NIO 的 I/O 操作都是通过 Buffer 来进行的------数据总是从 Channel 读入 Buffer,或从 Buffer 写入 Channel。

一、Buffer 的基本类型

Java NIO 提供了 7 种主要的 Buffer 实现,对应 7 种基本数据类型(除了 boolean):

类型 说明
ByteBuffer 最常用,其他类型可通过它转换
CharBuffer 字符缓冲区
ShortBuffer 短整型缓冲区
IntBuffer 整型缓冲区
LongBuffer 长整型缓冲区
FloatBuffer 浮点缓冲区
DoubleBuffer 双精度缓冲区

其中 ByteBuffer 是最核心的,因为网络和文件 I/O 的底层都是字节流。

二、Buffer 的四个核心属性

理解 Buffer 的关键在于掌握它的四个属性:

java 复制代码
public abstract class Buffer {
    private int mark = -1;      // 标记位置
    private int position = 0;   // 当前位置
    private int limit;          // 限制位置
    private int capacity;       // 容量
}

1. capacity(容量)

  • Buffer 能容纳的最大数据量,创建后不可改变。

2. position(位置)

  • 下一个要被读或写的元素的索引。
  • 写模式下:写入数据的位置。
  • 读模式下:读取数据的位置。

3. limit(限制)

  • 缓冲区中可读/可写的边界,不能超过 limit。
  • 写模式:limit = capacity。
  • 读模式:limit = 之前写入的数据量。

4. mark(标记)

  • 一个临时记录的位置,通过 mark() 设置,reset() 恢复到该位置。

关系恒成立: 0 <= mark <= position <= limit <= capacity

三、核心方法

1. 读写操作

java 复制代码
// 写数据:position 递增
buffer.put((byte) 1);

// 读数据:position 递增
byte b = buffer.get();

2. flip() ------ 写模式切换为读模式

java 复制代码
public final Buffer flip() {
    limit = position;   // limit 设为已写入的数据量
    position = 0;       // position 归零
    mark = -1;
    return this;
}

这是最容易出错 的地方:写完数据要读之前,必须调用 flip()。

3. clear() ------ 清空缓冲区

java 复制代码
public final Buffer clear() {
    position = 0;
    limit = capacity;
    mark = -1;
    return this;
}

注意:clear() 并不真正清除数据,只是重置指针,数据会被后续写入覆盖。

4. compact() ------ 保留未读数据

java 复制代码
public abstract ByteBuffer compact();

将未读的数据复制到缓冲区起始处,然后切换到写模式。用于读取部分数据后继续写入的场景。

5. rewind() ------ 重读

java 复制代码
public final Buffer rewind() {
    position = 0;
    mark = -1;
    return this;
}

将 position 归零,可以重新读取已读过的数据,limit 不变。

6. mark() 和 reset()

java 复制代码
buffer.mark();      // 记录当前 position
buffer.reset();     // 恢复到 mark 的位置

四、状态转换示意图

复制代码
初始状态(刚创建):
  0                    capacity
  |                       |
  position=0, limit=capacity

写入 3 个字节后:
  0  1  2  3              capacity
  |        |                 |
          position=3, limit=capacity

调用 flip() 后(切换到读模式):
  0  1  2  3
  |        |
  position=0, limit=3

读取 2 个字节后:
  0  1  2  3
     |     |
   position=2, limit=3

调用 clear() 后(回到写模式):
  0  1  2  3              capacity
  |                          |
  position=0, limit=capacity

五、使用示例

java 复制代码
public class BufferDemo {
    public static void main(String[] args) {
        // 创建容量为 10 的 ByteBuffer
        ByteBuffer buffer = ByteBuffer.allocate(10);
        
        // 写模式
        buffer.put((byte) 'H');
        buffer.put((byte) 'e');
        buffer.put((byte) 'l');
        buffer.put((byte) 'l');
        buffer.put((byte) 'o');
        
        // 切换到读模式
        buffer.flip();
        
        // 读取数据
        while (buffer.hasRemaining()) {
            System.out.print((char) buffer.get());
        }
        // 输出: Hello
        
        // 清空,准备下次写入
        buffer.clear();
    }
}

六、allocate vs allocateDirect

java 复制代码
// 1. 堆内存缓冲区(JVM 堆中)
ByteBuffer heapBuffer = ByteBuffer.allocate(1024);

// 2. 直接内存缓冲区(操作系统内存,零拷贝)
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);
对比项 allocate(堆) allocateDirect(直接)
内存位置 JVM 堆 堆外(OS 内存)
分配/回收速度 快 慢
I/O 性能 较低(需复制) 高(零拷贝)
适用场景 普通场景 高频 I/O、大文件

七、其他常用方法

java 复制代码
buffer.remaining();       // limit - position,剩余可读/可写数量
buffer.hasRemaining();    // 是否还有剩余
buffer.rewind();          // 重置 position 为 0,可重读
buffer.slice();           // 创建一个共享数据的子缓冲区
buffer.duplicate();       // 复制一个缓冲区(共享数据,独立属性)
buffer.asReadOnlyBuffer();// 创建只读缓冲区

八、常见误区

  1. 忘记 flip():写完直接读,会读到 position 之后的位置,导致读不到数据。
  2. 混淆 clear() 和真正清空 :clear() 只重置指针,不擦除内容。
  3. 读完后直接 clear() 导致未读数据丢失 :应使用 compact() 保留未读数据。
  4. 多次 flip():flip 只能调一次,第二次调用会把 limit 设为 0。

九、总结

Buffer 是 NIO 数据读写的桥梁,核心在于四个属性的流转:

  • 写数据 → position 增加
  • flip() → 切换到读模式
  • 读数据 → position 增加
  • clear()/compact() → 切换回写模式

掌握 position、limit、capacity 三者的变化,就掌握了 Buffer 的本质。

相关推荐
那个松鼠很眼熟w2 小时前
【NIO】5. FileChannel的基本用法
nio
那个松鼠很眼熟w3 小时前
【NIO】2. ByteBuffer的基本使用
nio
今天的砖头有点烫手啊5 天前
Java IO/NIO/AIO 演进:从 BIO 到 Netty 的底层逻辑
java·jvm·nio
名字还没想好☜23 天前
Java NIO ByteBuffer 实战:flip/clear/compact 三个绕晕人的方法与 position/limit 心智模型
java·开发语言·后端·spring·nio
滕州市燕猫虎计算机科技工作室个体工商户23 天前
java.nio.ByteBuffer的几个常用方法
nio
CodeStats1 个月前
【Java文件IO】Java 文件 IO 体系深度拆解(中):NIO API 详解、Files 与 FileChannel 巅峰对决
java·io·文件·nio·零拷贝·mmap
evans在进步1 个月前
Tomcat NIO 工作原理详解:从 Acceptor、Poller 到容器处理链
java·tomcat·nio
二炮手亮子1 个月前
从 BIO、NIO、AIO 到多路复用与 Netty
策略模式·nio
Bill FANG2 个月前
从原生Java NIO到Netty重构:充电桩Socket服务端高性能改造实战
java·重构·nio