NIO是同步非阻塞的。NIO里面有几个概念需要了解:缓冲区(Buffer)、选择器(Selector)、通道(Channel)。
1.缓冲区(Buffer):
缓冲区实际上是一个容器对象,更直接的说,其实就是一个数组,在 NIO 库中,所有数据都是用缓冲区处理的。在读
取数据时,它是直接读到缓冲区中的; 在写入数据时,它也是写入到缓冲区中的;任何时候访问 NIO 中的数据,都
是将它放到缓冲区中。
而在面向流I/O 系统中,所有数据都是直接写入或者直接将数据读取到 Stream 对象中。
在NIO 中,所有的缓冲区类型都继承于抽象类 Buffer,最常用的就是 ByteBuffer,对于 Java 中的基本类型,基本都有
一个具体 Buffer 类型与之相对应,它们之间的继承关系如下图所示:
2.Buffer的基本使用方法:
我们说过Buffer是一个特殊数组。它里面有几个属性:
position:指定下一个将要被写入或者读取的元素索引,它的值由 get()/put()方法自动更新,在新创建一个 Buffer 对象
时,position 被初始化为0。
limit:指定还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。
capacity:指定了可以存储在缓冲区中的最大数据容量,实际上,它指定了底层数组的大小,或者至少是指定了准许我
们使用的底层数组的容量。
上面属性的关系:0 <=position <= limit <=capacity。
我们写个代码来看下这三个属性:
准备一个test文档,存放的 D盘,输入以下内容:
下面写段代码读取D盘这个文件:
package com.gupaoedu.vip.netty.io.nio.buffer; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.Channel; import java.nio.channels.FileChannel; public class BufferDemoTest { public static void main(String[] args) throws IOException { //用文件io读取d盘里面的文件 FileInputStream fileInput=new FileInputStream ("D://test.txt"); //操作文件的管道 FileChannel channel=fileInput.getChannel (); //新建一个10大小的缓冲区。(10个大小的byte数组) ByteBuffer buffer= ByteBuffer.allocate (10); outPrint("初始化",buffer); channel.read (buffer); outPrint("调用read方法",buffer); //准备操作之前,先锁定操作范围 buffer.flip(); outPrint("调用flip()", buffer); //判断有没有可读数据 while (buffer.remaining() > 0) { byte b = buffer.get(); System.out.print(((char)b)); } outPrint("调用get()", buffer); //可以理解为解锁 buffer.clear(); outPrint("调用clear()", buffer); //最后把管道关闭 channel.close(); } //获取每个阶段的buffer的三个属性 public static void outPrint(String step,ByteBuffer buffer){ System.out.println (step+":"); //容量,数组大小 System.out.print("capacity: " + buffer.capacity() + ", "); //buffer里面的position属性(当前操作数据所在的位置,也可以叫做游标) System.out.print("position: " + buffer.position() + ", "); //锁定值,flip,数据操作范围索引只能在position - limit 之间 System.out.println("limit: " + buffer.limit()); } }
输出结果:
初始化:
capacity: 10, position: 0, limit: 10
调用read方法:
capacity: 10, position: 4, limit: 10
调用flip():
capacity: 10, position: 0, limit: 4
test调用get():
capacity: 10, position: 4, limit: 4
调用clear():
capacity: 10, position: 0, limit: 10
看着代码分析下:
初始化:capacity: 10, position: 0, limit: 10 画图如下:
在调用read方法,把数据读取到缓存区里面, 如果读取4个自己的数据,则此时position 的值为 4,即下一个将要被写入的字节索引为 4,而 limit 仍然是10,如下图所示:
下一步把读取的数据写入到输出通道中,相当于从缓冲区中读取数据,在此之前,必须调用 flip()方法,该方法将会完
成两件事情:
1. 把limit 设置为当前的 position 值
2. 把position 设置为0
因为position 为0,limit为我们读取读取数据的最后一个位置。我们就可以用position 来读取放在缓存区里面的数据。
现在调用 get()方法从缓冲区中读取数据写入到输出通道,这会导致position 的增加而 limit 保持不变,但 position 不
会超过limit 的值,所以在读取我们之前写入到缓冲区中的 4个自己之后,position 和 limit的值都为 4,如下图所示:
在从缓冲区中读取数据完毕后,limit的值仍然保持在我们调用 flip()方法时的值,调用 clear()方法能够把所有的状态变
化设置为初始化时的值,如下图所示:
3.缓冲区的分配
我们看上面代码,在创建一个缓冲区对象时,会调用静态方法allocate()来指定缓冲区的容量,其实调用
allocate()相当于创建了一个指定大小的数组,并把它包装为缓冲区对象。或者我们也可以直接将一个现有的数组,包装为缓冲区对象,如下示例代码所示:
public class BufferWrapTest { public static void main(String[] args) { // 分配指定大小的缓冲区 ByteBuffer buffer1 = ByteBuffer.allocate(10); // 包装一个现有的数组 byte array[] = new byte[10]; ByteBuffer buffer2 = ByteBuffer.wrap( array ); } }