Java NIO---缓冲区

先看ByteBuffer的父类Buffer

Buffer(java.nio)

成员

1
2
3
4
5
// Invariants: mark <= position <= limit <= capacity
private int mark = -1;//若设置,则reset时postion变为mark
private int position = 0;//下一个可读写的位置
private int limit;//第一个不能读写的位置
private int capacity;//该Buffer包含元素的数量

该类的每个子类定义了两种类型的读(get)和写(put)操作,相对的和绝对的。
相对的读写从当前position开始,然后相应的增加position的值。相对读超出limit值时,抛出BufferUnderflowException异常,而相对写超出limit时,抛出BufferOverflowException异常。
绝对的读写直接在输入参数中指定要读写的位置,且不改变position的值,超出limit时抛出IndexOutOfBoundsException异常。
另外,通过Channel(NIO另一个特性)进行读写时,总是相对position操作。

主要方法

位置读取相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public final int capacity() { return capacity; }//获取容量
public final int position() { return position; }//获取下一个读取位置
public final Buffer position(int newPosition) {//设置下一个读取位置
if ((newPosition > limit) || (newPosition < 0))
throw new IllegalArgumentException();
position = newPosition;
if (mark > position) mark = -1;//position小于mark时,重置mark为未定义
return this;
}
public final int limit() { return limit; }//获取limit
public final Buffer limit(int newLimit) {//设置limit
if ((newLimit > capacity) || (newLimit < 0))
throw new IllegalArgumentException();
limit = newLimit;
if (position > limit) position = limit;//position>limit,重置position为limit
if (mark > limit) mark = -1;//mark大于limit的,重置mark为未定义
return this;
}
public final Buffer mark() {//标记当前位置
mark = position;
return this;
}
public final Buffer reset() {//重置position到mark标记处
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}

缓冲区读写设置相关

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

清除缓冲区,position置0,limit置capacity,mark未定义。为put操作或者从通道中读做准备,缓冲区内的数据不清除。

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

limit置为当前position值,然后position置0,mark未定义。为get操作或者向通道中写数据做准备。

1
2
3
4
5
6
7
8
9
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
//例如:
out.write(buf); // Write remaining data
buf.rewind(); // Rewind buffer
buf.get(array); // Copy data into array

position置0,mark未定义。为一系列的get操作或向通道中写做准备,重复get缓冲区中的数据。

缓冲区属性相关

1
public abstract boolean isReadOnly();

一个Buffer可以为只读的,抽象方法,子类做相应实现。  

1
2
3
public abstract boolean hasArray();//抽象方法,底层实现
public abstract Object array();
public abstract int arrayOffset();

Buffer底层可以有可访问数组支持,即数组存储实际的数据,hasArray返回true时支持,这时array方法返回底层的数组,arrayOffset返回数组中第一个元素的偏移量。  

1
public abstract boolean isDirect();

返回true时,表明缓冲区分配的时直接内存,而不是虚拟机中的堆内存。

ByteBuffer

1
public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer>

可比较的字节缓冲。该类实现了6种操作:

  • 读写单个字节的相对或绝对位置的get和put操作
  • 相对位置的块get操作(连续字节的读)
  • 相对位置的块put操作
  • 相对或绝对的读写其他本原类型的get和put操作,以一定字节序将他们传输到连续的字节流或从连续的字节流中读取至缓冲区
  • 创建view缓冲区,允许一个字节缓冲区作为一个包含其他本原类型的缓冲区使用
  • compacting,duplicating,slicing一个字节缓冲区  

字节缓冲区可以通过分配创建,也可以通过wrap一个已存在的字节数组创建(这种情况下,改变任意一个另一个的内容将会改变)

