<p align="right">-- 日拱一卒, 不期而至!</p>
你好, 我是彤哥, 本篇是 netty 系列的第六篇.
简介
上一章我们一起学习了 Java NIO 的核心组件 Channel, 它可以看作是实体与实体之间的连接, 而且需要与 Buffer 交互, 这一章我们就来学习一下 Buffer 的特性.
概念
Buffer 用于与 Channel 交互时使用, 通过上一章的学习我们知道, 数据从 Channel 读取到 Buffer, 或者从 Buffer 写入 Channel.
Buffer 本质上是一个内存块, 可以向里面写入数据, 或者从里面读取数据, 在 Java 中它被包装成了 Buffer 对象, 并提供了一系列的方法用于操作这个内存块.
属性
为了更好地理解 Buffer 的数据结构, 我们必须熟悉它的三个常用属性:
capacity: 容量
position: 当前位置
limit: 限制长度
在读模式和写模式下, position 和 limit 的位置有所不同, 见下图:
capacity
Buffer 作为一个存储块, 是有固定大小的, 这个固定大小我们称作 "容量".
当 Buffer 写满之后, 需要先清空或者读取数据, 才能继续写入新的数据.
position
写模式下, position 从 0 开始, 每写入一个单位的数据, position 前进一位, position 最大可到达 (capacity-1) 的位置.
当 Buffer 从写模式切换为读模式时, position 将重置为 0. 读取数据时, 同样地, position 每读取一个单位, 前进一位, 此时, position 最大可到达 limit 的位置(实际最大可读取的位置是(limit-1)).
limit
写模式下, limit 最大值等于 capacity.
读模式下, limit 最大值等于切换为读模式时 position 的值, 本文来源工从号彤哥读源码.
这里可能有点绕, position 类似于数组的下标, 是从 0 开始的, limit 表示最大可以读取或者写入的长度, capacity 表示最大的容量, limit 和 capacity 不是下标, 类似于数组的长度, 所以跟 position 比较需要 - 1. 在写模式下, position 指向的是下一个待写入的位置; 在读模式下, position 指向的是下一个待读取的位置.
类型
Java NIO 自带的 Buffer 类型有:
- ByteBuffer
- MappedByteBuffer
- CharBuffer
- ShortBuffer
- IntBuffer
- LongBuffer
- FloatBuffer
- DoubleBuffer
与基本类型一样, 每一种 Buffer 的基本单位长度不一样罢了.
其中, MappedByteBuffer 是一种特殊的 ByteBuffer, 它使用内存映射的方式加载物理文件, 并不会耗费同等大小的物理内存, 是一种直接操作堆外内存的方式, 读写性能比较高.
基本用法
上面我们学习了 Buffer 的数据结构以及常用的 Buffer 类型, 它们怎么使用呢? 常见的用法主要有四种:
将数据写入 Buffer
切换为读模式 flip()
从 Buffer 中读取数据
清空数据并切换为写模式 clear()或者 compact()
来个栗子
- public class FileChannelTest {
- public static void main(String[] args) throws IOException {
- // 从文件获取一个 FileChannel
- FileChannel fileChannel = new RandomAccessFile("D:\\object.txt", "rw").getChannel();
- // 分配一个 Byte 类型的 Buffer
- ByteBuffer buffer = ByteBuffer.allocate(1024);
- // 将 FileChannel 中的数据读出到 buffer 中,-1 表示读取完毕
- // buffer 默认为写模式, 本文来源工从号彤哥读源码
- // read()方法是相对 channel 而言的, 相对 buffer 就是写
- while ((fileChannel.read(buffer)) != -1) {
- // buffer 切换为读模式
- buffer.flip();
- // buffer 中是否有未读数据
- while (buffer.hasRemaining()) {
- // 读取数据
- System.out.print((char)buffer.get());
- }
- // 清空 buffer, 为下一次写入数据做准备
- // clear()会将 buffer 再次切换为写模式
- buffer.clear();
- }
- }
- }
- allocate()
要获取一个 Buffer 对象, 必须先分配它, 每个 Buffer 类都有一个 allocate()方法用于分配 Buffer 对象.
以下示例分配了一个容量为 1024 的 ByteBuffer 对象:
ByteBuffer buffer = ByteBuffer.allocate(1024);
下面是分配了一个容量为 48 的 CharBuffer 的对象:
CharBuffer buf = CharBuffer.allocate(48);
将数据写入 Buffer
将数据写入 Buffer 有两种形式:
从 Channel 读出数据并写入 Buffer, 也叫从 Channel 读入 Buffer
调用 Buffer 自己的 put()方法写入数据
从 Channel 读入 Buffer 的示例如下:
int bytesRead = inChannel.read(buf); // 读入 Buffer
Buffer 自己 put()写入数据的示例如下:
buf.put(127);
当然, put()有很多不同的类型, 比如在特定位置写入, 写入不同类型的数据等等, 可以在 IDEA 中按 F12 查看.
flip()
flip()方法用于将 Buffer 从写模式切换为读模式, position 将切换到 0 位置, 且 limit 将切换到刚才 position 的位置.
也就是说, position 变成了可读数据的首位, limit 表示可以读取的最大数据长度.
从 Buffer 中读取数据
从 Buffer 中读取数据也有两种形式:
从 Buffer 读取数据, 并写入 Channel, 也叫作从 Buffer 写入 Channel
调用 Buffer 自己的 get()方法读取数据
从 Buffer 写入 Channel 的示例如下:
- // 本文来源工从号彤哥读源码
- int bytesWritten = inChannel.write(buf);
调用 Buffer 自己的 get()方法读取数据的示例如下:
byte aByte = buf.get();
当然, get()有很多不同的类型, 比如从特定的位置读取, 读取不同类型的数据等等, 可以在 IDEA 中按 F12 查看.
rewind()
rewind()方法会重置 position 为 0, 但 limit 保持不变, 因此可以用来重新读取数据. 通常是在重新读取数据之前调用.
clear()
clear()方法用于清空整个 Buffer, 并将 Buffer 从读模式切换回写模式, 且 position 归位到 0 位置.
compact()
compact()方法用于清空已读取的数据, 并将未读取的数据移至 Buffer 的头部, position 的位置移动到从头开始计算的未读取的数据的下一个位置, 它也会将 Buffer 从读模式切换回写模式.
mark() 和 reset()
mark()方法用于标记给定位置, 然后可以在之后通过 reset()方法重新回到 mark 的位置, 示例如下:
- buffer.mark();
- // 多次调用 buffer.get(), 例如在解析过程中.
- buffer.reset(); // 将位置重新设置为标记.
总结
今天我们学习了 Java NIO 核心组件 Buffer, 它经常跟 Channel 联合起来使用. 讲到这里我们一直在使用 FileChannel 在举例, 那么它们到底跟网络编程有什么关系呢? 请听下回分解.
参考
http://tutorials.jenkov.com/java-nio/channels.html
最后, 也欢迎来我的工从号 彤哥读源码 系统地学习 源码 & 架构 的知识.
来源: http://www.tuicool.com/articles/6zYBRjf