Buffer类族
从分析VasDolly的源码和android自身的签名、校验源码,经常可以看到ByteBuffer的身影。它是什么东西呢?有什么优势?下面具体分析下整个类以及相关的类。
Buffer类族是为基本数据类型(除boolean类型外)的数据提供一个线性的有限长度的特定序列的结构。
Buffer类不是线程安全的,应该避免多线程读写同一个Buffer。
Buffer.java
主要的属性
- capacity:由于是有限的数据结构,这是它的容量
- limit:真正读写的上线,一般情况下等于capacity
- position:当前读写的位置
- mark:为某次读过的位置做标记,从而可以回退到该位置
0 <= mark <= position <= limit <= capacity
具体操作
具体的操作都是针对上面的属性值的改变。
equals()
当满足下列条件时,表示两个Buffer相等:
- 相同的数据类型
- remaining的个数相等
- remaining中的各元素相等
public boolean equals(Object ob) {
if (this == ob)
return true;
if (!(ob instanceof ByteBuffer))
return false;
ByteBuffer that = (ByteBuffer)ob;
if (this.remaining() != that.remaining())
return false;
int p = this.position();
for (int i = this.limit() - 1, j = that.limit() - 1; i >= p; i--, j--)
if (!equals(this.get(i), that.get(j)))
return false;
return true;
}
!!!为啥是比较剩余的是否相等???
compareTo()
比较两个buffer大小关系。
public int compareTo(ByteBuffer that) {
int n = this.position() + Math.min(this.remaining(), that.remaining());
for (int i = this.position(), j = that.position(); i < n; i++, j++) {
int cmp = compare(this.get(i), that.get(j));
if (cmp != 0)
return cmp;
}
return this.remaining() - that.remaining();
}
- 从position开始,到limit,比较不相等的元素
- 如果等相等,则比较remaining的个数
PS:
不带参数的get() 和 put()都是会将position加1的
Buffer的子类
- ByteBuffer:处理字节数据类型
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
- MappedByteBuffer
MappedByteBuffer是以虚拟内存作为操作对象的直接字节缓存区。对象的创建是通过FileChannel.map
创建。
它有3个重要的方法:
- fore():缓冲区内容的修改强行写入文件
- load():将缓冲区的内容载入内存,并返回该缓冲区的引用
- isLoaded():如果缓冲区的内容在物理内存中,则返回真,否则返回假
而FileChannel.map
方法是将文件映射到虚拟内存中,并返回逻辑地址。
直接缓存区和间接缓存区
间接缓存区
该缓存区是在JVM的内存中创建,每次的IO,JVM都会将缓存区的内容复制到中间缓存区中,或者从中间缓存区复制内容到建立的缓存区。
缓存区都是驻留在JVM中,因此销毁容易,但是占用JVM的内存开销。
- 创建一个临时的直接ByteBuffer对象。
- 将非直接缓冲区的内容复制到临时缓冲中。
- 使用临时缓冲区执行低层次I/O操作。
- 临时缓冲区对象离开作用域,并最终成为被回收的无用数据。
直接缓存区
直接缓冲区,Java虚拟机直接执行native I/O操作,避免在操作系统的native I/O操作时还要复制内容到一个中间缓冲区。
它使用Native函数库直接分配堆外内存,然后通过一个存储在JAVA堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。
可以通过allocateDirect工厂方法直接创建直接缓冲区, 内部会创建DirectByteBuffer对象, 通过unsafe.allocateMemory
分配内存。 相对而言, 这个方法返回的缓冲区要比非直接缓冲区多少有点更高的分配/销毁的花费 (时间和空间)。 直接缓冲区在垃圾回收堆的外部, 所以建议主要用于大的长时间活动的缓冲区,确实能提高性能的环境中。
虽然直接缓冲区使JVM可以进行高效的I/O操作,但它使用的内存是操作系统分配的,绕过了JVM堆栈,建立和销毁比堆栈上的缓冲区要更大的开销。
Buffer的操作
创建
CharBuffer cb = CharBuffer.allocate(1024);
ByteBuffer bb = ByteBuffer.allocateDirect(1024*1024);
包装
int[] bytes = new int[1024];
IntBuffer ib = IntBuffer.wrap(bytes);
内存映射
FileChannel fc = new RandomAccessFile("test.data", "rw").getChannel();
MappedByteBuffer out = fc.map(FileChannel.MapMode.READ_WRITE, 0, length);