此系列文章会详细解读NIO的功能逐步丰满的路程,为Reactor-Netty 库的讲解铺平道路。java
关于Java编程方法论-Reactor与Webflux的视频分享,已经完成了Rxjava 与 Reactor,b站地址以下:编程
Rxjava源码解读与分享:www.bilibili.com/video/av345…api
Reactor源码解读与分享:www.bilibili.com/video/av353…数组
本系列源码解读基于JDK11 api细节可能与其余版本有所差异,请自行解决jdk版本问题。安全
本系列前几篇:dom
BIO到NIO源码的一些事儿之NIO 下 之 Selector源码分析
在Java BIO中,经过BIO到NIO源码的一些事儿之BIO开篇的Demo可知,全部的读写API,都是直接使用byte数组做为缓冲区的,简单直接。咱们来拿一个杯子作例子,咱们不讲它的材质,只说它的使用属性,一个杯子在使用过程当中会首先看其最大容量,而后加水,这里给一个限制,即加到杯子中的水量为杯子最大容量的一半,而后喝水,咱们最多也只能喝杯子里所盛水量。由这个例子,咱们思考下,杯子是否是能够看做是一个缓冲区,对于杯子倒水的节奏咱们是否是能够轻易的控制,从而带来诸多方便,那是否是能够将以前BIO
中的缓冲区也加入一些特性,使之变的和咱们使用杯子同样便捷。 因而,咱们给buffer
添加几个属性,对比杯子的最大容量,咱们设计添加一个capacity
属性,对比加上的容量限制,咱们设计添加一个limit
属性,对于加水加到杯中的当前位置,咱们设计添加一个position
属性,有时候咱们还想在杯子上本身作个标记,好比喝茶,我本身的习惯就是喝到杯里剩三分之一水的时候再加水加到一半,针对这个状况,设计添加一个mark
属性。由此,咱们来总结下这几个属性的关系,limit
不可能比capacity
大的,position
又不会大于limit
,mark
能够理解为一个标签,其也不会大于position
,也就是mark <= position <= limit <= capacity
。
结合以上概念,咱们来对buffer中这几个属性使用时的行为进行下描述:
capacity
也就是缓冲区的容量大小。咱们只能往里面写
capacity
个byte
、long
、char
等类型。一旦Buffer
满了,须要将其清空(经过读数据或者清除数据)才能继续写数据往里写数据。
position
(1)当咱们写数据到
Buffer
中时,position
表示当前的位置。初始的position
值为0.当一个byte
、long
、char
等数据写到Buffer
后,position
会向前移动到下一个可插入数据的Buffer
位置。position
最大可为capacity – 1
。
(2)当读取数据时,也是从某个特定位置读。当将
Buffer
从写模式切换到读模式,position
会被重置为0
. 当从Buffer
的position
处读取数据时,position
向前移动到下一个可读的位置。
limit
(1)在写模式下,
Buffer
的limit
表示你最多能往Buffer
里写多少数据。写模式下,limit
等于Buffer
的capacity
。
(2)读模式时,
limit
表示你最多能读到多少数据。所以,当切换Buffer
到读模式时,limit
会被设置成写模式下的position
值。换句话说,你能读到以前写入的全部数据(limit
被设置成已写数据的数量,这个值在写模式下就是position
)
mark
相似于喝茶喝到剩余三分之一谁加水同样,当buffer调用它的reset方法时,当前的位置
position
会指向mark
所在位置,一样,这个也根据我的喜爱,有些人就喜欢将水喝完再添加的,因此mark
不必定总会被设定,但当它被设定值以后,那设定的这个值不能为负数,同时也不能大于position
。还有一种状况,就是我喝水喝不下了,在最后将水一口喝完,则对照的此处的话,即若是对mark
设定了值(并不是初始值-1),则在将position
或limit
调整为小于mark
的值的时候将mark
丢弃掉。若是并未对mark
从新设定值(即仍是初始值-1),那么在调用reset
方法会抛出InvalidMarkException
异常。
可见,通过包装的Buffer是Java NIO中对于缓冲区的抽象。在Java有8中基本类型:byte、short、int、long、float、double、char、boolean
,除了boolean
类型外,其余的类型都有对应的Buffer
具体实现,可见,Buffer
是一个用于存储特定基本数据类型的容器。再加上数据时有序存储的,并且Buffer
有大小限制,因此,Buffer
能够说是特定基本数据类型的线性存储有限的序列。
接着,咱们经过下面这幅图来展现下上面几个属性的关系,方便你们更好理解:
先来看一个Demo:
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
//create buffer with capacity of 48 bytes
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf); //read into buffer.
while (bytesRead != -1) {
buf.flip(); //make buffer ready for read
while(buf.hasRemaining()){
System.out.print((char) buf.get()); // read 1 byte at a time
}
buf.clear(); //make buffer ready for writing
bytesRead = inChannel.read(buf);
}
aFile.close();
复制代码
咱们抛去前两行,来总结下buffer的使用步骤:
那咱们依据上面的步骤来一一看下其相应源码实现,这里咱们使用ByteBuffer来解读。首先是Buffer分配。
//java.nio.ByteBuffer#allocate
public static ByteBuffer allocate(int capacity) {
if (capacity < 0)
throw createCapacityException(capacity);
return new HeapByteBuffer(capacity, capacity);
}
//java.nio.ByteBuffer#allocateDirect
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
复制代码
ByteBuffer
是一个抽象类,具体的实现有HeapByteBuffer
和DirectByteBuffer
。分别对应Java
堆缓冲区与堆外内存缓冲区。Java堆缓冲区本质上就是byte数组(由以前分析的,咱们只是在字节数组上面加点属性,辅以逻辑,实现一些更复杂的功能),因此实现会比较简单。而堆外内存涉及到JNI代码实现,较为复杂,因此咱们先来分析HeapByteBuffer
的相关操做,随后再专门分析DirectByteBuffer
。
咱们来看HeapByteBuffer
相关构造器源码:
//java.nio.HeapByteBuffer#HeapByteBuffer(int, int)
HeapByteBuffer(int cap, int lim) {
super(-1, 0, lim, cap, new byte[cap], 0);
/* hb = new byte[cap]; offset = 0; */
this.address = ARRAY_BASE_OFFSET;
}
//java.nio.ByteBuffer#ByteBuffer(int, int, int, int, byte[], int)
ByteBuffer(int mark, int pos, int lim, int cap,
byte[] hb, int offset)
{
super(mark, pos, lim, cap);
this.hb = hb;
this.offset = offset;
}
//java.nio.Buffer#Buffer
Buffer(int mark, int pos, int lim, int cap) {
if (cap < 0)
throw createCapacityException(cap);
this.capacity = cap;
limit(lim);
position(pos);
if (mark >= 0) {
if (mark > pos)
throw new IllegalArgumentException("mark > position: ("
+ mark + " > " + pos + ")");
this.mark = mark;
}
}
复制代码
由上,HeapByteBuffer
经过初始化字节数组hd
,在虚拟机堆上申请内存空间。 因在ByteBuffer
中定义有hb
这个字段,它是一个byte[]
类型,为了获取这个字段相对于当前这个ByteBuffer
对象所在内存地址,经过private static final long ARRAY_BASE_OFFSET = UNSAFE.arrayBaseOffset(byte[].class)
中这个UNSAFE
操做来获取这个数组第一个元素位置与该对象所在地址的相对长度,这个对象的地址表明你的头所在的位置,将这个数组看做你的鼻子,而这里返回的是你的鼻子距离头位置的那个长度,即数组第一个位置距离这个对象开始地址所在位置,这个是在class字节码加载到jvm里的时候就已经肯定了。 若是ARRAY_INDEX_SCALE = UNSAFE.arrayIndexScale(byte[].class)
为返回非零值,则可使用该比例因子以及此基本偏移量(ARRAY_BASE_OFFSET)来造成新的偏移量,以访问这个类的数组元素。知道这些,在ByteBuffer
的slice
duplicate
之类的方法,就能理解其操做了,就是计算数组中每个元素所占空间长度获得ARRAY_INDEX_SCALE
,而后当我肯定我从数组第5个位置做为该数组的开始位置操做时,我就可使用this.address = ARRAY_BASE_OFFSET + off * ARRAY_INDEX_SCALE
。 咱们再经过下面的源码对上述内容对比消化下:
//java.nio.HeapByteBuffer
protected HeapByteBuffer(byte[] buf, int mark, int pos, int lim, int cap, int off) {
super(mark, pos, lim, cap, buf, off);
/* hb = buf; offset = off; */
this.address = ARRAY_BASE_OFFSET + off * ARRAY_INDEX_SCALE;
}
public ByteBuffer slice() {
return new HeapByteBuffer(hb,
-1,
0,
this.remaining(),
this.remaining(),
this.position() + offset);
}
ByteBuffer slice(int pos, int lim) {
assert (pos >= 0);
assert (pos <= lim);
int rem = lim - pos;
return new HeapByteBuffer(hb,
-1,
0,
rem,
rem,
pos + offset);
}
public ByteBuffer duplicate() {
return new HeapByteBuffer(hb,
this.markValue(),
this.position(),
this.limit(),
this.capacity(),
offset);
}
复制代码
每一个buffer
都是可读的,但不是每一个buffer
都是可写的。这里,当buffer
有内容变更的时候,会首先调用buffer
的isReadOnly
判断此buffer
是否只读,只读buffer
是不容许更改其内容的,但mark
、position
和 limit
的值是可变的,这是咱们人为给其额外的定义,方便咱们增长功能逻辑的。当在只读buffer
上调用修改时,则会抛出ReadOnlyBufferException
异常。咱们来看buffer
的put
方法:
//java.nio.ByteBuffer#put(java.nio.ByteBuffer)
public ByteBuffer put(ByteBuffer src) {
if (src == this)
throw createSameBufferException();
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;
}
//java.nio.Buffer#remaining
public final int remaining() {
return limit - position;
}
复制代码
上面remaining
方法表示还剩多少数据未读,上面的源码讲的是,若是src
这个ByteBuffer
的src.remaining()
的数量大于要存放的目标Buffer
的还剩的空间,直接抛溢出的异常。而后经过一个for循环,将src
剩余的数据,依次写入目标Buffer
中。接下来,咱们经过src.get()
来探索下Buffer
的读操做。
//java.nio.HeapByteBuffer#get()
public byte get() {
return hb[ix(nextGetIndex())];
}
public byte get(int i) {
return hb[ix(checkIndex(i))];
}
//java.nio.HeapByteBuffer#ix
protected int ix(int i) {
return i + offset;
}
//java.nio.Buffer#nextGetIndex()
final int nextGetIndex() {
if (position >= limit)
throw new BufferUnderflowException();
return position++;
}
复制代码
这里,为了依次读取数组中的数据,这里使用nextGetIndex()
来获取要读位置,即先返回当前要获取的位置值,而后position本身再加1。以此在前面ByteBuffer#put(java.nio.ByteBuffer)
所示源码中的for
循环中依次对剩余数据的读取。上述get(int i)
不过是从指定位置获取数据,实现也比较简单HeapByteBuffer#ix
也只是肯定所要获取此数组对象指定位置数据,其中的offset
表示第一个可读字节在该字节数组中的位置(就比如我喝茶杯底三分之一水是不喝的,每次都从三分之一水量开始位置计算喝了多少或者加入多少水)。 接下来看下单个字节存储到指定字节数组的操做,与获取字节数组单个位置数据相对应,代码比较简单:
//java.nio.HeapByteBuffer#put(byte)
public ByteBuffer put(byte x) {
hb[ix(nextPutIndex())] = x;
return this;
}
public ByteBuffer put(int i, byte x) {
hb[ix(checkIndex(i))] = x;
return this;
}
//java.nio.Buffer#nextPutIndex()
final int nextPutIndex() { // package-private
if (position >= limit)
throw new BufferOverflowException();
return position++;
}
复制代码
前面的都是单个字节的,下面来说下批量操做字节数组是如何进行的,因过程知识点重复,这里只讲get,先看源码:
//java.nio.ByteBuffer#get(byte[])
public ByteBuffer get(byte[] dst) {
return get(dst, 0, dst.length);
}
//java.nio.ByteBuffer#get(byte[], int, int)
public ByteBuffer get(byte[] dst, int offset, int length) {
// 检查参数是否越界
checkBounds(offset, length, dst.length);
// 检查要获取的长度是否大于Buffer中剩余的数据长度
if (length > remaining())
throw new BufferUnderflowException();
int end = offset + length;
for (int i = offset; i < end; i++)
dst[i] = get();
return this;
}
//java.nio.Buffer#checkBounds
static void checkBounds(int off, int len, int size) { // package-private
if ((off | len | (off + len) | (size - (off + len))) < 0)
throw new IndexOutOfBoundsException();
}
复制代码
经过这个方法将这个buffer中的字节数据读到咱们给定的目标数组dst中,由checkBounds可知,当要写入目标字节数组的可写长度小于将要写入数据的长度的时候,会产生边界异常。当要获取的长度是大于Buffer中剩余的数据长度时抛出BufferUnderflowException
异常,当验证经过后,接着就从目标数组的offset
位置开始,从buffer
获取并写入offset + length
长度的数据。 能够看出,HeapByteBuffer
是封装了对byte数组的简单操做。对缓冲区的写入和读取本质上是对数组的写入和读取。使用HeapByteBuffer
的好处是咱们不用作各类参数校验,也不须要另外维护数组当前读写位置的变量了。 同时咱们能够看到,Buffer
中对position
的操做没有使用锁保护,因此Buffer
不是线程安全的。若是咱们操做的这个buffer
会有多个线程使用,则针对该buffer
的访问应经过适当的同步控制机制来进行保护。
jdk自己是没这个说法的,只是按照咱们本身的操做习惯,咱们将Buffer
分为两种工做模式,一种是接收数据模式,一种是输出数据模式。咱们能够经过Buffer
提供的flip
等操做来切换Buffer
的工做模式。
咱们来新建一个容量为10的ByteBuffer
:
ByteBuffer.allocate(10);
复制代码
由前面所学的HeapByteBuffer
的构造器中的相关代码可知,这里的position
被设置为0,并且 capacity
和limit
设置为 10,mark
设置为-1,offset
设定为0。 可参考下图展现:
新建的Buffer
处于接收数据的模式,能够向Buffer
放入数据,在放入一个对应基本类型的数据后(此处假如放入一个char类型数据),position加一,参考咱们上面所示源码,若是position已经等于limit了还进行put
操做,则会抛出BufferOverflowException
异常。 咱们向所操做的buffer中put 5个char类型的数据进去:
buffer.put((byte)'a').put((byte)'b').put((byte)'c').put((byte)'d').put((byte)'e');
复制代码
会获得以下结果视图:
由以前源码分析可知,Buffer的读写的位置变量都是基于position
来作的,其余的变量都是围绕着它进行辅助管理的,因此若是从Buffer
中读取数据,要将Buffer
切换到输出数据模式(也就是读模式)。此时,咱们就可使用Buffer
提供了flip方法。
//java.nio.Buffer#flip
public Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
复制代码
咱们知道,在put的时候,会进行java.nio.Buffer#nextPutIndex()
的调用,里面会进行position >= limit
,因此,此时再进行写操做的话,会从第0个位置开始进行覆盖,并且只能写到flip
操做以后limit
的位置。
//java.nio.Buffer#nextPutIndex()
final int nextPutIndex() { // package-private
if (position >= limit)
throw new BufferOverflowException();
return position++;
}
复制代码
在作完put
操做后,position
会自增一下,因此,flip
操做示意图以下:
也是由于position
为0了,因此咱们能够很方便的从Buffer中第0个位置开始读取数据,不须要别的附加操做。由以前解读可知,每次读取一个元素,position
就会加一,若是position
已经等于limit
还进行读取,则会抛出BufferUnderflowException
异常。
咱们经过flip
方法把Buffer
从接收写模式切换到输出读模式,若是要从输出模式切换到接收模式,可使用compact
或者clear
方法,若是数据已经读取完毕或者数据不要了,使用clear
方法,若是只想从缓冲区中释放一部分数据,而不是所有(即释放已读数据,保留未读数据),而后从新填充,使用compact
方法。
对于clear
方法,咱们先来看它的源码:
//java.nio.Buffer#clear
public Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
复制代码
咱们能够看到,它的clear
方法内并无作清理工做,只是修改位置变量,重置为初始化时的状态,等待下一次将数据写入缓冲数组。 接着,来看compact
操做的源码:
//java.nio.HeapByteBuffer#compact
public ByteBuffer compact() {
System.arraycopy(hb, ix(position()), hb, ix(0), remaining());
position(remaining());
limit(capacity());
discardMark();
return this;
}
//java.nio.ByteBuffer#position
ByteBuffer position(int newPosition) {
super.position(newPosition);
return this;
}
//java.nio.Buffer#position(int)
public Buffer position(int newPosition) {
if (newPosition > limit | newPosition < 0)
throw createPositionException(newPosition);
position = newPosition;
if (mark > position) mark = -1;
return this;
}
//java.nio.ByteBuffer#limit
ByteBuffer limit(int newLimit) {
super.limit(newLimit);
return this;
}
//java.nio.Buffer#limit(int)
public Buffer limit(int newLimit) {
if (newLimit > capacity | newLimit < 0)
throw createLimitException(newLimit);
limit = newLimit;
if (position > limit) position = limit;
if (mark > limit) mark = -1;
return this;
}
//java.nio.Buffer#discardMark
final void discardMark() {
mark = -1;
}
复制代码
这里使用了数组的拷贝操做,将未读元素转移到该字节数组从0开始的位置,因为remaining()
返回的是limit - position
,假如在flip
操做的时候填入的元素有5个,那么limit
为5,此时读到了第三个元素,也就是在调用compact
时position
的数值为2,那remaining()
的值就为3,也就是此时position
为3,compact
操做后,limit
会回归到和初始化数组容量大小同样,并将mark值置为 -1。
咱们来看示意图,在进行buffer.compact()
调用前:
buffer.compact()
调用后:
接下来,咱们再接触一些ByteBuffer
的其余方法,方便在适当的条件下进行使用。
首先来看它的源码:
//java.nio.Buffer#rewind
public Buffer rewind() {
position = 0;
mark = -1;
return this;
}
复制代码
这里就是将position
设定为0,mark
设定为-1,其余设定的管理属性(capacity
,limit
)不变。结合前面的知识,在字节数组写入数据后,它的clear
方法也只是重置咱们在Buffer
中设定的那几个加强管理属性(capacity
、position
、limit
、mark
),此处的英文表达的意思也很明显:倒带,也就是能够回头从新写,或者从新读。可是咱们要注意一个前提,咱们要确保已经恰当的设置了limit
。这个方法能够在Channel
的读或者写以前调用,如:
out.write(buf); // Write remaining data
buf.rewind(); // Rewind buffer
buf.get(array); // Copy data into array
复制代码
咱们经过下图来进行展现执行rewind
操做后的结果:
在JDK9版本中,新增了这个方法。用来建立一个与原始Buffer
同样的新Buffer
。新Buffer
的内容和原始Buffer
同样。改变新Buffer
内的数据,一样会体如今原始Buffer
上,反之亦然。两个Buffer
都拥有本身独立的 position
,limit
和mark
属性。 刚建立的新Buffer
的position
,limit
和mark
属性与原始Buffer
对应属性的值相同。 还有一点须要注意的是,若是原始Buffer
是只读的(即HeapByteBufferR
),那么新Buffer
也是只读的。若是原始Buffer
是DirectByteBuffer
,那新Buffer
也是DirectByteBuffer
。 咱们来看相关源码实现:
//java.nio.HeapByteBuffer#duplicate
public ByteBuffer duplicate() {
return new HeapByteBuffer(hb,
this.markValue(),
this.position(),
this.limit(),
this.capacity(),
offset);
}
//java.nio.HeapByteBufferR#duplicate
public ByteBuffer duplicate() {
return new HeapByteBufferR(hb,
this.markValue(),
this.position(),
this.limit(),
this.capacity(),
offset);
}
//java.nio.DirectByteBuffer#duplicate
public ByteBuffer duplicate() {
return new DirectByteBuffer(this,
this.markValue(),
this.position(),
this.limit(),
this.capacity(),
0);
}
复制代码
基本类型的参数传递都是值传递,因此由上面源码可知每一个新缓冲区都拥有本身的 position
、limit
和 mark
属性,并且他们的初始值使用了原始Buffer
此时的值。 可是,从HeapByteBuffer
角度来讲,对于hb 做为一个数组对象,属于对象引用传递,即新老Buffer
共用了同一个字节数组对象。不管谁操做,都会改变另外一个。 从DirectByteBuffer
角度来讲,直接内存看重的是地址操做,因此,其在建立这个新Buffer
的时候传入的是原始Buffer
的引用,进而能够获取到相关地址。
可使用 asReadOnlyBuffer()
方法来生成一个只读的缓冲区。这与 duplicate()
实现有些相同,除了这个新的缓冲区不容许使用put()
,而且其isReadOnly()
函数 将会返回true 。 对这一只读缓冲区调用put()
操做,会致使ReadOnlyBufferException
异常。 咱们来看相关源码:
//java.nio.ByteBuffer#put(java.nio.ByteBuffer)
public ByteBuffer put(ByteBuffer src) {
if (src == this)
throw createSameBufferException();
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;
}
//java.nio.HeapByteBuffer#asReadOnlyBuffer
public ByteBuffer asReadOnlyBuffer() {
return new HeapByteBufferR(hb,
this.markValue(),
this.position(),
this.limit(),
this.capacity(),
offset);
}
//java.nio.HeapByteBufferR#asReadOnlyBuffer
//HeapByteBufferR下直接调用其duplicate方法便可,其原本就是只读的
public ByteBuffer asReadOnlyBuffer() {
return duplicate();
}
//java.nio.DirectByteBuffer#asReadOnlyBuffer
public ByteBuffer asReadOnlyBuffer() {
return new DirectByteBufferR(this,
this.markValue(),
this.position(),
this.limit(),
this.capacity(),
0);
}
//java.nio.DirectByteBufferR#asReadOnlyBuffer
public ByteBuffer asReadOnlyBuffer() {
return duplicate();
}
//java.nio.HeapByteBufferR#HeapByteBufferR
protected HeapByteBufferR(byte[] buf, int mark, int pos, int lim, int cap, int off) {
super(buf, mark, pos, lim, cap, off);
this.isReadOnly = true;
}
//java.nio.DirectByteBufferR#DirectByteBufferR
DirectByteBufferR(DirectBuffer db,
int mark, int pos, int lim, int cap,
int off)
{
super(db, mark, pos, lim, cap, off);
this.isReadOnly = true;
}
复制代码
能够看到,ByteBuffer
的只读实现,在构造器里首先将isReadOnly
属性设定为true
。接着,HeapByteBufferR
继承了HeapByteBuffer
类(DirectByteBufferR
也是相似实现,就不重复了),并重写了全部可对buffer修改的方法。把全部能修改buffer
的方法都直接抛出ReadOnlyBufferException来保证只读。来看DirectByteBufferR
相关源码,其余对应实现同样:
//java.nio.DirectByteBufferR#put(byte)
public ByteBuffer put(byte x) {
throw new ReadOnlyBufferException();
}
复制代码
slice
从字面意思来看,就是切片,用在这里,就是分割ByteBuffer
。即建立一个从原始ByteBuffer
的当前位置(position
)开始的新ByteBuffer
,而且其容量是原始ByteBuffer
的剩余消费元素数量( limit-position
)。这个新ByteBuffer
与原始ByteBuffer
共享一段数据元素子序列,也就是设定一个offset值,这样就能够将一个相对数组第三个位置的元素看做是起点元素,此时新ByteBuffer
的position
就是0,读取的仍是所传入这个offset
的所在值。分割出来的ByteBuffer
也会继承只读和直接属性。 咱们来看相关源码:
//java.nio.HeapByteBuffer#slice()
public ByteBuffer slice() {
return new HeapByteBuffer(hb,
-1,
0,
this.remaining(),
this.remaining(),
this.position() + offset);
}
protected HeapByteBuffer(byte[] buf, int mark, int pos, int lim, int cap, int off) {
super(mark, pos, lim, cap, buf, off);
/* hb = buf; offset = off; */
this.address = ARRAY_BASE_OFFSET + off * ARRAY_INDEX_SCALE;
}
复制代码
由源码可知,新ByteBuffer
和原始ByteBuffer
共有了一个数组,新ByteBuffer
的mark
值为-1,position
值为0,limit
和capacity
都为原始Buffer
中limit-position
的值。 因而,咱们能够经过下面两幅图来展现slice
方法先后的对比。
原始ByteBuffer
:
调用slice
方法分割后获得的新ByteBuffer
:
本篇到此为止,在下一篇中,我会着重讲下DirectByteBuffer
的实现细节。
本文参考及图片来源:www.jianshu.com/p/12c81abb5…