【NIO】学习系列(二)ByteBuffer类

1.介绍

byteBuffer是Buffer类的子类,可以在缓冲区中以字节为单进行数据存取。在nio中会经常使用byteBuffer

ByteBuffer主要提供了六类操作

  • 以绝对位置和相对位置读写单个字节的get()和put()方法
  • 使用相对批量get(byte[] dst)方法可以将缓冲区中连续的字节输出到byte[] dst目标数组中。
  • 使用相对批量put(byte []src)方法可以将byte[] 数组或其他字节缓冲区中的连续字节存储到此缓冲区中。
  • 使用绝对和相对getType和putType方法可以按照字节顺序在字节序列中读写其他基本数据类型的值,方法getType和putType可以进行数据类型的自动转换。
  • 提供了创建试图缓冲区的方法,这些方法允许将字节缓冲区视为包含其他基本类型值的缓冲区,比如asCharBuffer()、asDoubleBuffer()、asFloatBuffer()、asIntBuffer()、asLongBuffer()和asShortBuffer()
  • 提供了对字节缓冲区进行压缩(compacting)、赋值(duplication)和截取(slicing)的方法。

2.创建非直接缓冲区与直接缓冲区

非直接缓冲区的代表是堆缓冲区

字节缓冲区分直接缓冲区和非直接缓冲区直接缓冲区:

如果字节缓冲区为直接缓冲区,则JVM会尽量在直接字节缓冲区上执行I/O,而不是从中间缓冲区存储数据,这样省下一步就会提高效率。

工厂方法allocateDirect()可以创建直接字节缓冲区,通过allocateDirect()方法返回的缓冲区的释放和分配通常高于非直接缓冲区

直接缓冲区操作的数据不再JVM堆中,而在内核空间中。因此直接缓冲区善于保存易受I/O操作影响的大量的、长时间保存的数据。

alocateDirect(int capacity)方法

分配新的直接字节缓冲区。新缓冲区的位置将为0,其limit = capacity,mark是不确定的。无论是否具有底层实现,mark都不确定。

allocate(int capacity)方法的作用

创建一个非直接缓冲区。

分配一个新的非直接字节缓冲区。新缓冲区的位置为0,其接线将为其容量,其标记也不确定。他讲具有一个底层实现数组,且其偏移量为0.

注意:

allocate会在内部创建一个新的数组,而wrap是将引用赋值给内部数组。如果使用wrap那么对原数组的值进行设置会影响Buffer内部数据,如果修改了内部数据也会影响外部数组。

3.直接缓冲区的垃圾释放

alocateDirect()创建出来的缓冲区类型为DirectByteBuffer,使用allocate()方法创建出来的缓冲区类型为HeapByteBuffer。使用wrap()工厂方法创建的也是HeapByteBuffer。

使用allocateDirect()方法创建ByteBuffer缓冲区时,capacity是字节个数,IntBuffer如果将int转为字节则capacity的值要乘4因为int = 4字节

1.手动释放