缓冲区可分为直接缓冲区或非直接缓冲区。对于直接字节缓冲区来说,Java虚拟机将尽可能执行本地I/O操作,这将减少相应的拷贝操作。  
直接缓冲区可以通过allocateDirect方法完成分配,一般来说,直接缓冲区的分配和释放相对于非直接缓冲区代价更大。直接缓冲位于Java虚拟机堆内存管理之外,对一个应用的内存占用影响不明显,因此推荐直接缓冲主要用在大的,长时间存活的面向底层操作系统的I/O操作的缓冲区。通常仅仅在分配直接缓冲能够对程序性能有显著增益的场合下。直接缓冲也可以通过将一个文件直接映射到内存中。  
缓冲区是否是直接缓冲,通过isDirect方法表现。

该类定义了很多对于除了boolean之外其他所有本原类型的读写方法,本原值可以根据当前的字节序转换为连续的字节(或将连续的字节转换为本原值)。当前字节序可以通过order方法获取或修改,字节序为ByteOrder类,默认为ByteOrder.BIG_ENDIAN。可以通过getFloat,putFloat等方法或者将本字节缓冲通过asFloatBuffer转换为一个FloatBuffer的view缓冲进行操作。  

本类成员

1
2
3
4
5
final byte[] hb;                  // Non-null only for heap buffers
final int offset;
boolean isReadOnly; // Valid only for heap buffers
boolean bigEndian = true; // package-private
boolean nativeByteOrder = (Bits.byteOrder() == ByteOrder.BIG_ENDIAN);// package-private

还有继承自Buffer的成员。

主要方法

分配

  • allocation

    1
    2
    3
    4
    5
    6
    7
    8
    public static ByteBuffer allocateDirect(int capacity) {//分配直接内存缓冲
    return new DirectByteBuffer(capacity);
    }
    public static ByteBuffer allocate(int capacity) {//分配Java虚拟机堆内存缓冲
    if (capacity < 0)
    throw new IllegalArgumentException();
    return new HeapByteBuffer(capacity, capacity);
    }
  • wrap

    1
    2
    3
    4
    5
    6
    7
    8
    public static ByteBuffer wrap(byte[] array, int offset, int length){
    try {
    return new HeapByteBuffer(array, offset, length);
    } catch (IllegalArgumentException x) {
    throw new IndexOutOfBoundsException();
    }
    }//通过存在的字节数组分配Java虚拟机堆内存缓冲
    public static ByteBuffer wrap(byte[] array) { return wrap(array, 0, array.length); }

    wrap方法给定的字节数组array作为缓冲区的底层数组,修改缓冲区内容将同时修改array的内容,反之亦然。新缓冲区的容量为array.length,position为offset,limit为offset+length,mark未定义。  

  • slice

    1
    public abstract ByteBuffer slice();

    创建一个共享原Buffer内容子序列的新缓冲。新缓冲的position为0,limit和capacity为原来缓冲的remaining值,mark值未定义。两个缓冲区的position,limit和mark值彼此独立,不过两者任意一个改变缓冲区内容时,另一个缓冲区内容也会改变。
    新缓冲区当且仅当旧缓冲区是直接缓冲时才是直接缓冲区,当且仅当旧缓冲区是只读的时才是只读缓冲区。

  • duplicate

    1
    public abstract ByteBuffer duplicate();

    复制旧缓冲区为新缓冲区,新缓冲区的position,limit,mark,capacity与旧缓冲区一样,分配后彼此独立,改变任意一个缓冲区的内容另一个缓冲区内容跟着改变。当且仅当旧缓冲区为直接缓冲区时,新缓冲区为直接缓冲区。当且仅当旧缓冲区为只读缓冲时,新缓冲区为只读缓冲区。

  • asReadOnlyBuffer

    1
    public abstract ByteBuffer asReadOnlyBuffer();

    创建一个只读的,共享原缓冲区内容的新缓冲区。新缓冲区的position,limit,mark,capacity与旧缓冲区一样,分配后彼此独立。旧缓冲区内容改变时,新缓冲区的内容也跟着改变,不过新缓冲区不允许修改其中的内容。

  • compact

    1
    public abstract ByteBuffer compact();

    严格意义不算分配新Buffer,position~limit之间的数据拷贝至开始处(0~remaining),拷贝后position的值为原来的remaining。
    这样是为了腾出position之前的空间。

