近期又深入学习了一下NIO,预计写几篇NIO相关的博客作为知识总结,博客内容主要为笔者个人理解,如果错误望指正。
Java NIO是JDK1.4引入的新语法,与传统IO不同,NIO是非阻塞且面向缓冲区(buffer)的编程。NIO有三个核心概念:Channel、Buffer与Select。
Channel: 表示IO源与目标(例如:文件、套接字)打开的连接。
Buffer: 表示缓冲区,应用程序都是通过buffer从channel中读写数据的,永远不可能直接从channel读写数据。
Selector: 选择器,是channel对象的多路复用器,可以监听多个channel的IO状态。应用程序可以通过selector获取自己感兴趣的IO事件,然后执行对应的业务逻辑。因此Selector多用在网络编程中(例如多个client通过tcp连接一个server端),而本地文件读写通常用不到。
三者的关系如下:
2.1作用
channel即管道,buffer中的数据可以通过channel进行传输,类似于传统IO总的stream,与stream不同的是channel是双向的。由于channel是双向的,他能更好的反映出底层操作系统的真实状况,因为底层操作系统的通道就是双向的。
2.2 主要实现类
实现类 | 概念 |
---|---|
FileChannel | 用于本地读取、写入、映射和操作文件的通道 |
DatagramChannel | 通过UDP读写网络中的数据通道 |
SocketChannel | 通过TCP读写网络中的数据通道 |
ServerSocketChannel | 可以监听新进来的TCP连接,对每一个新进来的连接都会创建一个SocketChannel |
2.3 channel用法
FileInputStream inputStream = new FileInputStream("test.txt");
// 从stream中获取channel
FileChannel channel = inputStream.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(128);
// 通过channel,把文件的内容写入buffer
channel.read(buffer);
3.1 作用
buffer即缓冲区,数据的载体,如:应用程序向目标(文件、套接字)写入数据,那么先将内容写入buffer,buffer通过channel的传输写入目标。若将channel比做高速公路,那么buffer可以理解为汽车。
3.2 实现类
Buffer本质上就是一块内存,底层是通过数组实现的,通过数组存储同一类型的数据,java针对主要基本类型都有具体的buffer实现(注意: boolean类型除外),如ByteBuffer、CharBuffer 、ShortBuffer 、IntBuffer 、LongBuffer 、FloatBuffer 、DoubleBuffer。
3.3 用法
下面以从文件读取数据打印为例,简单说明buffer的用法。
FileInputStream inputStream = new FileInputStream("test.txt");
FileChannel channel = inputStream.getChannel();
// 创建buffer
ByteBuffer buffer = ByteBuffer.allocate(64);
// 从管道中把文件内容写入buffer
channel.read(buffer);
// buffer读入数据后,索引属性发生变化,需要通过flip方法重置后才能读取
buffer.flip();
// 把buffer内容打印出来
while (buffer.remaining() > 0) {
byte b = buffer.get();
System.out.println("charactre: " + (char) b);
}
inputStream.close();
3.4 基本属性介绍
名称 | 概念 |
---|---|
capacity | 表示Buffer最大的数据容量,缓冲区的容量不能为负数,而且一旦创建,不可修改 |
limit | 缓冲区中当前的数据量,即位于limit之后的数据不可读写 |
position | 下一个要读取或写入的数据的索引 |
mark | 调用mark()方法来记录一个特定的位置:mark=position,然后再调用reset()可以让position恢复到标记的位置即position=mark |
下面通过图示的方式,讲解读取过程中,各属性的变化。当初始化一个buffer(容量为64的)时,position=0,limit和capacity指向buffer数组结尾的后一个虚拟位置,属性如下:
假设文件只有三个字节,全部读入buffer后,position=3,position和capacity不变,如下:
此时如果想把buffer中的三个数据读出来,必须调用flip方法,因为position此时指向3,position只能向右移动,而右边是没有数据的,所以必须把position指向0,同时需要标明最大可以读取到哪个位置,因此需要把limit指向3,此时属性如下:
flip方法的源码如下,和我们分析的一致,先将limit指向position,再将position指向0:
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
mark属性请看描述,这里不再赘述。
4.1 作用
selector是通道(SelectableChannel)的多路复用器,selector可以监听多个已注册通道的IO状态,应用程序可以设置需要监听的IO状态类型。在传统io中,通过soeket建立的连接,如果想实现监听多个连接的IO状态,那么必须一个线程对应一个socket连接,当连接多了以后必定造成线程的增多,从而导致大量线程上下文切换的开销。但是通过selector可以实现一个线程监听多个连接,从而大大减少了线程的数量以及线程切换的开销。
上面提到的SelectableChannel,是指一个支持selector进行状态检查的抽象类,在java api中,ServerSocketChannel、SocketChannel都继承了该类,但是fileChannel这种不需要监听状态的channel没有继承。
selectableChannel在Selector中注册的标识.每个Channel向Selector注册时,都将会创建一个selectionKey,selectionKey 将Channel与Selector建立了关系,并维护了channel事件。
4.2 selector的创建
通过Selector的静态方法open()进行创建
Selector selector = Selector.open();
通过源码简单介绍下创建过程,open()的源码如下:
public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
}
可以看出,首先获取SelectorProvider,然后再获取Selector。先看下SelectorProvider.provider()的逻辑,源码如下:
public static SelectorProvider provider() {
synchronized (lock) {
if (provider != null)
return provider;
return AccessController.doPrivileged(
new PrivilegedAction<SelectorProvider>() {
public SelectorProvider run() {
if (loadProviderFromProperty())
return provider;
if (loadProviderAsService())
return provider;
provider = sun.nio.ch.DefaultSelectorProvider.create();
return provider;
}
});
}
}
此方法返回Java虚拟系统范围默认的SelectorProvider,从方法注释可以看到,首先如果系统定义了java.nio.channels.spi.SelectorProvider属性,那么直接通过反射实例化该类并返回,否则,返回sun.nio.ch.DefaultSelectorProvider.create(),create()方法返回WindowsSelectorProvider,由于我自己看的源码不是openjdk,因此在类库中根本就没有sun.nio.ch这个包。因此,SelectorProvider.provider()方法返回的是WindowsSelectorProvider,WindowsSelectorProvider的openSelector方法返回的是WindowsSelectorImpl,需要下载openJdk才能看到源码,源码如下:
public class WindowsSelectorProvider extends SelectorProviderImpl {
public AbstractSelector openSelector() throws IOException {
return new WindowsSelectorImpl(this);
}
}
4.3 selector用法
后续介绍网络编程时详解介绍。