NIO的全称是non-blocking IO,也就是非阻塞IO,也有的人叫他New IO。他的核心内容主要有三部分,Channel(通道),Buffer(缓冲区), Selecto(选择器)。下面我们针对这三部分详细了解一下NIO。
Buffer(缓冲区)
Buffer缓冲是一个指定固定数据量的容器,一个连续数组。除内容之外,缓冲区还具有位置和界限,其中位置是要读写的下一个元素的索引,界限是第一个应该读写的元素的索引。java中每个非布尔基本类型都有一个缓冲区类。
Buffer通过capacity, position, limit, mark这四个变量来保存这个数据的当前位置状态,下面介绍一下这四个属性的意义。
- capacity(容量值):缓冲区数组的总长度
- position(位置):下一个要操作的数据元素的位置
- limit(极限):缓冲区数组中不可操作的下一个元素的位置
- mark(标记):用于记录当前position的位置,默认是-1
基本 Buffer 类定义了这些属性以及清除、反转和重绕方法,用以标记当前位置,以及将当前位置重置为前一个标记处。
-
clear()
使缓冲区准备好信道读取或相对放置操作的一个新的序列:它设置了限制的能力和位置为零。 -
flip()
使缓冲区准备好新的通道写入或相对获取操作序列:它将限制设置为当前位置,然后将位置设置为零。 -
rewind()
使缓冲区准备好重新读取已经包含的数据:它保持限制不变,并将位置设置为零。
举例说明Buffer中各个属性:
package stream.nio; import java.nio.ByteBuffer; public class BufferTest { public static void main(String[] args) { //创建一个10个字节的缓冲区,有两种方法 byte[] bytes = new byte[10]; ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); //ByteBuffer byteBuffer = ByteBuffer.allocate(10); System.out.println("创建缓冲区后的buffer信息为..."); printBufferInfo(byteBuffer); //为缓冲区赋值 for (int i = 0; i < 5; i++) { byteBuffer.put((byte) i); } System.out.println("初始赋值后的buffer信息为..."); printBufferInfo(byteBuffer); //bufferTest1(byteBuffer); //bufferTest2(byteBuffer); } /** * 打印Buffer中的信息 * @param byteBuffer */ public static void printBufferInfo(ByteBuffer byteBuffer) { System.out.println("limit = " + byteBuffer.limit()); System.out.println("capacity = " + byteBuffer.capacity()); System.out.println("position = " + byteBuffer.position()); } public static void bufferTest1(ByteBuffer byteBuffer) { //调用mark方法,标记此时的position byteBuffer.mark(); //继续为缓冲区赋值 byteBuffer.put((byte) 6); byteBuffer.put((byte) 7); byteBuffer.put((byte) 8); System.out.println("再次赋值后的buffer信息为..."); printBufferInfo(byteBuffer); System.out.println("-----------reset()方法---------------"); //调用reset方法,回到mark标记的position位置 byteBuffer.reset(); System.out.println("调用reset方法后的buffer信息为..."); printBufferInfo(byteBuffer); System.out.println("打印buffer position后的内容..."); while (byteBuffer.remaining() > 0) { //调用此方法后,会影响position的位置 System.out.print(byteBuffer.get()); } System.out.println(); System.out.println("-----------reset()方法---------------"); System.out.println("打印此时buffer信息为..."); printBufferInfo(byteBuffer); } public static void bufferTest2(ByteBuffer byteBuffer) { //调用flip方法,转换缓冲区的可操作性位置 byteBuffer.flip(); System.out.println("调用flip方法后的buffer信息为..."); printBufferInfo(byteBuffer); System.out.println("-----------rewind()方法---------------"); System.out.println("读取缓冲区中的信息..."); while (byteBuffer.remaining() > 0) { System.out.print(byteBuffer.get()); } System.out.println(); //调用rewind方法 byteBuffer.rewind(); System.out.println("调用rewind方法后再次读取缓冲区中的信息..."); while (byteBuffer.remaining() > 0) { System.out.print(byteBuffer.get()); } System.out.println(); System.out.println("-----------rewind()方法---------------"); //调用clear方法,转换缓冲区的可操作性位置 byteBuffer.clear(); System.out.println("调用clear方法后的buffer信息为..."); printBufferInfo(byteBuffer); } public static void bufferTest3(ByteBuffer byteBuffer) { //创建子缓存区 byteBuffer.position(3); byteBuffer.limit(7); ByteBuffer slice = byteBuffer.slice(); //子缓存区重新赋值 for (int i = 0; i < slice.capacity(); i++) { slice.put((byte) i); } byteBuffer.position(0); byteBuffer.limit(byteBuffer.capacity()); //打印之前的缓存区,值变成子缓存区的内容了 while (byteBuffer.remaining() > 0) { System.out.println(byteBuffer.get()); } } }
打印结果如下:
创建缓冲区后的buffer信息为... limit = 10 capacity = 10 position = 0 初始赋值后的buffer信息为... limit = 10 capacity = 10 position = 5
打开bufferTest1()方法测试mark()方法。运行结果如下:
再次赋值后的buffer信息为... limit = 10 capacity = 10 position = 8 -----------reset()方法--------------- 调用reset方法后的buffer信息为... limit = 10 capacity = 10 position = 5 打印buffer position后的内容... 67800 -----------reset()方法--------------- 打印此时buffer信息为... limit = 10 capacity = 10 position = 10
可以发现,reset()方法确实将position值设置到了调用mark()方法时的位置。而且读取完buffer信息后,position的位置也到了所定义的最大长度。
注释bufferTest1()方法,打开bufferTest2()方法测试rewind()等方法。运行结果如下:
调用flip方法后的buffer信息为... limit = 5 capacity = 10 position = 0 -----------rewind()方法--------------- 读取缓冲区中的信息... 01234 调用rewind方法后再次读取缓冲区中的信息... 01234 -----------rewind()方法--------------- 调用clear方法后的buffer信息为... limit = 10 capacity = 10 position = 0
从结果可知,调用flip()方法,其实就是将读取写入的部分。我们刚刚写入了五个数据,所以我们也就之能读取0-4位置的数据。rewind()方法是可以让我们重新读取一次。之前读过的数据不调用clear()方法,是不会被清除掉。
Channel(通道)
Channel是一个对象,可以通过它读取和写入数据。Channel和传统IO中的Stream很相似。主要区别为:通道是双向的,通过一个Channel既可以进行读,也可以进行写;而Stream只能进行单向操作,通过一个Stream只能进行读或者写,比如InputStream只能进行读取操作,OutputStream只能进行写操作;但是Channel中的所有数据都通过Buffer对象来处理。我们永远不会将字节直接写入通道中,相反是将数据写入包含一个或者多个字节的缓冲区。同样不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。
Selector(选择器)
与Selector有关的一个关键类是SelectionKey,一个SelectionKey表示一个到达的事件,主要有OP_ACCEPT(用于套接字准备接受操作位)、OP_CONNECT(用于套接字连接操作的操作位)、OP_READ(读操作的操作位)、OP_WRITE(写操作的操作位)。通过判断SelectionKey处于什么样的状态,进而做对应的操作。
为了避免篇幅太长,影响阅读体验,同时也是想对NIO一层一层的了解,一下灌入太多也不好,所以打算分几篇来介绍学习。上面说了一些概念的东西以及Buffer的相关使用,下一篇将会探究一下Channel和Selector。