NIO系列(一) 核心概念介绍

1. 核心概念概述

  近期又深入学习了一下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端),而本地文件读写通常用不到。
  三者的关系如下:
NIO系列(一) 核心概念介绍_第1张图片

2. Channel介绍

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. 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数组结尾的后一个虚拟位置,属性如下:
NIO系列(一) 核心概念介绍_第2张图片
  假设文件只有三个字节,全部读入buffer后,position=3,position和capacity不变,如下:
NIO系列(一) 核心概念介绍_第3张图片
  此时如果想把buffer中的三个数据读出来,必须调用flip方法,因为position此时指向3,position只能向右移动,而右边是没有数据的,所以必须把position指向0,同时需要标明最大可以读取到哪个位置,因此需要把limit指向3,此时属性如下:
NIO系列(一) 核心概念介绍_第4张图片
  flip方法的源码如下,和我们分析的一致,先将limit指向position,再将position指向0:

 	public final Buffer flip() {
   		limit = position;
    	position = 0;
    	mark = -1;
    	return this;
	}

  mark属性请看描述,这里不再赘述。

4. Selector介绍

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用法
  后续介绍网络编程时详解介绍。

你可能感兴趣的:(core,java)