Java-NIO(2)
缓冲区
常规I/O操作存在很大缺点,主要是因为它们是阻塞的,而NIO正是为了解决常规I/O执行效率低的问题,采用非阻塞高性能运行的方式来避免出现笨拙的同步I/O带来的效率低的问题
缓冲区Buffer,在NIO的使用中地位很高,因为数据就是放到缓冲区,对数据进行处理的
NIO中的Buffer是一个用于存储基本数据类型的容器,它以类似于数组有序的方式来存储和组织数据,每个基本数据类型(除boolean)都有一个子类相对应
Buffer类
Buffer类及其子类都是抽象类,不能直接实例化,使用方式是将对应的数据类型的数组包装进入缓冲区,借助静态方法wrap实现
API
NIO缓冲区中,有四个重要的参数:
capacity-容量
limit-限制
position-位置
mark-标记
四个之间的大小关系
0 <= mark <= position <= limit <= capacity
capacity
代码包含元素的数量。值不能为负数,且不可更改
方法:
/**
* Returns this buffer's capacity.
*
* @return The capacity of this buffer
*/
public final int capacity() {
return capacity;
}
public class T1 {
public static void main(String[] args) {
byte[] bytes = new byte[]{1,2,3};
short[] shorts = new short[]{1,2,3,4};
int[] ints = new int[]{1,2,3,4,5};
long[] longs = new long[]{1,2,3,4,5,6};
float[] floats = new float[]{1,2,3,4,5,6,7};
double[] doubles = new double[]{1,2,3,4,5,6,7,8};
char[] chars = new char[]{'a','b','c','d','e'};
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
ShortBuffer shortBuffer = ShortBuffer.wrap(shorts);
IntBuffer intBuffer = IntBuffer.wrap(ints);
LongBuffer longBuffer = LongBuffer.wrap(longs);
FloatBuffer floatBuffer = FloatBuffer.wrap(floats);
DoubleBuffer doubleBuffer = DoubleBuffer.wrap(doubles);
CharBuffer charBuffer = CharBuffer.wrap(chars);
print(byteBuffer);
print(shortBuffer);
print(intBuffer);
print(longBuffer);
print(floatBuffer);
print(doubleBuffer);
print(charBuffer);
}
private static void print(Buffer buffer) {
System.out.println("Name : \n");
System.out.println(buffer.getClass().getName() + "\n");
System.out.println("Capacity : \n");
System.out.println(buffer.capacity());
System.out.println("----------------------------------");
}
}
看下ByteBuffer中wrap方法:
//将字节数组包装到缓冲区中。
//新缓冲区将由给定的字节数组支持; 也就是说,对缓冲区的修改将导致数组被修改,反之亦然。 新缓冲区的容量和限制将为array.length,其位置为零,其标记为未定义,其字节顺序为BIG_ENDIAN。 它的支持数组将是给定的数组,其数组偏移量将为零。
public static ByteBuffer wrap(byte[] array) {
return wrap(array, 0, array.length);
}
limit
何为限制?缓冲区的限制代表了第一个不应该读取或者写入元素的index(索引),limit不可以为负,且不可以大于capacity,假设position大于limit,那么将position设置为新的limit,假设mark已定义且大于新的limit,则丢弃mark
limit应用图示:
测试:
public class T2 {
public static void main(String[] args) {
char[] chars = new char[]{'a','b','c','d','e'};
CharBuffer charBuffer = CharBuffer.wrap(chars);
System.out.println("capacity : \n");
System.out.println(charBuffer.capacity());
System.out.println("");
System.out.println("Limit : \n");
System.out.println(charBuffer.limit());
System.out.println("");
charBuffer.limit(3);
System.out.println("设置调整之后 : \n");
System.out.println("capacity : \n");
System.out.println(charBuffer.capacity());
System.out.println("");
System.out.println("Limit : \n");
System.out.println(charBuffer.limit());
System.out.println("");
charBuffer.put(0,'0');
charBuffer.put(1,'1');
charBuffer.put(2,'2');
charBuffer.put(3,'3');//报错
charBuffer.put(4,'4');
charBuffer.put(5,'5');
charBuffer.put(6,'6');
}
}
Limit使用场景就是当反复向缓冲区存取数据时使用,如下图,第一次向缓冲区存储9个数据,然后读取全部9个数据,完成之后再进行第二次向缓冲区存储数据,第二次只存4个数据:
当读取会出现问题,如果读取全部1 2 3 4 E F G H I是错误的,所以要结合limit限制读取的范围,在E设置Limit,实现读取1 2 3 4这4个正确的数据
position
何为位置?它代表下一个要读取或写入元素的index,不能为负,且不能大于limit
图示:
position对应index为3,说明从此位置开始写入或者读取,直到limit结束
public class T3 {
public static void main(String[] args) {
char[] chars = new char[]{'a','b','c','d','e'};
CharBuffer charBuffer = CharBuffer.wrap(chars);
print(charBuffer);
System.out.println("-----------");
charBuffer.position(2);
print(charBuffer);
System.out.println("-----------");
charBuffer.put('1');
for (int i = 0; i < chars.length; i++) {
System.out.println(chars[i] + " ");
}
}
private static void print(Buffer buffer) {
System.out.println("Capacity : " + buffer.capacity()
+" " + "limit : " + buffer.limit() + " "
+ "position : " + buffer.position());
}
}
remaining
方法remaining作用:返回当前位置与limit之间的元素个数
图示:
源码:
/**
* Returns the number of elements between the current position and the
* limit.
*
* @return The number of elements remaining in this buffer
*/
public final int remaining() {
return limit - position;
}
public class T4 {
public static void main(String[] args) {
char[] chars = new char[]{'a','b','c','d','e'};
CharBuffer charBuffer = CharBuffer.wrap(chars);
print(charBuffer);
System.out.println("-----------");
charBuffer.position(2);
print(charBuffer);
System.out.println("-----------");
System.out.println("remaining " + charBuffer.remaining());
}
private static void print(Buffer buffer) {
System.out.println("Capacity : " + buffer.capacity()
+" " + "limit : " + buffer.limit() + " "
+ "position : " + buffer.position());
}
}
mark()
缓冲区位置设置标记
标记作用:缓冲区的标记是一个索引,在调用reset方法时,会将缓冲区的position位置重置为该索引
缓冲区的mark类似于爬山时在关键路口设置路标,为了在原路返回时找到回去的路
public class T6 {
public static void main(String[] args) {
char[] chars = new char[]{'a','b','c','d','e'};
CharBuffer charBuffer = CharBuffer.wrap(chars);
System.out.println("capacity " + charBuffer.capacity());
charBuffer.position(1);
charBuffer.mark(); // 位置1设置标记
System.out.println("position " + charBuffer.position());
System.out.println("-----------");
charBuffer.position(2);
System.out.println("position " + charBuffer.position());
charBuffer.reset();
System.out.println("---------");
System.out.println("reset \n");
System.out.println("position " + charBuffer.position());
}
}
判断只读
public class T7 {
public static void main(String[] args) {
byte[] bytes = new byte[]{1,2,3};
short[] shorts = new short[]{1,2,3,4};
int[] ints = new int[]{1,2,3,4,5};
long[] longs = new long[]{1,2,3,4,5,6};
float[] floats = new float[]{1,2,3,4,5,6,7};
double[] doubles = new double[]{1,2,3,4,5,6,7,8};
char[] chars = new char[]{'a','b','c','d','e'};
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
ShortBuffer shortBuffer = ShortBuffer.wrap(shorts);
IntBuffer intBuffer = IntBuffer.wrap(ints);
LongBuffer longBuffer = LongBuffer.wrap(longs);
FloatBuffer floatBuffer = FloatBuffer.wrap(floats);
DoubleBuffer doubleBuffer = DoubleBuffer.wrap(doubles);
CharBuffer charBuffer = CharBuffer.wrap(chars);
print(byteBuffer);
print(shortBuffer);
print(intBuffer);
print(longBuffer);
print(floatBuffer);
print(doubleBuffer);
print(charBuffer);
}
private static void print(Buffer buffer) {
System.out.println("is readOnly ? : \n" +
buffer.isReadOnly());
}
}
直接缓冲区
何为直接与非直接缓冲区?
图示:
使用非直接缓冲区存取数据,需要将数据暂存在JVM的中间缓冲区,如果有频繁的数据操作,则会大大降低软件对于数据的吞吐量,效率低下,所以就使用直接缓冲区来解决这个问题
public class T8 {
public static void main(String[] args) {
ByteBuffer byteBuffer = ByteBuffer.allocate(100);
System.out.println(byteBuffer.isDirect());
ByteBuffer byteBuffer1 = ByteBuffer.allocateDirect(100);
System.out.println(byteBuffer1.isDirect());
}
}
还原缓冲区状态
final Buffer clear():
还原缓冲区到初始状态:
位置设为0
限制设为容量
丢弃标记
源码实现:
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
此方法使用场景就是在对缓存区存储数据之前调用此方法,如:
buf.clear();
in.read(buf);
但clear方法不是真正的清除缓存区中的数据,它的清除是通过将position位置归0,再执行写入新数据的代码,将最新数据由索引位置0开始进行覆盖,新值覆盖旧值,间接的清除数据
public class T9 {
public static void main(String[] args) {
byte[] bytes = new byte[]{1,2,3,4,5,6,7};
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
byteBuffer.position(2);
byteBuffer.limit(3);
byteBuffer.mark();
byteBuffer.clear();
System.out.println("position: \n" + byteBuffer.position());
System.out.println("");
System.out.println("limit : \n" + byteBuffer.limit());
System.out.println("");
}
}
缓冲区反转
源码:
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
效果:
限制设为当前位置
位置设为0
如果定义了标记就丢弃
当向缓冲区存储数据,然后再从缓冲区读取数据时,就是使用flip方法的好地方:
A : buf.allocate(10);
B : buf.put(8);
C : 先向buf写入数据
D : buf.flip();
E : 再从buf读数据
图示:
public class T2 {
public static void main(String[] args) {
CharBuffer charBuffer = CharBuffer.allocate(20);
print(charBuffer);
charBuffer.put(" I am from China");
System.out.println("-----------------------------");
print(charBuffer);
System.out.println("-----------------------------");
charBuffer.position(0);
print(charBuffer);
System.out.println("-----------------------------");
// 以下循环多输出了6个空格 为无效的数据
// 正确的应该是只打印前面有效的字符 空格不在输出
for (int i = 0; i < charBuffer.limit(); i++) {
System.out.println(charBuffer.get());
}
/*
上面为错误的读取数据示例
*/
System.out.println("-----------------------------");
print(charBuffer);
System.out.println("-----------------------------");
// 还原缓冲区状态
charBuffer.clear();
print(charBuffer);
System.out.println("-----------------------------");
charBuffer.put(" I ams usa");
print(charBuffer);
System.out.println("-----------------------------");
/*
以下操作是自己手动实现了 flip方法的逻辑
*/
charBuffer.limit(charBuffer.position());
charBuffer.position(0);
print(charBuffer);
System.out.println("-----------------------------");
for (int i = 0; i < charBuffer.limit(); i++) {
System.out.println(charBuffer.get());
}
}
private static void print(Buffer buffer){
System.out.println("position : \n"
+ buffer.position()
+"\n"
+"limit : \n"
+buffer.limit());
}
}
判断是否有底层实现的数组
源码:
public final boolean hasArray() {
return (hb != null) && !isReadOnly;
}
public class T3 {
public static void main(String[] args) {
ByteBuffer byteBuffer = ByteBuffer.allocate(100);
byteBuffer.put((byte) 1);
byteBuffer.put((byte) 2);
System.out.println(byteBuffer.hasArray());
/* 对直接缓冲区判断 */
ByteBuffer dircet_byteBuffer = ByteBuffer.allocateDirect(100);
dircet_byteBuffer.put((byte) 1);
dircet_byteBuffer.put((byte) 2);
System.out.println(dircet_byteBuffer.hasArray());
}
}
打印true是因为使用了byte[] hb存储数据,所以hb[]对象为非空,结果就是true
打印false是因为代表byte[] hb数组为null,数据直接存在内存了
判断当前位置与限制之间是否还有剩余元素
源码:
public final boolean hasRemaining() {
return position < limit;
}
以及返回当前位置与限制之间的元素个数:
public final int remaining() {
return limit - position;
}
public static void main(String[] args) {
byte[] bytes = new byte[]{1,2,3,4,5};
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
byteBuffer.limit(3);
byteBuffer.position(2);
System.out.println("hasRemainging : \n"
+byteBuffer.hasRemaining()
+"Remainging : \n"
+byteBuffer.remaining());
}
重绕缓冲区
源码实现:
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
在一系列通道重新写入或获取的操作之前调用此方法:
out.write(buf); // Write remaining data
buf.rewind(); // Rewind buffer
buf.get(array); // Copy data into array</pre></blockquote>
rewind():标记清除,位置position归0,limit不变
rewind经常在重新读取缓冲区数据时使用
而clear()方法主要使用场景是在对缓冲区进行存储数据之前调用
flip()是缩小limit范围
三者侧重点不同:
rewind:侧重于在“重新”,在重新读取,重新写入时使用
clear():侧重于还原一切状态
flip():侧重于substring截取
public class T5 {
public static void main(String[] args) {
byte[] bytes = new byte[]{1,2,3,4,5,6,7,8};
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
print(byteBuffer);
System.out.println("");
byteBuffer.position(1);
byteBuffer.limit(3);
byteBuffer.mark();
print(byteBuffer);
System.out.println("");
byteBuffer.rewind();
print(byteBuffer);
byteBuffer.reset();
}
private static void print(Buffer buffer){
System.out.println("capacity : \n"
+ buffer.capacity()
+"\n"
+"limit : \n"
+buffer.limit()
+"\n"
+"position : \n"
+ buffer.position());
}
}
List.toArray(T[])转出数组类型
public class T6 {
public static void main(String[] args) {
ByteBuffer b1 = ByteBuffer.wrap(new byte[]{'1','2','3'});
ByteBuffer b2 = ByteBuffer.wrap(new byte[]{'1','2','3'});
ByteBuffer b3 = ByteBuffer.wrap(new byte[]{'1','2','3'});
ByteBuffer b4 = ByteBuffer.wrap(new byte[]{'1','2','3'});
ByteBuffer b5 = ByteBuffer.wrap(new byte[]{'1','2','3'});
List<ByteBuffer> list = new ArrayList<>();
list.add(b1);
list.add(b2);
list.add(b3);
list.add(b4);
list.add(b5);
ByteBuffer[] byteBuffers = new ByteBuffer[list.size()];
list.toArray(byteBuffers);
System.out.println("--------------------------");
System.out.println(byteBuffers.length);
for (int i = 0; i < byteBuffers.length; i++) {
ByteBuffer byteBuffer = byteBuffers[i];
while (byteBuffer.hasRemaining()) {
System.out.println((char)byteBuffer.get());
}
System.out.println("--------------------------");
}
}
}
参考资料
《NIO与Socket编程技术指南》