读写操作

  • 基本读写

    1
    2
    3
    4
    public abstract byte get();//相对读
    public abstract ByteBuffer put(byte b);//相对写
    public abstract byte get(int index);//绝对读
    public abstract ByteBuffer put(int index, byte b);//绝对写
  • 块数据的读写

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    public ByteBuffer get(byte[] dst, int offset, int length) {//块get操作
    checkBounds(offset, length, dst.length);
    if (length > remaining())
    throw new BufferUnderflowException();
    int end = offset + length;
    for (int i = offset; i < end; i++)
    dst[i] = get();
    return this;
    }
    public ByteBuffer get(byte[] dst) { return get(dst, 0, dst.length); }

    public ByteBuffer put(ByteBuffer src) {//块数据拷贝
    if (src == this)
    throw new IllegalArgumentException();
    if (isReadOnly())
    throw new ReadOnlyBufferException();
    int n = src.remaining();
    if (n > remaining())
    throw new BufferOverflowException();
    for (int i = 0; i < n; i++)
    put(src.get());
    return this;
    }
    public ByteBuffer put(byte[] src, int offset, int length) {//块put操作
    checkBounds(offset, length, src.length);
    if (length > remaining())
    throw new BufferOverflowException();
    int end = offset + length;
    for (int i = offset; i < end; i++)
    this.put(src[i]);
    return this;
    }
    public final ByteBuffer put(byte[] src) { return put(src, 0, src.length); }

属性相关

1
2
3
4
5
6
7
8
public abstract boolean isDirect();//是否为直接内存缓冲
public int hashCode() {//hash码
int h = 1;
int p = position();
for (int i = limit() - 1; i >= p; i--)
h = 31 * h + (int)get(i);
return h;
}

其他本原类型访问

  • 字节序

    1
    2
    3
    4
    5
    6
    7
    8
    public final ByteOrder order() {//使用的字节序
    return bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN;
    }

    public final ByteBuffer order(ByteOrder bo) {//设置字节序
    bigEndian = (bo == ByteOrder.BIG_ENDIAN);
    nativeByteOrder = (bigEndian == (Bits.byteOrder() == ByteOrder.BIG_ENDIAN));
    return this;

    }
  • get*或set*方法访问其他本原类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public abstract char getChar();//读取两个字节转换为char
    public abstract ByteBuffer putChar(char value);//将value转换为两个字节数据写入
    public abstract char getChar(int index);
    public abstract ByteBuffer putChar(int index, char value);
    public abstract short getShort();//读取两个字节转换为short
    ...//同上char的另外三种情况
    public abstract int getInt();//读取4个字节转换为int
    ...//同上
    public abstract long getLong();//读取8个字节转换为long
    ...//同上
    public abstract float getFloat();//读取4个字节转换为float
    ...//同上
    public abstract double getDouble();//读取8个字节转换为double
    ...//同上
  • view缓冲区访问其他本原类型

    1
    2
    3
    4
    5
    6
    public abstract CharBuffer asCharBuffer();//字节缓冲的字符缓冲
    public abstract ShortBuffer asShortBuffer();
    public abstract IntBuffer asIntBuffer();
    public abstract LongBuffer asLongBuffer();
    public abstract FloatBuffer asFloatBuffer();
    public abstract DoubleBuffer asDoubleBuffer();

比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public int compareTo(ByteBuffer that) {//一般比较器会调用该方法
//依次比较两个缓冲区中每一个字节
int n = this.position() + Math.min(this.remaining(), that.remaining());
for (int i = this.position(), j = that.position(); i < n; i++, j++) {
int cmp = compare(this.get(i), that.get(j));
if (cmp != 0)
return cmp;
}
return this.remaining() - that.remaining();
}

private static int compare(byte x, byte y) {
return Byte.compare(x, y);
}

其他Buffer,IntBuffer,FloatBuffer等

大致一样,只不过操作的单位不一样,可参照ByteBuffer中的view Buffer原理,对IntBuffer来说一次读写对应四个字节,比较的时候也是比较一个Int的值。