第23篇:缓冲区数据结构 ByteBuffer
📌 系列导航 :《Java 100 天进阶之路》完整目录 |
⬅️ 上一篇:第22篇:Java字符串简介 |
➡️ 下一篇:第24篇:Java枚举类型 enum 用法
一、核心知识点
- Buffer 抽象类与子类(
ByteBuffer、CharBuffer、IntBuffer等) ByteBuffer核心属性:capacity、limit、position、mark- 核心方法:
put()、get()、flip()、rewind()、clear()、compact() - 直接缓冲区 vs 堆缓冲区
- NIO(New I/O)中的使用场景
二、通俗讲解(1分钟开心学)
1. 什么是 Buffer?
Buffer 是 Java NIO 中用于存储数据的容器,可以理解为一块可以读写的内存区域,并带有一套指针系统来管理读写位置。最常用的是 ByteBuffer。
2. 四个核心属性
capacity:容量,创建后不可变。limit:读写的极限位置。position:下一个要读/写的位置索引。mark:标记位置,可用reset()返回。
3. 两种模式
- 写模式 :刚创建时,
position = 0,limit = capacity,往里面put数据。 - 读模式 :调用
flip()后,limit = position(原写到的位置),position = 0,然后get数据。
生活类比 :
缓冲区就像一个水池。
capacity是水池总容量。你在水池里倒水(写),position是水面高度。倒完后,你标记一下现在的水位(flip),然后开始从底部取水(读)。取水时不能超过之前的水位(limit)。
4. 直接缓冲区 vs 堆缓冲区
- 堆缓冲区 (
allocate()):内存分配在 JVM 堆上,受 GC 管理,数据拷贝到 native 内存时多一次复制。 - 直接缓冲区 (
allocateDirect()):内存分配在系统本机内存(native heap),减少一次拷贝,适合大文件、网络传输,但分配和释放成本高。
三、实操代码案例 + 场景说明
场景 :使用
ByteBuffer实现一个简单的"写数据 → 读数据"流程。
java
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
public class ByteBufferDemo {
public static void main(String[] args) {
// 1. 创建堆缓冲区,容量10
ByteBuffer buffer = ByteBuffer.allocate(10);
System.out.println("初始: pos=" + buffer.position() + ", limit=" + buffer.limit());
// 2. 写入数据
buffer.put((byte) 'H');
buffer.put((byte) 'i');
buffer.put((byte) '!');
System.out.println("写入后: pos=" + buffer.position()); // 3
// 3. 切换到读模式
buffer.flip();
System.out.println("flip后: pos=" + buffer.position() + ", limit=" + buffer.limit()); // pos=0, limit=3
// 4. 读取数据
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
System.out.println(); // 输出 "Hi!"
// 5. rewind:重新读
buffer.rewind();
System.out.println("rewind后 pos=" + buffer.position()); // 0
// 6. clear:清空状态,准备重新写
buffer.clear();
System.out.println("clear后 pos=" + buffer.position() + ", limit=" + buffer.limit()); // pos=0, limit=10
// 7. 直接缓冲区示例
ByteBuffer directBuf = ByteBuffer.allocateDirect(1024);
if (directBuf.isDirect()) {
System.out.println("这是直接缓冲区,适合网络传输");
}
// 8. 字符串与 ByteBuffer 互转
String msg = "Hello NIO";
ByteBuffer buf = ByteBuffer.wrap(msg.getBytes(StandardCharsets.UTF_8));
// 读取时
byte[] bytes = new byte[buf.remaining()];
buf.get(bytes);
String decoded = new String(bytes, StandardCharsets.UTF_8);
System.out.println(decoded);
}
}
四、避坑要点
| 错误/误区 | 后果 | 正确做法 |
|---|---|---|
写入后忘记 flip() 直接读取 |
读不到数据(position 在末尾) | 写后读前必须 flip() |
flip() 多次调用 |
limit 被错误设置 |
只在切换模式时调用一次 |
| 直接缓冲区频繁分配释放 | 性能反而下降 | 复用缓冲区或使用池化技术 |
使用 get() 前不判断 hasRemaining() |
可能抛出 BufferUnderflowException |
先判断或捕获异常 |
五、面试高频考点
Q1:ByteBuffer 的 flip() 方法做了什么?
将
limit设为当前position,position归零,为从写模式切换到读模式做准备。
Q2:直接缓冲区的优缺点?
优点:减少 JVM 堆和 native 堆之间的数据拷贝,提高 I/O 性能,适合大文件、网络。缺点:分配和释放成本高,不受 JVM 堆大小限制但受物理内存限制。
Q3:clear() 和 compact() 的区别?
clear()重置position=0, limit=capacity,丢弃未读数据;compact()将未读数据复制到缓冲区头部,position移到未读数据后,limit=capacity,为写入留出空间。
六、练习题
- 代码填空:完成如下操作:写入字符串 "Java" 的字节,然后读取并打印。
- 简答:什么时候使用直接缓冲区?什么时候使用堆缓冲区?
- 动手 :用
ByteBuffer和FileChannel实现文件拷贝(使用transferTo或read/write)。
📊 你的学习进度
- 当前:第23篇 / 共44篇 · 第三阶段:字符串、Buffer、枚举、类加载(第21~25篇)
- ✅ 已完成:第1~22篇
- 📖 正在学:第23篇
- ⏳ 待学习:第24~44篇
👉 📚 完整目录 & 学习指南 | 🔥 订阅本专栏,不错过每一篇
💡 本专栏每篇都包含:避坑表 + 面试高频考点 + 练习题。每天30分钟,100天拿offer!
下一篇文章预告
《Java枚举类型 enum 用法》
内容简介:枚举的定义、构造器、成员变量和方法,values()/valueOf()/ordinal(),枚举单例模式。
💡 学完这篇,你将掌握枚举的全部用法,并用枚举写出更安全的代码。
📌 《Java 100 天进阶之路 | 从入门到上岗就业》 每天一篇,建议收藏 + 关注 ,一起100天拿offer!
👉 点击关注我,更新后第一时间收到推送!