ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

NIO——Buffer

2022-03-19 09:04:10  阅读:184  来源: 互联网

标签:10 NIO Buffer Assertions ByteBuffer position buf


Buffer是Java NIO技术中的一个组件,简单来说它就是一个缓冲区,但它比我们自己编写的byte[] buf具有更多功能,甚至还支持堆外的直接内存分配,比较适用于大型数据的传输。

本笔记只记录Buffer中我觉得不太熟悉的API。

mark

调用mark时,当前Buffer的位置被记下,等调用reset时,恢复之前标记的位置。

    @Test
void testMarkAndReset() {
    ByteBuffer buf = ByteBuffer.allocate(10);
    buf.put(new byte[] {1, 2, 3, 4}); // position = 4, limit = 10, cap = 10
    System.out.println(buf);
    buf.mark(); // mark = 4
    buf.put(new byte[] {5, 6, 7, 8}); // position = 8, limit = 10, cap = 10
    System.out.println(buf);
    buf.reset(); // position = 4, limit = 10, cap = 10
    System.out.println(buf);
}

mark未被设置时,它是-1,这时你调用reset会出错,因为Buffer类认为你还没设置过mark

positionlimit小于mark时,mark会被丢弃,变成-1

直接缓冲

非直接缓冲是在堆内存中分配,得到的Buffer实例是java.nio.HeapByteBuffer,这时你是向JVM申请缓冲区的空间,然后JVM再向操作系统申请,而直接缓冲越过JVM直接向操作系统申请,得到的是java.nio.DirectByteBuffer实例。

DirectByteBuffer类中使用Unsafe.allocateMemory来进行直接内存分配。

可以使用ByteBuffer.isDirect来判断该缓冲区是否来自直接内存,

@Test
void testDirectBuffer() {
    ByteBuffer buf = ByteBuffer.allocateDirect(1024);
    Assertions.assertTrue(buf.isDirect());
}

hasArray

hasArray返回Buffer是否具有底层数组。

public final boolean hasArray() {
    return (hb != null) && !isReadOnly;
}

ByteBuffer类中维护了一个数组hb,默认为nullHeapByteBuffer通过JVM堆来维护缓冲区,所以这个底层数组是它实现缓冲区的唯一方式,所以它初始化了这个数组并使用了。而DirectByteBuffer通过直接内存来维护缓冲区,所以它不用也不能使用这个底层数组,所以它没有初始化这个数组。所以DirectByteBuffer总是不具有底层数组的,它的hasArray总返回false

此外,当Buffer是只读时,也返回false,只读Buffer一般是某个Buffer的一个包装,它们共享底层数组,但只读Buffer不能对底层数组进行写入。

@Test
void testHasArray() {
    Assertions.assertTrue(
            ByteBuffer.allocate(10).hasArray()
    );

    Assertions.assertFalse(
            ByteBuffer.allocateDirect(10).hasArray()
    );

    Assertions.assertFalse(
            ByteBuffer.allocate(10).asReadOnlyBuffer().hasArray()
    );
}

rewind

清除标记,设置position为0,limit不变。当重新读取或写入一个缓冲区时使用

读写

Buffer类提供了一系列的读写方法,它们以get/put开头。简单把它们分成两类,绝对读写和相对读写。

绝对读写要求提供一个下标,它只是对下标位置的数据进行更改,Buffer的position并不会因此改变。

@Test
void testAbsolutePutAndGet() {
    ByteBuffer buf = ByteBuffer.allocate(10);
    // 通过buf.put(index, data) 在buf下标为5的地方写入10
    buf.put(5, (byte) 10);
    Assertions.assertEquals(0, buf.position());
    Assertions.assertEquals(10, buf.get(5));
}

相对读写不需要提供下标,它从当前position位置开始写入,写入完成后更新position位置。

