NIO源码分析之Buffer
NIO博大精深,探寻NIO的源码后收获很多,在此进行记录。这次主要分析Buffer的源码,主要有以下几个内容。
- 绝对方法和相对方法(position,limit,capacity属性的含义)
- Clearing,flipping,and rewinding 的作用
- 线程不安全
- 链式调用
- 常用API源码分析
绝对方法和相对方法
- 相对方法: limit值与position值会在操作时被考虑到.
- 绝对方法,完全忽略掉limit值与position值.
三个重要属性的含义: position, limit, capacity
源码中的解析:
A buffer’s capacity is the number of elements it contains. The capacity of a buffer is never negative and never changes.
一个buffer的capacity是它锁包含的元素的数量。buffer的capacity不为负而且不能改变。
A buffer’s limit is the index of the first element that should not be read or written. A buffer’s limit is never negative and is never greater than its capacity.
一个buffer的limit是第一个不能被读写的元素的索引。一个buffer的limit不能为负而且不能大于它的capacity。
A buffer’s position is the index of the next element to be read or written. A buffer’s position is never negative and is never greater than its limit.
一个buffer 的position是下一个被读写的元素的索引。一个buffer 的position不能为负而且不能大于limit。
创建时调用Buffer的一个子类ByteBuffer的 allocate申请空间。返回的是HeapByteBuffer(capacity, capacity)
层层跟进
它调用了父类ByteBuffer中的构造方法
由此可得出cpacity和limit的初始值是相同的,position为0。
另外调用ByteBuffer的allocateDirect,为
它调用了DirectByteBuffer的构造方法来进行初始化。
可见limit和capacity依然是相同的。position为0。
所以初始时为:
其中capacity永不变。读了四个以后为:
flip方法:将p指向0,将limit指向原来position的位置。
这样进行写或者读的时候,随着position向limit的移动就可以成功读取响应的数据。
mark作用为进行标记,以便返回标记处。
0 <= mark <= position <= limit <= capacity
Clearing, flipping, and rewinding
- clear makes a buffer ready for a new sequence of channel-read or relative put operations: It sets the limit to the capacity and the position to zero.
使得limit和capacity归零,相当于重置。 - flip makes a buffer ready for a new sequence of channel-write or relative get operations: It sets the limit to the current position and then sets the position to zero.
使得limit放在position的位置,使position归零,用于下一次读写。 - rewind makes a buffer ready for re-reading the data that it already contains: It leaves the limit unchanged and sets the position to zero.
使得position归零,进行新的读写相当于恢复为上一次读写前的状态,进行新的读写
线程不安全
Thread safety
Buffers are not safe for use by multiple concurrent threads. If a buffer is to be used by more than one thread then access to the buffer should be controlled by appropriate synchronization.
使用多线程的时候Buffer不是多线程的,如果Buffer要在多于一个线程中使用,需要进行适当的同步。
链式调用
Invocation chaining
Methods in this class that do not otherwise have a value to return are specified to return the buffer upon which they are invoked. This allows method invocations to be chained; for example, the sequence of statements
此类中没有要返回的值的方法被指定为返回调用它们的缓冲区。这允许将方法调用链接起来;例如,语句序列
b.flip();
b.position(23);
b.limit(42);
can be replaced by the single, more compact statement
b.flip().position(23).limit(42);
常用API源码分析
- allocate()
HeapIntBuffer与IntBuffer为父子关系。
IntBuffer调用父类 IntBuffer的构造方法进行初始化
- allocateDerect() 零拷贝
直接申请堆外内存.实现了零拷贝.之前为什么要拷贝?因为直接操作堆上的内存(可以看做用户空间的内存),然后分配一个address给buffer的话,期间可能会出现GC等,导致数据内存地址发生改变.所以只好把内容拷贝给buffer。直接操作堆外内存(内核空间的内存)的话,不会出现GC,所以把地址直接给buffer,可以实现零拷贝。
进入DerecByteBuffer()方法,如下图,以看出里面用JNI,unsafe的方法直接申请内存
在Buffer类中可以找到address变量,表示在对外内存分配的内存的地址,为什么不直接放在DerectByteBuffer呢,上面注释说了,为了加快GetDirectBufferAddress的调用方法.
- mark()
将mark标记设置为position处
- reset()
重新设置position位置为之前标记的m处
- clear()
清空buffer,想象一下,即将position置零(最左端),limit被赋值为capacity(limit和capacity在最右端),一切都回到了最开始的地方.
注意: 这个数组不会理所当然的被抹去,这个数组会随着之后的写入而把之前的值给覆盖.
- flip()
翻转,意思就是把limit置为position,position置为0,mark置为-1(代表丢弃),准备给channel进行读写.
- rewind()
非常好理解,倒带,就是把position置为0,重复利用buffer,解进行下一次channel-write或者get操作.它和flip()的区别是,flip操作了limit,而这个limit是定的,只是为了再次重复利用buffer.
- remaining()&&hasRemaining()
remaining意思为剩余,顾名思义,remaining()方法利用limit - position成功得出剩余的处理的数量.
hasRemaining返回一个boolean值,表示是否还有剩余.
- slice()
Slice Buffer 和 原有 Buffer 共享相同的底层数组,但是不共享limit和position
- asReadOnlyBuffer()
点开实现类,为 HeapByteBufferR,
它的put方法全都抛出ReadOnlyBufferException()异常.
以上解析了部分核心API的源码,更多的源码,原理大概相似,可以自行看源码进行理解。下面会分析Selector的相关源码,对其进行更深入的理解。