因为使用通信框架不同的缘故,bytebuffer这种东西在每个框架中都有可能是不同的,比如在Mina中叫IoBuffer,在Netty中叫ByteBuf,虽然叫法不同,但其实用法相似。
有时候为了方便,就直接使用java内置的ByteBuffer了。所以了解ByteBuffer的使用,触类旁通也会变得很容易了。
在一些容易混淆的问题产生之前,先复习一下常用场景下的基本使用。
创建:
public static void main(String[] args) {
byte[] bytes = new byte[10];
for (int i = 0; i < 10; i++) {
bytes[i] = (byte) i;
}
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
}
在实际场景中,wrap的使用可能比较频繁一些,直接传入byte数组即可,如果需要分次传入内容的话,就需要使用allocate先创建一个bytebuffer然后使用put方法写入内容。wrap和allocate相比,好处就在于wrap之后positon的位置是已经重置过了的,直接可以用来读,而不需要使用flip来重置position。
输出:
System.out.println(Arrays.toString(byteBuffer.array()));
可以使用array()输出,但需要注意的是,array()输出的永远是全部内容,也就是bytebuffer中一开始传入的byte数组是什么,输出的就是什么。
读写:
for (int i = 0; i < 10; i++) {
byteBuffer.put((byte) i);
}
byteBuffer.flip();
for (int i = 0; i < 10; i++) {
byteBuffer.get((byte) i);
}
byteBuffer.putLong(2L);
byteBuffer.flip();
byteBuffer.getLong();
读写的话,对应的各种put/get方法,需要注意的就是position所代表的意义就行。每一次读写操作,都会影响到position,position是一个读写操作的当前有效起始位置,可以说是bytebuffer中最需要关注的一个变量。
reset/rewind/clear
reset方法用于重置position为mark值,通常和mark()方法一起用。
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
比如这样:
private static void testMark(ByteBuffer byteBuffer){
byteBuffer.get();
byteBuffer.mark();
byteBuffer.get();
System.out.println("remain:"+byteBuffer.remaining());
byte[] remain1 = new byte[byteBuffer.remaining()];
byteBuffer.get(remain1);
System.out.println(byteBuffer.toString());
System.out.println(Arrays.toString(remain1));
System.out.println();
byteBuffer.reset();
System.out.println("remain:"+byteBuffer.remaining());
byte[] remain2 = new byte[byteBuffer.remaining()];
byteBuffer.get(remain2);
System.out.println(byteBuffer.toString());
System.out.println(Arrays.toString(remain2));
}
输出:
remain:8
java.nio.HeapByteBuffer[pos=2 lim=10 cap=10]
[2, 3, 4, 5, 6, 7, 8, 9]
remain:9
java.nio.HeapByteBuffer[pos=1 lim=10 cap=10]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
mark方法就是标记一个位置,使得reset之后,读写操作又可以从这个位置开始。
而rewind就不管这些了,统统从0开始。
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
rewind本身的意思就是“倒带,重来”的意思,相比之下,比reset更像“重置”。
clear则更进一步,将limit也重置为原始值也就是capacity值。
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
从这几个方法也可以看出所谓的mark<=position<=limit<=capacity是怎么来的。
flip
flip相比之下有些特殊,它通常用于将要开始读写的状态之前,比如一个bytebuffer刚刚写入了一些内容,这时候position还在最大值,也就是写入内容的最后一位,此时开始读内容肯定是不可以的,因为在这个position之后是没有内容可读的了。
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
此时想要进行读操作,就必须把position重置,这也是flip常常在读操作或写操作完毕后使用的缘故,更多地是代表着一种“进入下一状态”的意思。
position
作为一个“游标”,只听说它会变,至于怎么变,倒从来没见识过。还是看源码:
//HeapByteBuffer.java
public ByteBuffer put(byte x) {
hb[ix(nextPutIndex())] = x;
return this;
}
protected int ix(int i) {
return i + offset;
}
//Buffer.java
final int nextPutIndex() { // package-private
if (position >= limit)
throw new BufferOverflowException();
return position++;
}
写入字节是position每次自增,而写入其他数据类型则同样是在调用nextPutIndex方法,只不过不是自增1了而是根据数据类型来定,比如putLong就是增加8:
//HeapByteBuffer.java
public ByteBuffer putLong(long x) {
Bits.putLong(this, ix(nextPutIndex(8)), x, bigEndian);
return this;
}
//Bits.java
static void putLong(ByteBuffer bb, int bi, long x, boolean bigEndian) {
if (bigEndian)
putLongB(bb, bi, x);
else
putLongL(bb, bi, x);
}
static void putLongB(ByteBuffer bb, int bi, long x) {
bb._put(bi , long7(x));
bb._put(bi + 1, long6(x));
bb._put(bi + 2, long5(x));
bb._put(bi + 3, long4(x));
bb._put(bi + 4, long3(x));
bb._put(bi + 5, long2(x));
bb._put(bi + 6, long1(x));
bb._put(bi + 7, long0(x));
}
private static byte long7(long x) { return (byte)(x >> 56); }
private static byte long6(long x) { return (byte)(x >> 48); }
private static byte long5(long x) { return (byte)(x >> 40); }
private static byte long4(long x) { return (byte)(x >> 32); }
private static byte long3(long x) { return (byte)(x >> 24); }
private static byte long2(long x) { return (byte)(x >> 16); }
private static byte long1(long x) { return (byte)(x >> 8); }
private static byte long0(long x) { return (byte)(x ); }
void _put(int i, byte b) { // package-private
hb[i] = b;
}
好吧,看起来是有点眼花,个人觉得这里其实可以用循环一次解决的。类似这种方法:
for (int i = 0; i < 8; i++) {
bb._put(bi + (7 - i), x >> ((7 - i) * 8));
}
不过可能是由于这样一眼很难看出在干什么吧(?)。
所以put其他类型的数据,也是同样的道理。
HeapByteBuffer
在上面创建的bytebuffer,默认都是HeapByteBuffer。
比如:
public static ByteBuffer wrap(byte[] array,
int offset, int length)
{
try {
return new HeapByteBuffer(array, offset, length);
} catch (IllegalArgumentException x) {
throw new IndexOutOfBoundsException();
}
}
又比如:
public static ByteBuffer allocate(int capacity) {
if (capacity < 0)
throw new IllegalArgumentException();
return new HeapByteBuffer(capacity, capacity);
}
它们使用了不同的构造方法,但都使用了同样的父类构造方法:
HeapByteBuffer(int cap, int lim) { // package-private
super(-1, 0, lim, cap, new byte[cap], 0);
/*
hb = new byte[cap];
offset = 0;
*/
}
HeapByteBuffer(byte[] buf, int off, int len) { // package-private
super(-1, off, off + len, buf.length, buf, 0);
/*
hb = buf;
offset = 0;
*/
}
//ByteBuffer.java
ByteBuffer(int mark, int pos, int lim, int cap, // package-private
byte[] hb, int offset)
{
super(mark, pos, lim, cap);
this.hb = hb;
this.offset = offset;
}
//Buffer.java
Buffer(int mark, int pos, int lim, int cap) { // package-private
if (cap < 0)
throw new IllegalArgumentException("Negative capacity: " + cap);
this.capacity = cap;
limit(lim);
position(pos);
if (mark >= 0) {
if (mark > pos)
throw new IllegalArgumentException("mark > position: ("
+ mark + " > " + pos + ")");
this.mark = mark;
}
}
public final Buffer limit(int newLimit) {
if ((newLimit > capacity) || (newLimit < 0))
throw new IllegalArgumentException();
limit = newLimit;
if (position > limit) position = limit;
if (mark > limit) mark = -1;
return this;
}
public final Buffer position(int newPosition) {
if ((newPosition > limit) || (newPosition < 0))
throw new IllegalArgumentException();
position = newPosition;
if (mark > position) mark = -1;
return this;
}
所以,在正常的情况下,一个bytebyffer被创建后,
mark = -1;
position = 0;
limit = array.length = capacity;
只要弄清楚这几者的关系,bytebuffer就可以放心使用了。