Java Buffer

缓冲体系

体系


1
2
3
4
5
6
7
8
9
10
11
12
13
14
Buffer
|-----ByteBuffer
| |-----HeapByteBuffer
| | |-----HeapByteBufferR
| |
| |-----MappedByteBuffer
| |-----DirectByteBuffer
| |-----DirectByteBufferR
|-----CharBuffer
|-----ShortBuffer
|-----IntBuffer
|-----LongBuffer
|-----FloatBuffer
|-----DoubleBuffer

Buffer


Buffer.java
1
2
3
4
5
6
7
8
public abstract class Buffer {
// Invariants: mark <= position <= limit <= capacity
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;
//...
}

Buffer是一个抽象类,类内只维护一组变量,并不维护具体的数据存储,定义了各种逻辑操作

  • position:下一个操作的读写位置
  • mark:标记,可以用来提示已经处理到的位置
  • capacity:最大数据容量
  • limit:读写的上限位置,不应操作的位置索引
    可操作位置[position,limit)
    0 <= mark <= position <= limit <= capacity
    理解上比较直观,读写不会操作到旧数据,也不会超过上限

这一组变量的大小关系需要时刻保持
设置position,如果设的比mark还小,说明设到了历史数据上,那么mark就失效废弃

Buffer.java
1
2
3
4
5
6
public final Buffer position(int newPosition) {
//...
position = newPosition;
if (mark > position) mark = -1;
return this;
}

设置limit,如果position已经越界,那么回退position到limit,如果mark越界直接失效废弃

Buffer.java
1
2
3
4
5
6
7
public final Buffer limit(int newLimit) {
//...
limit = newLimit;
if (position > limit) position = limit;
if (mark > limit) mark = -1;
return this;
}

标记操作

mark只能标记下一位置,不接受参数

Buffer.java
1
2
3
4
public final Buffer mark() {
mark = position;
return this;
}

reset将下一操作位置设为上次标记的位置

Buffer.java
1
2
3
4
5
6
7
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}

重置操作

clear彻底恢复成初始状态,整个空间可用,limit与capacity相等

Buffer.java
1
2
3
4
5
6
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}

flip将缓存从输入模式转成输出模式。经过一系列输入后的position就是读出的上限
典型的使用模式:输入后输出,即先read进来再write出去

Sample
1
2
3
4
buf.put(magic);
in.read(buf);
buf.flip();
out.write(buf);
Buffer.java
1
2
3
4
5
6
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}

rewind重新回到起点,可以重复读出
典型使用模式:输出后再输出一遍

Sample
1
2
3
out.write(buf);
buf.rewind();
buf.get(array);
Buffer.java
1
2
3
4
5
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}

clear,fliprewind都重置了当前位置和标记,不同的地方在于对上限的处理

遍历


hasRemaining可作为标记实现类似迭代器遍历

1
2
3
while(buf.hasRemaining()){
//
}

ByteBuffer


ByteBuffer是Buffer的子类,它还是抽象的,还可以进一步扩展子类

ByteBuffer.java
1
2
3
4
5
6
public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer> {
final byte[] hb;
final int offset;
boolean isReadOnly;
//...
}

字节序

  • Big-Endian: 高位存低地址 0x01020304 => 01 02 03 04,01是高位存在前面,符合人类习惯
  • Little-Endian: 低位存低地址 0x01020304 => 04 03 02 01
    ByteBuffer默认是大端顺序,网络流中也是按照大端序,先收到的是高位

创建

ByteBuffer创建提供了两种静态方法,建出两种不同类型的缓存
DirectBuffer是为channel和native IO设计,可以便于让操作系统直接使用,堆外分配内存不受GC移动位置等影响,保持稳定性和连续性
如果缓存只是用于jvm内部的操作,那么HeapByteBuffer更适用
和堆缓冲相比,直接缓冲贴近底层,读写顺序快,但是创建销毁开销很大

ByteBuffer.java
1
2
3
4
5
6
7
8
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
public static ByteBuffer allocate(int capacity) {
if (capacity < 0)
throw new IllegalArgumentException();
return new HeapByteBuffer(capacity, capacity);
}

堆Buffer还可以通过包装已有字节数组获得

ByteBuffer.java
1
2
3
4
5
6
7
8
9
10
public static ByteBuffer wrap(byte[] array) {
return wrap(array, 0, array.length);
}
public static ByteBuffer wrap(byte[] array,int offset, int length) {
try {
return new HeapByteBuffer(array, offset, length);
} catch (IllegalArgumentException x) {
throw new IndexOutOfBoundsException();
}
}

数据共享

  • duplicate方法,可以共享底层数据,但是单独维护各种操作位置标记,可实现多角度灵活数据处理
  • slice方法,局部共享,作为原来的子数据段,从position开始一直到limit,单独维护标记
  • asReadOnlyBuffer方法,返回只读版本,进行数据保护

压紧

compact操作,将没输出完的长度为n的部分(position和limit之间)复制到缓存的开端,position设为n,limit设为最大的capacity,这样接下来的写入就能直接接在后面,不会覆盖掉没读完的数据

Sample
1
2
3
4
5
6
buf.clear();  // Prepare buffer for use
while (in.read(buf) >= 0 || buf.position != 0) {
buf.flip();
out.write(buf);
buf.compact(); // In case of partial write
}