ByteBuffer是Buffer的一个子类,也是我们在平时使用最多的buffer,这里我们来看它是如何给出一些存取操作以及构造的逻辑的。
构造方法
ByteBuffer有两个构造方法:
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;
}
// Creates a new buffer with the given mark, position, limit, and capacity
//
ByteBuffer(int mark, int pos, int lim, int cap) { // package-private
this(mark, pos, lim, cap, null, 0);
}
相比于父类Buffer,它多了两个参数hb和offset,我们不需要关心这两个参数,构造方法同样是package private的,并且它最终也是通过调用父类Buffer的构造方法来实现的,所以这两个构造方法也只是设置了mark、position、limit和capacity这四个参数而已,真正创建缓冲区的并不是构造方法。
通过allocate方法来创建缓冲区
我们一般会使用ByteBuffer的allocate来创建缓冲区,我们来看它的实现:
public static ByteBuffer allocate(int capacity) {
if (capacity < 0)
throw new IllegalArgumentException();
return new HeapByteBuffer(capacity, capacity);
}
它只介绍一个capacity也就是容量作为参数,然后直接创建了一个HeapByteBuffer,HeapByteBuffer从名字上来理解应该是在jvm的堆内存上创建的buffer,与之对应的是allocateDirect方法,它会直接在机器内存上创建缓存而不是jvm的堆上,相对于对内存而言,它会减少内存复制的开销,但同时创建的开销会增加,这里我们主要来看HeapByteBuffer,它的构造函数应该接收两个参数,这里都是capacity,我们来看:
class HeapByteBuffer extends ByteBuffer
这个类并非public,应该只是内部使用,它居然是ByteBuffer的子类。来看构造方法:
HeapByteBuffer(int cap, int lim) { // package-private
super(-1, 0, lim, cap, new byte[cap], 0);
}
它的构造方法直接调用了父类ByteBuffer的构造方法。。。。。。 所以还是回到了ByteBuffer以及Buffer的构造方法上。
到现在为止,我们还是没看到这个buffer是怎么创建的,只是对position、capacity等属性进行了赋值,实际上,jvm会自动为我们实现buffer的创建,我们来看一下allocate方法的注释:
会创建一个新的byte buffer。新创建的buffer的position是0,limit是capacity,mark未定义,它会有一个背后的数组,并且每个元素会被初始化为零。
通过wrap方法来包装数组到缓冲区
除了通过allocate方法来直接创建buffer,我们还可以通过wrap方法来将一个字节数组包装成一个buffer:
public static ByteBuffer wrap(byte[] array,
int offset, int length)
{
try {
return new HeapByteBuffer(array, offset, length);
} catch (IllegalArgumentException x) {
throw new IndexOutOfBoundsException();
}
}
它也是通过HeapByteBuffer实现的。我们需要看一下这个方法的注释:
新的buffer会将参数中的字节数组作为背后的数组,对buffer的改动会反映到数组上,反之亦然。buffer的capacity是数组的长度,position是offset,它的limit是offset+length。
还有一个相对简单的wrap方法:
public static ByteBuffer wrap(byte[] array) {
return wrap(array, 0, array.length);
}
它是通过调用上面的wrap方法实现的。它的含义我们就不过多解释了。
获取元素
ByteBuffer有几个get方法来实现从buffer中获取元素的操作。对于获取元素,我们可以相对获取,就是每次都获取当前position位置的元素,然后将position增加,也可以绝对获取,就是直接获取指定位置的元素并且不影响position。
相对单个获取
相对获取使用没有参数的get方法实现:
/**
* Relative <i>get</i> method. Reads the byte at this buffer's
* current position, and then increments the position.
*
* @return The byte at the buffer's current position
*
* @throws BufferUnderflowException
* If the buffer's current position is not smaller than its limit
*/
public abstract byte get();
绝对单个获取
绝对获取使用带有参数的get方法实现,它需要一个指定位置的参数:
/**
* Absolute <i>get</i> method. Reads the byte at the given
* index.
*
* @param index
* The index from which the byte will be read
*
* @return The byte at the given index
*
* @throws IndexOutOfBoundsException
* If <tt>index</tt> is negative
* or not smaller than the buffer's limit
*/
public abstract byte get(int index);
相对批量获取
相对批量获取就是从当前的position开始一次获取多个字节然后修改position的值,它是通过调用单个相对获取方法来实现的:
public ByteBuffer get(byte[] dst, int offset, int length) {
checkBounds(offset, length, dst.length);
if (length > remaining())
throw new BufferUnderflowException();
int end = offset + length;
for (int i = offset; i < end; i++)
dst[i] = get();
return this;
}
它接收三个参数,dst是保存获取的字节的字节数组,offset指从dst的哪个字节位置开始保存,length指定要获取的字节的数量。可以看到它循环调用了get方法。
注意ByteBuffer没有提供绝对批量获取方法。
添加元素
与获取元素类似,添加元素也可以相对添加和绝对添加。相对添加也支持批量操作。基本原理与获取元素一致,就不做过多解释了。
相对添加
/**
* Relative <i>put</i> method <i>(optional operation)</i>.
*
* <p> Writes the given byte into this buffer at the current
* position, and then increments the position. </p>
*
* @param b
* The byte to be written
*
* @return This buffer
*
* @throws BufferOverflowException
* If this buffer's current position is not smaller than its limit
*
* @throws ReadOnlyBufferException
* If this buffer is read-only
*/
public abstract ByteBuffer put(byte b);
绝对添加
/**
* Absolute <i>put</i> method <i>(optional operation)</i>.
*
* <p> Writes the given byte into this buffer at the given
* index. </p>
*
* @param index
* The index at which the byte will be written
*
* @param b
* The byte value to be written
*
* @return This buffer
*
* @throws IndexOutOfBoundsException
* If <tt>index</tt> is negative
* or not smaller than the buffer's limit
*
* @throws ReadOnlyBufferException
* If this buffer is read-only
*/
public abstract ByteBuffer put(int index, byte b);
相对批量添加
public ByteBuffer put(byte[] src, int offset, int length) {
checkBounds(offset, length, src.length);
if (length > remaining())
throw new BufferOverflowException();
int end = offset + length;
for (int i = offset; i < end; i++)
this.put(src[i]);
return this;
}
直接添加ByteBuffer
批量添加还有一个以ByteBuffer作为参数的方法:
public ByteBuffer put(ByteBuffer src) {
if (src == this)
throw new IllegalArgumentException();
if (isReadOnly())
throw new ReadOnlyBufferException();
int n = src.remaining();
if (n > remaining())
throw new BufferOverflowException();
for (int i = 0; i < n; i++)
put(src.get());
return this;
}
这个方法会将src从position到limit的字节添加到当前buffer的position开始的位置。
与其他原始类型相关的方法
因为java中的其他原始类型如int、long、char等都可以用字节表示,所以ByteBuffer提供了一些跟这些原始类型相关的方法,一般都是添加和获取方法:
这些都是抽象方法,我们就不去罗列了。
抽象方法的最终实现
我们上面说到的各种添加元素、获取元素以及与其他原始类型相关的方法都是抽象的,那么它是怎么实现的?答案就是HeapByteBuffer和DirectByteBuffer。上面将allocate方法时我们看到,通过allocate方法创建的ByteBuffer实际上是它的子类HeapByteBuffer的实例,所以ByteBuffer的这些抽象方法都是HeapByteBuffer来实现的。
实际上,对于每个Buffer的子类,例如ByteBuffer、IntBuffer、CharBuffer等,都有一个对应的Heap-X-Buffer作为它们的实现类,在创建ByteBuffer、IntBuffer等的时候,实际上就是创建的它们对应的子类Heap-X-Buffer。这里我们以ByteBuffer为例进行了分析,其他的就不逐一解释了。