在进行数据传输的时候,每每须要使用到缓冲区,经常使用的缓冲区就是JDK NIO类库中提供的java.nio.Buffer,实现类以下:java
在使用NIO编程时,最经常使用的是其中的ByteBuffer,本篇分析ByteBuffer内部的源码实现,顺序从父类Buffer入手,了解父类中基础API的实现,再到各个实现子类的实现。编程
Buffer数组
Buffer是存放一种特定的、原始的数据的容器。Buffer是一种特定原始类型元素的线性的有限序列集合,其核心的属性有capacity、limit、Position。缓存
capacity:Buffer的容量,表示能够容纳的元素数量app
limit:表示第一个不能够被读取或者写入的元素的位置ide
position:表示下一个被读取或者写入的位置spa
三者之间的关系以下:0<=position<=limit<=capacity翻译
Buffer只有一个构造方法:orm
这个构造方式是protected的,也就是说只有在包内能够调用。构造方法中除了capacity、limit、position外还有一个mark参数,且校验了mark参数必须小于position。这个参数很是简单,用于标记position的当前位置,在进行读取写入之类的操做以后能够经过API从新将position重置到标记的位置,对应的API为:Buffer#mark()\Buffer#reset()xml
Buffer中一个比较重要的API是Buffer#flip
这个方法就是将limit设置到position位置,将position调整到0,将mark设置为-1。
为何须要有这么一个方法调整位置呢?
这个主要和Buffer只有一个position做为游标相关,读写都是基于position的,因此在写操做完成以后须要进行读操做时,须要将limit设置为position标记有写到哪儿了,而将position 从新移到0,这样就能够读取到全部的写入数据。假设若是有两个游标分别表示读取和写入的位置,是否就能够不用这个API了呢?
Buffer中的代码都很是简单,主要就是自身属性信息的设置和返回,像返回position、返回limit信息等,展开细看。
ByteBuffer
ByteBuffer是Buffer的一个子类,是字节缓冲区。ByteBuffer在Buffer之上定义了6中操做:
经过当前位置和指定位置的方式读取和写入byte
经过get(byte[])的方式将ByteBuffer中的数据读取到byte[]中
经过put(byte[])的方式将连续大量的byte数据写入缓冲区
经过当前位置和指定位置的方式将其余类型的数据写入缓冲区或从缓冲区读取数据转换成特定类型
提供将ByteBuffer转换成其余类型的Buffer视图的方法,例如ByteBuffer#asCharBuffer
提供compact、duplicate、slice来执行一些对ByteBuffer的操做
ByteBuffer的构造方法以下:
提供了两个构造方法,相对于Buffer增长了一个byte数组和一个offset。byte数组用于存储数据,offset表示ByteBuffer背后实际用于存储的byte数据的其实位置。即你可使用一个byte数据,从它的任何一个下标开始存储数据,而不必定是0。
固然,这两个方法都是protected的,也就是说实际咱们“不能”经过这两个方法去构造咱们须要的缓冲区。
那么当咱们须要使用缓冲区的时候咱们如何去构造一个呢?ByteBuffer提供了两个API:ByteBuffer#allocateDirect、ByteBuffer#allocate
ByteBuffer#allocateDirect分配一个DirectByteBuffer,即这个缓冲区是使用堆外内存的。
ByteBuffer#allocate在JVM堆上分配一块内存。
新分配的内存position都是0,limit为容量,初始内部填充的数据都为0。
除了经过allocate去建立ByteBuffer,还有一种方式是经过wrap来包装一个byte数组,这样就可使用ByteBuffer的API来对byte数据进行操做。
由于byte数据自己在堆内,因此wrap的ByteBuffer也就是HeapByteBuffer。
offset和length将被做为ByteBuffer初始的position和limit。
allocate和wrap都是建立了“新”的ByteBuffer,这里新的含义是他们背后都有本身独立的byte数组用于存储数据。还有一类API,他们也建立ByteBuffer,可是它只是个视图,拥有本身的position、limit等属性,可是存储的byte数组是共享的:
ByteBuffer#slice:建立一个的ByteBuffer,内容是当前ByteBuffer的一个子序列,共享一个byte数组;两个ByteBuffer的position、limit、mark是独立的;新ByteBuffer的起始位置是原ByteBuffer的position位置
ByteBuffer#duplicate:“复制”一个ByteBuffer,共享存储的byte数据,拥有独立的capacity、limit、position、mark属性;若是当前ByteBuffer是DirectByteBuffer,那么新Buffer也是DirectByteBuffer,若是当前是HeapByteBuffer,那么新分配的也是HeapByteBuffer
ByteBuffer提供另一类API来将本身转换成另外一个类型的缓冲区:
ByteBuffer#asXXXBuffer:好比asLongBuffer建立一个新的LongBuffer,底层的存储仍是共享当前的byte数组,同时拥有本身的position、limit、mark属性,新Buffer的position为0,limit和capacity为原Buffer除8,由于一个long类型占用8个byte;其余asXXXBuffer方法都相似
ByteBuffer中还有一类API是提供基于当前位置或者指定位置来读写数据的:
byte getByte()
byte getByte(int index)
int getInt()
int getInt(int index)
...
这两种API的差别是没有参数的API会从当前position开始读取数据,以后会修改position位置。而经过传入index,会从index开始读取数据,不会变动position信息。因此若是只是要读取数据,并不但愿更改Buffer自己的信息(position),应该使用带有参数的方法。
ByteBuffer的内容只有这么多,接着看它的子类实现,主要是HeapByteBuffer和DirectByteBuffer。
HeapByteBuffer
HeapByteBuffer顾名思义就是JVM堆上的字节缓冲区,他用于缓存数据的byte数组就是直接在堆内申请的。默认的构造方法直接就是new一个byte数组做为数据存储的缓冲区。
HeapByteBuffer很是简单,就是实现了ByteBuffer定义的各类put和get方法,没有什么好分析的。
DirectByteBuffer
DirectByteBuffer翻译过来就是直接的字节缓冲区,它是使用直接内存的,即不从JVM的堆上分配内存。
首先看DirectByteBuffer的一个内部类:Deallocator。从类名能够看出这个类应该是作“回收的”。
从代码看,Deallocator实现了Runnable接口,run方法内的实现就是经过unsafe释放内存。
结合Cleaner就能明白Cleaner是统一的接口,返回Cleaner来执行清楚操做,而真正的内存回收在Deallocator中执行。
接着看DirectByteBuffer的构造方法:
只有一个容量做为参数,而内存是直接经过unsafe分配的,可见内存是直接分配的,而不是在堆上申请的。另外这是一个受保护的方法,也就是说用户是不能直接调用的。
另外还有几个构造方法,能够直接经过内存地址来初始化,或者经过文件描述符来初始化(For memory-mapped buffers),经过已近存在的DirectBuffer来初始化。
这些方法都是提供给MMAP之类的使用的,通常用户都不会直接调用到。
剩下的方法,像是slice、duplicate,包括经过address返回内存地址都很是简单就不描述了。
另外DirectByteBuffer内部还有一个特殊的方法是asReadOnlyBuffer方法,返回了一个DirectByteBufferR对象。下面看一下DirectByteBufferR作了些什么。
简单从方法出发,大概就是返回只读的一个对象,不能作写入操做。
实际上也是很是简单,全部的put操做都抛出了异常。剩下get和slice等也相似,再也不赘述。
-------------------------------------------------------------------------------------