@Test
void testRelativePutAndGet() {
    ByteBuffer buf = ByteBuffer.allocate(10);
    buf.put(new byte[] {1, 2, 3, 4, 5});
    Assertions.assertEquals(5, buf.position());
}

wrap方法需要注意的一个点

wrap方法直接使用一个传入的数组做为底层数组,它的容量等于传入数组大小。

容易迷惑的是wrap(byte[] buf, int offset, int length),看起来好像是创建了一个新的子数组作为底层数组,但不是,offset会被设置成buffer的position,length + position会被设置成buffer的limit。

@Test
void testWrap() {
    // 缓冲区数组是{1, 2, 3, 4, 5, 6, 7, 8},其可用内容是{5, 6, 7}
    ByteBuffer buf = ByteBuffer.wrap(new byte[]{ 1, 2, 3, 4, 5, 6, 7, 8}, 4, 3);
    Assertions.assertEquals(8, buf.capacity());
    Assertions.assertEquals(4, buf.position());
    Assertions.assertEquals(7, buf.limit());

    Assertions.assertEquals(5, buf.get());
    Assertions.assertEquals(6, buf.get());
    Assertions.assertEquals(7, buf.get());
}

slice和arrayOffset

slice根据当前ByteBuffer创建一个新的ByteBuffer。

达到的目的和Python中的切片、JS中的slice一样,都是获得一个Buffer的子Buffer,JavaNIO中的实现是让它们共用相同的底层数组,然后调节新Buffer的偏移量,也就是arrayOffset

@Test
void testSliceAndArrayOffset() {
    ByteBuffer buf = ByteBuffer.wrap(new byte[] {1,2,3,4,5,6,7,8,9,0});
    ByteBuffer buf2 = buf.slice(4, 3);
    // buf和buf2共用同一个底层数组,只是buf2中的所有操作都需要加上一个为4的偏移量。
    Assertions.assertEquals(4, buf2.arrayOffset());
}

新旧Buffer具有独立的position、mark、limit等信息,但是对于底层数组的修改会反映到所有共用该底层数组的Buffer上。

@Test
void testSameArray() {
    ByteBuffer buf1 = ByteBuffer.wrap(new byte[] {1,2,3,4,5,6,7,8,9,0});
    ByteBuffer buf2 = buf1.slice(4, 3);
    buf2.put(0, (byte) 10); // buf2: 10, 6 ,7 ; buf1: 1, 2, 3, 4, 10, 6, 7, 8, 9, 0

    byte[] arrayInBuf1 = new byte[buf1.remaining()];
    buf1.get(arrayInBuf1);
    Assertions.assertArrayEquals(
            new byte[] {1,2,3,4,10,6,7,8,9,0},
            arrayInBuf1
    );
}

压缩缓冲区

compact方法对缓冲区进行压缩。它的作用是将目前缓冲区中没读完的数据移动到缓冲区的最前面,并将指针放置到移动后的位置。

它的存在是为了应付防止循环中数据未写(读)完的情况

equals

不关心capacity,limit和position是否一致,只要两个缓冲区内的剩余内容(position到limit)完全一样即可。

  1. 两个都是ByteBuffer
  2. remining一样
  3. position到limit的每个字节都一样

下面的两个buffer的position、limit、capacity都不一致,但它们的剩余内容一致,所以它们相等。

@Test
void testEquals() {
    ByteBuffer buf1 =
            ByteBuffer.wrap(new byte[]{1, 2, 3, 100, 101, 102, 5, 4, 6});
    buf1.position(3);
    buf1.limit(6);

    ByteBuffer buf2 =
            ByteBuffer.wrap(new byte[]{100, 101, 102, 103, 104});
    buf2.limit(3);

    Assertions.assertTrue(buf1.equals(buf2));

}

同时compareTo方法也是按照字典序比较它们的剩余序列。

标签:10,NIO,Buffer,Assertions,ByteBuffer,position,buf
来源: https://www.cnblogs.com/lilpig/p/16025391.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有