前言
ByteBuffer 是 Java nio 包中 Buffer 的一个 子类抽象类
也是学习 Buffer 中的一个核心、重点
简介
在 nio 编程中,Channel 管道的数据是要写到 Buffer 中,才可以被程序操作的,由于 Channel 没有方向性,不像输入流输出流一样,有明确的方向性,Buffer 为了区分读写,引入了读模式、写模式进行区分
其实 Buffer 有多种
-
ByteBuffer
-
CharBuffer
-
DoubleBuffer
-
FloatBuffer
-
IntBuffer
-
LongBuffer
-
ShortBuffer
-
MappedByteBuffer..
但是 ByteBuffer 和 MappedByteBuffer 才是其中的核心,因为在网络传输中,我们实际中的就是字节形式进行传输
ByteBuffer 主要实现类
- HeapByteBuffer
-
- 占用的 jvm 中的堆内存
- 读写操作 效率低 会收到GC影响
- MappedByteBuffer(DirectByteBuffer)
-
- 占用的操作系统的内存
- 读写操作 效率高 不会收到GC影响
- 不主动析构,会造成内存的泄露
获取 ByteBuffer 的方式
- ByteBuffer.allocate(10);//一旦分配空间,不可以动态调整
- encode()
ByteBuffer 的核心结构
ByteBuffer是一个类似数组的结构,整个结构中包含三个主要的状态
- Capacity :buffer的容量,类似于数组的size
- Position :buffer当前缓存的下标,在读取操作时记录读到了那个位置,在写操作时记录写到了那个位置。从0开始,每读取一次,下标+1
- Limit :读写限制,在读操作时,设置了你能读多少字节的数据,在写操作时,设置了你还能写多少字节的数据
所谓的读写模式,本质上就是这几个状态的变化。主要有Position和Limit联合决定了Buffer的读写数据区域。
以下图片来自抖音 up 主孙哥
Buffer 中可以看到 这三个几个属性
public abstract class Buffer {
/**
* The characteristics of Spliterators that traverse and split elements
* maintained in Buffers.
*/
static final int SPLITERATOR_CHARACTERISTICS =
Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.ORDERED;
// Invariants: mark <= position <= limit <= capacity
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;
// Used only by direct buffers
// NOTE: hoisted here for speed in JNI GetDirectBufferAddress
long address;
结合上面的图片食用,实际上 nio 开发中,针对 Buffer 说的所谓的读写模式,本质上就是这几个属性的值发生了变化,这几个值变成了,让你可以读、让你可以写的状态
核心 API
字节操作
将数据写入 buffer
- channel 的 read 方法
// 将管道中的数据写入到缓冲区中
channel.read(buffer)
- buffer的 put 方法
buffer.put(byte) buffer.put((byte)'a')..
buffer.put(byte[])
从 buffer 中读数据
- channel的write方法
// 将缓冲区的字节读取到管道
channel.write(buffer);
- buffer的get方法
// 每次调用会影响 position 的位置
// 从 buffer 中获取一个字节
byte b = buffer.get();
- buffer 的 rewind方法
// 可以将postion重置成0 ,用于复读数据。
buffer.rewind();
// 以下片段来自源码
/**
* Rewinds this buffer. The position is set to zero and the mark is
* discarded.
*
* <p> Invoke this method before a sequence of channel-write or <i>get</i>
* operations, assuming that the limit has already been set
* appropriately. For example:
*
* <blockquote><pre>
* out.write(buf); // Write remaining data
* buf.rewind(); // Rewind buffer
* buf.get(array); // Copy data into array</pre></blockquote>
*
* @return This buffer
*/
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
- mark&reset方法,通过mark方法进行标记(position),通过reset方法跳回标记,从新执行.
buffer.mark();
// 源码
public final Buffer mark() {
mark = position;
return this;
}
buffer.reset();
// 源码
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
- get(index) 方法
// 获取特定position上的数据,但是不会对position的位置产生影响。
buffer.get(0);
// 源码
// HeapByteBuffer
public byte get(int i) {
return hb[ix(checkIndex(i))];
}
// DirectByteBuffer
public byte get(int i) {
return ((unsafe.getByte(ix(checkIndex(i)))));
}
字符串操作
字符串操作中,需要设置编码,如果不自定义编码的话,程序会调用系统默认编码,mac 是默认 utf-8,win 是默认 GBK。如果在 win 系统下,对字符串操作时,如果其中包含了汉字,那么就会有问题
将字符串写到 Buffer 中
- nio 中的 Charset 工具类
ByteBuffer buffer = Charset.forName("UTF-8").encode("yzy");
- nio 中的 StandardCharsets 类
ByteBuffer buffer = StandardCharsets.UTF_8.encode("yzy");
- ByteBuffer.wrap 方法
ByteBuffer buffer = ByteBuffer.wrap("yzy".getBytes());
- ByteBuffer 的 put 方法
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put("yzy".getBytes());
Buffer 中的数据转换成字符串
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put("杨".getBytes());
buffer.flip();
CharBuffer result = StandardCharsets.UTF_8.decode(buffer);
System.out.println("result.toString() = " + result.toString());