ByteBuffer注意事项

因为使用通信框架不同的缘故,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就可以放心使用了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值