一.NIO与IO:
IO: 一般泛指进行input/output操作(读写操作),Java IO其核心是字符流(inputstream/outputstream)和字节流(reader/writer)做为基本进行操作,只能做单向操作,而IO的读写方式采用流的方式进行读写操作,如图所示
对于NIO既可以说是(NEW NIO) 也是(NON Blocking IO),为什么说他的性能和效率高于IO流,其的传输方式采用块传输方式,也就是使用缓冲区(buffer),使用channel(通道)进行双向传输。
其关键三个API为:
channel(通道): 实现数据的双向输出
Buffer(缓冲区):用于存储临时的读写数据
Selector(选择器) 若干客户端在Selector中注册自己,若干channel注册到Selector中,通过选择操作选出就绪的键,通道线程来实现少量线程的为多个客户端服务
二.Buffer缓冲区
什么是缓冲区?Buffer 是一个对象, 它包含一些要写入或者刚读出的数据。 在 NIO 中加入 Buffer 对象,体现了新库与原 I/O 的一个重要区别。在面向流的 I/O 中,您将数据直接写入或者将数据直接读到 Stream 对象中。
在内存中开辟一段连续的区域,进行临时存储,实际上缓冲区为一个数组,在Buffer中可以进行存储的数据类型为:
-
CharBuffer
-
FloatBuffer
-
IntBuffer
-
DoubleBuffer
-
ShortBuffer
-
LongBuffer
-
ByteBuffer
这些基本的实现都继承自Buffer类。
Buffer中定义了4个属性为:
1 private int mark = -1; //此属性将在随后说到 2 private int position = 0; 3 private int limit; 4 private int capacity;
其中:
|
不变性(以1,2,3,4为从低到高排序) |
|
position |
即将被读或写的位置 |
2 |
limit |
限制最大取出值/放入值,注意:position < limit |
3 |
capacity |
缓冲区中可以存储的最大容量 |
4 |
mark |
标记位置,用于标记某一次的读取/写入的位置 |
1 |
三.以ByteBuffer为例
在进行读写操作前需要开辟一个区域,对ByteBuffer进行初始化操作。
继承体系:
ByteBuffer bf = ByteBuffer.allocate(1024);
在使用allocate初始化中,进入方法中,当容量为负时,返回一个异常,反之初始化一个HeapByteBuffer
public static ByteBuffer allocate(int capacity) { if (capacity < 0) throw new IllegalArgumentException(); return new HeapByteBuffer(capacity, capacity); }
并进行对属性进行赋值并创建一个相应容量的Byte数组
HeapByteBuffer(int cap, int lim) { // package-private super(-1, 0, lim, cap, new byte[cap], 0); /* hb = new byte[cap]; offset = 0; */ }
ByteBuffer(int mark, int pos, int lim, int cap, // package-private byte[] hb, int offset) { super(mark, pos, lim, cap); this.hb = hb; this.offset = offset; }
由此完成便开辟一个区域用来存储数据
使用position 方法,limit方法,capacity方法查询相关信息,根据上面例子创建一个1024容量的缓冲区
//存储位置 System.out.println("position:---"+bf.position()); //存储限制大小 System.out.println("limit:---"+bf.limit()); //存储容量 System.out.println("capacity:---"+bf.capacity());
此时position所指向0的位置。limiit所指向1024的位置。
随后在进行写操作时使用put进行写入操作,
1 //为缓存区中存入数据 2 System.out.println("-------------put写入-------------"); 3 //定义一个数据 4 String str= "123456"; 5 bf.put(str.getBytes()); //将结果存储到新的字节数组中。 6 System.out.println("position:---"+bf.position()); 7 System.out.println("limit:---"+bf.limit()); 8 System.out.println("capacity:---"+bf.capacity());
此时:
那么position是如何得知的那?
通过在HeapByteBuffer中position初始化为0+数据转化为byte数组后的长度加起来得到position的值。并记录
写入时,position < limit 此时limit没有变化,
1 public ByteBuffer put(byte[] src, int offset, int length) { 2 3 checkBounds(offset, length, src.length); 4 if (length > remaining()) 5 throw new BufferOverflowException(); 6 System.arraycopy(src, offset, hb, ix(position()), length); 7 position(position() + length); 8 return this;
这就完成了数据的写入,当想在缓冲区中读取数据时,在需要先切换至读取状态,使用flip方法,在Buffer中的flip方法中 ,定义了将position赋给limit 限制读取的最大长度。并使position置为0
1 public final Buffer flip() { 2 limit = position; 3 position = 0; 4 mark = -1; 5 return this; 6 }
进行切换状态
1 //写入 后切换为读取状态 2 System.out.println("-------------flip切换-------------"); 3 /* 4 * 将limit = position 5 * 令 position = 0 6 * */ 7 bf.flip(); //切换读取状态 8 System.out.println("position:---"+bf.position()); 9 System.out.println("limit:---"+bf.limit()); 10 System.out.println("capacity:---"+bf.capacity());
此时:
切换完成后,便进行读的操作。
1 //切换成功后进行读取 2 System.out.println("-------------get读取-------------"); 3 /* 4 * 从零开始读 ,读6个长度停止 5 * */ 6 bf.get(str.getBytes(), 0, 6); 7 System.out.println("position:---"+bf.position()); 8 System.out.println("limit:---"+bf.limit()); 9 System.out.println("capacity:---"+bf.capacity());
你也可以并不止于读写,还可以清空缓冲区。使用clear方法
/** * Clears this buffer. The position is set to zero, the limit is set to * the capacity, and the mark is discarded. * * <p> Invoke this method before using a sequence of channel-read or * <i>put</i> operations to fill this buffer. For example: * * <blockquote><pre> * buf.clear(); // Prepare buffer for reading * in.read(buf); // Read data</pre></blockquote> * * <p> This method does not actually erase the data in the buffer, but it * is named as if it did because it will most often be used in situations * in which that might as well be the case. </p> * * @return This buffer */ public final Buffer clear() { position = 0; limit = capacity; mark = -1; return this; }
根据官方注释解释,这里清除并非真正的清除数据,而是抹去了存在的痕迹。使它被遗忘掉。数据也是可读的。
1 //清空缓存区 2 System.out.println("-------------clear清空-------------"); 3 4 bf.clear(); 5 System.out.println("position:---"+bf.position()); 6 System.out.println("limit:---"+bf.limit()); 7 System.out.println("capacity:---"+bf.capacity()); 8 //查看实际是否存在数据,并未被清除,只是被遗忘了。 9 System.out.println((char)bf.get(0)); 10 System.out.println((char)bf.get(1));
此时:
在Buffer中还提供了一些方法如下:
rewind() |
对缓冲区存入的数据重复读 |
remaining() |
统计属性间的元素数limit - position |
hasRemaining() |
统计当前位置中是否还有数据 |