ByteBuffer阅读笔记
问题
一、简介
是一个抽象类,但是可以根据类提供的静态方法,来生成堆内或堆外的实例对象,Buffer的第一级子类
采用了《模板模式》的设计模式
二、继承关系图
- 相关实现
我们通过IDEA的插件可以看出ByteBuffer相关的继承关系及实现类,我们下面简单描述一下
- HeapByteBuffer:继承与ByteBuffer,数据存储在 JVM中间缓存区
- HeapByteBufferR:继承于HeapByteBuffer,堆缓存区,只支持只读
- MappedByteBuffer:继承于ByteBuffer,是一个抽象类
- 是Java NIO中引入的一种硬盘物理文件和内存映射方式,当物理文件较大,采用MappedByteBuffer,读写性能较高,其内部的核心实现了DirectByteBuffer(JVM堆外直接物理内存)
- JVM进程通过内存映射方式加载的物理文件并不会耗费同等大小的物理内存,当应用程序访问数据时,程序通过虚拟地址寻址对应的内存页,如果物理内存中不存在对应页,MMU则会产生缺页中断异常,CPU尝试从系统Swap分区中查找,如仍不存在,则会直接从硬盘中物理文件中读取。
- 所以MappedByteBuffer使用虚拟内存,因此分配(map)的内存大小不受JVM的-Xmx参数限制,但是也是有大小限制的。 如果当文件超出1.5G限制时,可以通过position参数重新map文件后面的内容。
- DirectByteBuffer:继承于MappedByteBuffer,也称呼为JVM堆外直接物理内存。因为数据存储在JVM堆外的直接物理内存中
- DirectByteBufferR:继承于DirectByBuffer,物理内存缓冲区,仅支持读
三、存储结构
四、源码分析
内部类
属性
/** 不为空,仅用于JVM堆缓存区 */
final byte[] hb; // Non-null only for heap buffers
/** 数组的偏移量 */
final int offset;
/** 是否仅支持读,已R结尾的就是仅支持读 */
boolean isReadOnly;
// 是否采用默认排序
// 默认true为ByteOrder.BIG_ENDIAN,将字节排序的顺序按照高位开始
// false ByteOrder.LITTLE_ENDIAN,讲字节排序的顺序按照低位开始
// 统一字节的排序,不然会导致数据读取异常
boolean bigEndian = true; // package-private
// 本缓存的字节数据排列的顺序
boolean nativeByteOrder = (Bits.byteOrder() == ByteOrder.BIG_ENDIAN);// package-private
构造
- 构造方法都是只能同包或子类使用,可以通过静态方法实例化不同的子类对象
/** 仅用于HeapByteBuffer实例化时使用 */
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;
}
/** 仅用于MappedByteBuffer实例化使用 */
ByteBuffer(int mark, int pos, int lim, int cap) { // package-private
// 不需要堆数组,因为是直接操作物理内存
this(mark, pos, lim, cap, null, 0);
}
主要方法
1、创建缓存区
-
根据选择不同创建JVM堆内缓存区或堆外直接物理内存。都是静态方法
/** * 创建堆外直接物理内存 缓存区(仅Byte支持) */ public static ByteBuffer allocateDirect(int capacity) { return new DirectByteBuffer(capacity); } /** * 创建JVM堆内 缓存区,自定义容量 */ public static ByteBuffer allocate(int capacity) { if (capacity < 0) throw new IllegalArgumentException(); return new HeapByteBuffer(capacity, capacity); } /** * 创建JVM堆内 缓存区 * 自定义数组,offset其实读写索引,length 支持读写长度 */ public static ByteBuffer wrap(byte[] array, int offset, int length){ try { return new HeapByteBuffer(array, offset, length); } catch (IllegalArgumentException x) { throw new IndexOutOfBoundsException(); } } /** * 创建JVM堆内 缓存区, * 自定义数组,默认offset为0,length 为数组的长度 */ public static ByteBuffer wrap(byte[] array) { return wrap(array, 0, array.length); }
2、get方法
-
2个
/** * dst,用于存储读取内容 * offset,读取内容写入到dst数组的其实索引 * length,本次读取的数据大小 */ public ByteBuffer get(byte[] dst, int offset, int length) { // Buffer的边界检测,检测dst是否有足够存放容量的空间 checkBounds(offset, length, dst.length); // 如果读取的大小 大于剩余的容量大小,则抛出BufferUnderflowException异常 if (length > remaining()) throw new BufferUnderflowException(); // 本次读取结束索引位 int end = offset + length; for (int i = offset; i < end; i++) // 模板模式,根据不同的构造调用不同的get方法 dst[i] = get(); return this; } /** * 直接获取dst数组大小的数据,但是如果剩余的数量小于dst.length 则会抛出异常哦 */ public ByteBuffer get(byte[] dst) { // 调用的是上面的方法,直接默认从0下入,写入数组的最大长度 return get(dst, 0, dst.length); }
3、put方法
-
3个
/** * 读取缓存块(堆内或堆外)中的数据到自身,仅支持数据写入到JVM缓存快 */ public ByteBuffer put(ByteBuffer src) { // 参数效验 // 1.不可是自身 // 2.不可是只读(也就是通过asReadOnlyBuffer方法获取的只读缓冲区) // 3.待读取的src缓存快 剩余可读取的容量 不可大于自身 可写入的容量 if (src == this) throw new IllegalArgumentException(); if (isReadOnly()) throw new ReadOnlyBufferException(); int n = src.remaining(); if (n > remaining()) throw new BufferOverflowException(); // 开始读取src中的数据,写入到自身 for (int i = 0; i < n; i++) put(src.get()); return this; } /** * src 待读取的数组 * offset 读取的开始位置 * length 本次读取的长度 */ public ByteBuffer put(byte[] src, int offset, int length) { // 参数效验,是否超出src的可读写边界 checkBounds(offset, length, src.length); // 从数组中读取的容量大小不可超过缓存快剩余可写入的容量大小 if (length > remaining()) throw new BufferOverflowException(); int end = offset + length; // 开始读取scr中的数组,写入到自身 for (int i = offset; i < end; i++) this.put(src[i]); return this; } public final ByteBuffer put(byte[] src) { // 调用的上面的方法,直接默认从0开始,读取数组的最大长度 return put(src, 0, src.length); }
4、其他方法
例如:一下XXX代表Long、Char、Int等
-
slice()把剩余的可操作容量生成一个独立buffer,底层数组是共享的,只是操作区不同
-
arrayOffSet()获取当前position所在的数组索引位
-
putXXX(xxx) 在当前position插入当前类型数据,例如:char类型,就插入2个字节
-
putXXX(int,xxx) 在指定的位置插入xxx数据
-
asXXXBuffer 创建只读缓存区,把xxxBuffer自身作为xxxBufferR的属性对象
-
order 获取当前buffer的字节排列顺序,高位排列还是地位排列
-
order(ByteOrder)设置当前字符的排序顺序,BIG_ENDIAN或LITTLE_ENDIAN
-
compact() 压缩缓存区,将缓存区的当前位置(position)和限制(limit)之间的字节(如果有)赋值到缓冲区开始处(0)。
-
equals(Object)比较,比较的是position和limit之间的数据、数量是否一致
-
compareTo(Object)比较两个从position之后的数据是否不一样,不一样的时候就用自己的position数据-传入的position数据,自己大就返回正数,否则负数,如果遍历了最小的剩余数量还没有结果,就看两个buffer的剩余数量谁大。所以如果结果等于0,那么类似equals,可用于排序比较哈
-
duplicate() 赋值此缓存区,4大基本属性mark、position、limit、capacity、数组、offset都一起复制
-
还有前天的常用方法和抽象方法,有兴趣的可以去看
补充-关于CharBuffer和ByteBuffer转换及解决编码问题
-
关于byte转换为中文,需要考虑编码问题,需要利用CharSets
public classs demo { public static void main(String[] args) { utf8(); utf16be(); } private static void utf8() { byte[] bytes = "我是中国人".getBytes(StandardCharsets.UTF_8); ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); System.out.println(byteBuffer.toString()); CharBuffer charBuffer = Charset.forName("utf-8").decode(byteBuffer); for (int i = 0; i < charBuffer.limit(); i++) { System.out.print(charBuffer.get()); } System.out.println(); } private static void utf16be() { byte[] bytes = "我是中国人".getBytes(StandardCharsets.UTF_16BE); ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); System.out.println(byteBuffer.toString()); CharBuffer charBuffer = Charset.forName("utf_16be").decode(byteBuffer); for (int i = 0; i < charBuffer.limit(); i++) { System.out.print(charBuffer.get()); } System.out.println(); } }
五、总结
buffer没有扩容方法,所以可以自己实现一个,然后把原来的copy过去到新的即可