4、Buffer
缓冲区实际上是一个容器,一个连续数组。在Java NIO中负责数据的存储,用于存储不同数据类型的数据。Buffer是一个抽象类。
(1)、子类
根据数据类型不同(boolean除外),提供了相应类型的缓冲区。以下缓冲区的管理方式几乎一致,通过allocate()方法获取非直接缓冲区,但ByteBuffer也可以通过allocateDirect()分配直接缓冲区。
①、ByteBuffer:存储字节数据到缓冲区
②、CharBuffer:存储字符数据到缓冲区
③、ShortBuffer
④、IntBuffer
⑤、FloatBuffer
⑥、LongBuffer
⑦、DoubleBuffer
这几个子类分别对应各自基本数据类型:byte、char、short、int、float、long、double。
(2)、Buffer抽象类
①、属性
a、capacity:容量,即可以容纳的最大数据量;在缓冲区创建时被设定且不能改变
b、position:位置,下一个要被读或写的元素的索引,每次读写缓冲区数据时都会改变该值,为下次读写作准备
c、limit:表示缓冲区的当前终点,不能对缓冲区超过极限的位置进行读写操作(limit后的数据不能进行读写)
d、mark:标记,调用mark()来设置mark=position,再调用reset()可以让position恢复到标记的位置
package java.nio;
public abstract class Buffer {
/**
* 0 <= mark <= position <= limit <= capacity
*/
// 容量,即可以容纳的最大数据量;在缓冲区创建时被设定且不能改变
private int capacity;
// 位置,下一个要被读或写的元素的索引,每次读写缓冲区数据时都会改变该值,为下次读写作准备
private int position = 0;
// 表示缓冲区的当前终点,不能对缓冲区超过极限的位置进行读写操作(limit后的数据不能进行读写)。
private int limit;
// 标记,调用mark()来设置mark=position,再调用reset()可以让position恢复到标记的位置
private int mark = -1;
/**
* 在此缓冲区的位置设置标记
*/
public final Buffer mark() {
mark = position;
return this;
}
/**
* 将此缓冲区的位置重置为以前标记的位置
*/
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
/**
* 清楚此缓冲区,即将各个标记恢复到初始状态,但是数据并没有真正擦除
*/
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
/**
* 反转此缓冲区(read、write)
*/
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
/**
* 重绕此缓冲区
*/
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
/**
* 返回当前位置与限制之间的元素数
*/
public final int remaining() {
return limit - position;
}
/**
* 告知在当前位置和限制之间是否有元素
*/
public final boolean hasRemaining() {
return position < limit;
}
/**
* 告知此缓冲区是否为只读缓冲区
*/
public abstract boolean isReadOnly();
/**
* 告知此缓冲区是否具有可访问的底层实现数组
* @since 1.6
*/
public abstract boolean hasArray();
/**
* 返回此缓冲区的底层实现数组
* @since 1.6
*/
public abstract Object array();
/**
* 返回此缓冲区的底层实现数组中第一个缓冲区元素的偏移量
* @since 1.6
*/
public abstract int arrayOffset();
/**
* 告知此缓冲区是否为直接缓冲区
* @since 1.6
*/
public abstract boolean isDirect();
}
(3)、ByteBuffer抽象类
package java.nio;
public abstract class ByteBuffer extends Buffer implements Comparable<java.nio.ByteBuffer>
{
/**
* 从堆空间中分配一个容量大小为capacity的byte数组作为缓冲区的byte数据存储器
*/
public static ByteBuffer allocate(int capacity) {
if (capacity < 0)
throw new IllegalArgumentException();
// 在堆中创建缓冲区,在JVM内存中。
return new HeapByteBuffer(capacity, capacity);
}
/**
* 不使用JVM堆栈而是直接通过操作系统来创建内存块用作缓冲区,它与当前操作系统能够更好的耦合,因此能进一步提高I/O操作速度。
* 但是分配直接缓冲区的系统开销很大,因此只有在缓冲区较大并长期存在,或者需要经常重用时,才使用这种缓冲区。
*/
public static ByteBuffer allocateDirect(int capacity) {
// 在物理内存中建立缓冲区,不在JVM中,通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用来进行操作。
return new DirectByteBuffer(capacity);
}
}
①、put(byte b)
相对写,向position的位置写入一个byte,并将postion+1,为下次读写作准备。
②、put(int index, byte b)
绝对写,向byteBuffer底层的bytes中下标为index的位置插入byte b,不改变position。
③、Buffer => flip()
将一个处于存数据状态的缓冲区变为一个处于准备取数据的状态。
④、byte get()
相对读,从position位置读取一个byte,并将position+1,为下次读写作准备。
⑤、byte get(int index)
绝对读,读取byteBuffer底层的bytes中下标为index的byte,不改变position。
⑥、Buffer => rewind()
把position设为0,mark设为-1,不改变limit的值。
⑦、Buffer => clear()
把position = 0;limit = capacity;mark = -1; 但是并不影响底层byte数组的内容,缓冲区的数据依然存在。
⑧、Buffer => mark()
把mark设置成position的值,标记position当前的位置。
⑨、Buffer => reset()
把position设置成mark的值,相当于之前做过一个标记,现在要退回到之前标记的地方。
(4)、直接缓冲区与非直接缓冲区
//非直接缓冲区:堆内创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//直接缓冲区:JVM内存外,直接在物理内存创建缓冲区,通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用来进行操作。
ByteBuffer direct = ByteBuffer.allocateDirect(1024);
①、直接字节缓冲区可以通过调用此类的allocateDirect()方法来创建,通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用来进行操作。此方法返回的缓冲区进行分配和取消分配所需成本通常高于非直接缓冲区。直接缓冲区的内容可以驻留在常规的垃圾回收堆之外,因为对应用程序的内存需求量造成影响并不明显。所以建议将直接缓冲区主要分配给那些易受基础系统的本级IO操作影响的大型、持久的缓冲区。
②、字节缓冲区是直接缓冲区还是非直接缓冲区可通过调用其isDirect()方法来确定,提供此方法是为了能够在性能关键型代码中执行显示缓冲区管理。
(5)、分散和聚集
NIO还支持通过多个Buffer(即 Buffer数组)完成读写操作,即Scattering和Gathering
①、Scattering(分散):将数据写入到buffer时,可以采用buffer数组,依次写入。
②、Gathering(聚集):从buffer读取数据时,可以采用buffer数组,依次读。