文章目录
- 1.介绍
- 2.创建非直接缓冲区与直接缓冲区
- 3.直接缓冲区的垃圾释放
- 4.直接缓冲区与非直接缓冲区的运行效率比较
- 5.包装Wrap数据的处理
- 6.put(byte b)和get()方法的使用与position自增特性
- 7.put(byte[] src,int offset,int length)和get(byte[] dst,int offset,int length)方法的使用。
- 8.put(int index,byte b)和get(int index)方法的使用与position不变
- 9.put(ByteBuffer src)
- 10.putType()与getType()方法
- 11.slice()方法、arrayOffset()方法和offset非零情况
- 12.asCharBuffer()转换为CharBuffer字符缓冲区及中文处理
- 13.转为其他类型
- 14.设置与获得字节顺序
- 15.创建只读缓冲区
- 16.压缩缓冲区
- 17.比较缓冲区的内容
- 18.复制缓冲区
- 19.对缓冲区进行扩容
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源码如下
其比较顺序
- 比较地址
- 比较是不是ByteBuffer类的实例
- 判断可用个数一不一样
- 可用个数的值一不一样,有一个不一样就返回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;
}
}