@Test
    public void test9(){
        byte[] ints = {1, 2, 3, 4, 5, 6, 7};
        ByteBuffer buffer = ByteBuffer.allocateDirect(10);
        for(byte i = 0;buffer.hasRemaining();i++){
            buffer.put(i);
        }
        System.out.println("put end!!");
        //垃圾回收
        try {
            Method cleaner = buffer.getClass().getMethod("cleaner");
            cleaner.setAccessible(true);
            Object invoke = cleaner.invoke(buffer);
            Method clean = invoke.getClass().getMethod("clean");
            clean.invoke(invoke);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

2.JVM自动处理

代码:略

4.直接缓冲区与非直接缓冲区的运行效率比较

直接缓冲区会直接操作本地操作系统的I/O,处理效率比非直接的要快一些。

为什么会快?

非直接缓冲区使用的是操作数组的方式,而且还是在堆中来进行操作,可能会慢一点,但不一定会慢。
在这里插入图片描述

直接缓冲区直接使用Unsafe来使用底层直接操作操作系统来进行I/O

在这里插入图片描述

5.包装Wrap数据的处理

wrap(byte[] arrray)方法的作用:将byte数组包装到缓冲区中。其缓冲区的数组就是传递的数组。

wrap(byte[] array,int offset,int length)方法参数详解

  • array传递的数组
  • offset 设置position的起始位置,offset <= array.legth
  • legth 数组的可用长度。 legth <= array.legth - offset。limit的值会设置为 offset+length

总之:offset可以确定position的值

​ length和offset可以确定limit的值

如下代码所示,设置wrap(ints,2,2);

此时只能从数组的下标为2开始存,且只能存两个因为limit是4=offset+length

@Test
public void test12(){
    int[] ints = {1, 2, 3, 4, 5,7,8};
    IntBuffer buffer = IntBuffer.wrap(ints, 2, 2);
    System.out.println("limit ->"+buffer.limit());
    buffer.put(10);
    buffer.put(20);
    //buffer.put(30);
    System.out.println("position -> "+buffer.position());
    System.out.println("limit ->"+buffer.limit());
}

6.put(byte b)和get()方法的使用与position自增特性

Buffer类的每个子类都定义了两种get和put操作,分别是相对位置操作和绝对位置操作。

byte get()方法:使用相对位置get()操作,然后该位置递增。

put(byte b):使用相对位置put然后位置自增

7.put(byte[] src,int offset,int length)和get(byte[] dst,int offset,int length)方法的使用。

put(byte[] src,int offset,int length)方法:相对批量put,将给定数组的值一个个赋值到内部数组,如果给定数组的数量大于内部数量会报错BufferOverFlowException

测试如下

之后,打印的position为3。

@Test
public void test13(){
    byte[] bytes = new byte[]{1,2,3};
    byte[] newBytes = new byte[]{11,21,31};
    ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
    byteBuffer.put(newBytes);
    System.out.println(byteBuffer.position());
}

get(byte[] dst,int offset,int length)方法的作用:相对批量get。将缓冲区当前位置开始的数据传输到指定的目标数组中。

注意:内部数组可用个数,和接收数组的可用个数必须一致,否则报错

如下所示:如果get(newBytes,1,3)不加1,3来限定个数那么就会报错。

@Test
public void test14(){
    byte[] bytes = new byte[]{1,2,3};
    byte[] newBytes = new byte[]{11,21,31,0};
    ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
    byteBuffer.get(newBytes,1,3);
    System.out.println(Arrays.toString(newBytes));
}

8.put(int index,byte b)和get(int index)方法的使用与position不变

注意:使用绝对操作后,position的位置不变。

put(int index,byte b)方法的作用:绝对put方法,将给定字节写入此缓冲区的给定索引位置。

get(int index);获取指定位置的元素

测试代码如下

@Test
public void test15(){
    byte[] bytes = new byte[]{1,2,3,4};
    ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
    byteBuffer.put(2,(byte)30);
    System.out.println("position -> "+byteBuffer.position());
    while(byteBuffer.hasRemaining()){
        System.out.println(byteBuffer.get());
    }
}

9.put(ByteBuffer src)

将src缓冲区复制到当前缓冲区,如果src的缓冲区过大那就报错

**缓冲区的大小是从position -> limit **

BufferOverFlowException。否则从本缓冲区的当前位置开始,一个个赋值

注意,put(ByteBuffer src)会使两个缓冲区的position都会移动

10.putType()与getType()方法

putChar(char src)

将char类型存储在byteBuffer中。因为char类型在java中是双字节,所以会先转为字节,然后进行存储两位,此时position会增加2。

putChar(int index,char src)

注意:这种是绝对操作,从index下标开始将src赋值到当前缓冲区中。注意:绝对操作不会更改position的值。

注意:在bByteBuffer中还有putInt(int src)、和putInt(int index,int src)等等,其操作和char类似,但是要看当前类型是几个字节,那么position就移动几次

11.slice()方法、arrayOffset()方法和offset非零情况

arrayOffset返回当前的offset

slice():创建新的字节缓冲区。

源码如下所示

虽然Buffer是新的,但是内部维护的是同一个数组,因此只要一个Buffer修改了值,也会影响其他的Buffer

在这里插入图片描述

实例代码

@Test
public void test18(){
    byte[] bytes = new byte[]{1,2,3,4};
    ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
    byteBuffer.get();//原缓冲区position+1
    ByteBuffer sliceBuffer = byteBuffer.slice();
    System.out.println("new buffer :------------");
    System.out.println("position -> "+sliceBuffer.position());
    System.out.println("limit -> "+sliceBuffer.limit());
    System.out.println("capacity -> "+sliceBuffer.capacity());
    while(sliceBuffer.hasRemaining()){
        System.out.println(sliceBuffer.get());
    }
}

注意点:

  • 如果原缓冲区是只读的,那么新缓冲区也是只读
  • 只有原缓冲区是只读的,新缓冲区才是只读的
  • 只有原缓冲区是直接缓冲区,新缓冲区才是直接缓冲区

12.asCharBuffer()转换为CharBuffer字符缓冲区及中文处理

asCharBuffer()方法的作用:创建此字节缓冲区的视图,作为char缓冲区。

源码如下所示:

因为char是两个字节,当转为char时,会将原缓冲区的可用字节个数的一半作为charBufer的capacity和limit。

在这里插入图片描述

注意点:

  • 修改本缓冲区,新缓冲区同样被修改
  • 仅当本缓冲区是只读的时,新缓冲区是只读的。
  • 本缓冲区将从position开始 到 Limit结束赋值到新缓冲区。

测试代码如下

注意在如下代码,我们将字节缓冲区转为了charBuffer,但是当我们打印字符的时候出现乱码?

因为在charbuffer.get()使用的是utf-16BE的编码,而目前我们编辑器idea使用的是UDF编码,我们只需要指定存储时使用UTF-16BE编码即可

byte[] bytes = "我是中国人".getBytes("utf-16BE");
public void test19(){
    try {
        byte[] bytes = "我是中国人".getBytes();
        ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
        System.out.println("newBuffer ******************** ");
        System.out.println("position -> "+byteBuffer.position()+" limit -> "+byteBuffer.limit()+" capacity -> "+byteBuffer.capacity());
        CharBuffer charBuffer = byteBuffer.asCharBuffer();
        System.out.println("oldBuffer ******************** ");
        System.out.println("position -> "+charBuffer.position()+" limit -> "+charBuffer.limit()+" capacity -> "+charBuffer.capacity());
        while(charBuffer.hasRemaining()){
            System.out.println(charBuffer.get());
        }
    }catch (Exception e) {
        e.printStackTrace();
    }
}

13.转为其他类型

asIntBuffer

因为int为4字节所以当转化时intBuffer的大小为原缓冲区的1/4

asDoubleBuffer

转为doubleBuffer,大小为原缓冲区的1/8

14.设置与获得字节顺序

public final ByteOrder order();//获取当前读取的字节顺序
public final ByteBuffer order(ByteOrder bo);//设置缓冲区的读取顺序

order方法与字节的排列顺序有关,不同的CPU读取字节的顺不同,有的从低位到高位,有的从高位到低位。

ByteOrder BIG_ENDIAN;//从高位到地位
ByteOrde LITTLE_ENDIAN;//从低位到高位

注意:新创建的字节缓冲区的顺序始终未BIG_ENDIAN(高位到地位),但是如果我们强制从低位读取货出问题

如下代码所示,现有byte => 0001

换成2进制 - 00000000 00000000 00000000 00000001

从高位开始是1

从低位开始是16777216

@Test
public void test21(){
    byte[] bytes = new byte[]{0,0,0,1};//
    ByteBuffer buffer = ByteBuffer.wrap(bytes);
    buffer.order(ByteOrder.LITTLE_ENDIAN);
    while(buffer.hasRemaining()){
        System.out.println(buffer.getInt());
    }
}

15.创建只读缓冲区

public abstract ByteBuffer asReadOnlyBuffer();//将此缓冲区改为只读缓冲区

注意:这里不是将本缓冲区变成只读的,而是返回一个新的缓冲区,类型为HeapByteBufferR,比传统的多一个R但是新的缓冲区时只读的。但是原缓冲区的修改对此缓冲区也是可见的。新缓冲区的容量限制是相互独立的但是值相等。

在这里插入图片描述

测试代码如下所示

@Test
public void test22(){
    byte[] bytes = new byte[]{0,0,0,1};//
    ByteBuffer buffer = ByteBuffer.wrap(bytes);
    ByteBuffer byteBuffer = buffer.asReadOnlyBuffer();
    boolean readOnly = byteBuffer.isReadOnly();
    System.out.println(readOnly);
}

16.压缩缓冲区

public abstract ByteBuffer compact();//压缩缓冲区

注意:压缩返回的缓冲区和原来的是同一个缓冲区

压缩过程如下所示

  • 将position位置开始移动,总共需要移动limt-psotion个数 分别为 456789
  • 将4移动到下标为0处,此时position指向5
  • 再重复上面操作知道全部移动
  • 移动个数为n 那么position位置在第n+1个下标处

在这里插入图片描述

实例代码如下

@Test
public void test23(){
    byte[] bytes = new byte[]{1,2,3,4,5,6,7,8,9};
    ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
    byteBuffer.position(3);
    ByteBuffer compact = byteBuffer.compact();
    System.out.println(Arrays.toString(bytes));
}

17.比较缓冲区的内容

两种方法比较

boolean equals(Object o);

equlas源码如下

其比较顺序

  1. 比较地址
  2. 比较是不是ByteBuffer类的实例
  3. 判断可用个数一不一样
  4. 可用个数的值一不一样,有一个不一样就返回false

在这里插入图片描述

compare源码如下

compare比较的是,假设有两个ByteBuffer,分别为A和B

A的remainning为3

B的remainning为4

那么比较最少的3此

那么会比较A从position开始到3次结束,B也从他的position开始,与A比较,零表示相等,负数表示小于,正数表示大于。
在这里插入图片描述

18.复制缓冲区

ByteBuffer duplicate();

创建共享此缓冲区内容的新的字节缓冲区。新缓冲区的内容将为此缓冲区的内容。在新建的缓冲区中,位置,标记,容量,限制都与原缓冲区的值相等。但是都是相互独立的。仅当此缓冲区只读,新建的缓冲区才能只读。

duplicate()方法和slice()方法都会创建新的缓冲区对象,而且使用的是同一个数组。

duplicate()和slice()的区别

  • duplicate()当前sposition和milit和capcaity和mark都是和新建的相同的。
  • slice的offset为原缓冲区的position,mark是被丢弃的。

19.对缓冲区进行扩容

一旦创建缓冲区capcacity就无法改变了,如果想对缓冲区进行扩容,需要如下处理

public class MyApplication {
    public static void main(String[] args) {
        byte[] bytes = new byte[]{1,2,3,4};
        ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
        ByteBuffer byteBuffer1 = extendsSize(byteBuffer, 5);
        System.out.println(byteBuffer1.capacity());
    }

    private static ByteBuffer extendsSize(ByteBuffer oldBuffer ,int size){
        ByteBuffer newBuffer = ByteBuffer.allocateDirect(oldBuffer.capacity()+size);
        newBuffer.put(oldBuffer);
        return newBuffer;
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值