Java NIO AIO介绍、示例及性能分析

参考:http://tutorials.jenkov.com/java-nio/index.html

1、Java NIO Tutorial

NIO最早出现在Java 1.4版本中,从那个时候开始,Java至少有两套可用的IO方面的API集,一套是标准的,另一套就是NIO,两者的工作原理不同。

Java NIO: Channels and Buffers

在标准IO系统中,用户直接与字节流或者字符流打交道。在NIO中用户总是与channel与buffer打交道,读操作意味着数据从channel读入到buffer,而写操作则意味着将buffer中的数据写入到channel。

Java NIO: Non-blocking IO

NIO能够使IO操作以非阻塞的方式工作。例如,线程可以请求channel读数据到buffer,在channel读数据到buffer的过程中,线程可以去做其它的事情,一旦数据被读入进buffer,线程可以回过头来处理buffer中的数据,将buffer中的数据写入到channel与此过程类似。

Java NIO: Selectors

NIO包含“选择器”的概念,本质上它是一个channel的多路复用器,能够监视注册在其上的channel状态,如连接打开,或者连接上有数据到达,channel处于可读状态,或者内核写缓存有剩余空间,channel处于可写状态等。这样的话,单个线程可以处理多路channel的读写,低层应该是利用操作系统提供的内核函数实现,如Linux中的selector、poll、epoll等。

本节关于NIO的介绍比较笼统,不必深究,容易把自己搞迷糊,只需要知道大概概念就可以,详细的实现原理后边的章节会介绍。

2、Java NIO Channel

Channel是Java NIO的核心概念之一,类似标准IO中的stream,但存在几个不同的点,这些很关键,如下:

  1. Channel可同时读、写,而stream是单向的,要不就只读,要不就只写。
  2. Channel可异步读写(原文:Channels can be read and written asynchronously),我觉得这句话不准确,应该是Channel可非阻塞读写,非阻塞与异步是不同的概念。
  3. Channel总是通过buffer进行读写操作。读的时候将数据从数据源写入buffer,写的时候将数据从buffer写入channel。

注意channel与stream的相同点与不同点,这很重要。

Channel Implementations

Channel是一个抽象的概念,它有如下几种具体的实现:

  • FileChannel,文件。
  • DatagramChannel,UDP。
  • SocketChannel,TCP客户端。
  • ServerSocketChannel,TCP服务端。

Basic Channel Example

下边的代码虽然简单,但基本说明了channel的工作原理,需要仔细解读,我添加了一些注释。

// 打开文件,注意使用的是RandomAccessFile类
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
// 取得FileChannel,这说明了FileChannel是如何创建出来的。
FileChannel inChannel = aFile.getChannel();
// 分配字节类型的缓存,长度是48。显然增长缓存长度可降低读写次数,但过长的话可能浪费空间。
ByteBuffer buf = ByteBuffer.allocate(48);
// 下边这行代码很关键,涉及到channel,channel低层的文件,以及刚刚分配好的buffer。
// 要求channel从文件读数据进来,并且写入到buffer中,buffer最大48个字节。
// 另外read操作不会阻塞,不管是否能读到数据,它都立即返回。
// 返回值代表读到的字节数,如果为-1表示文件结尾,它有可能是0,但不会超过48。
int bytesRead = inChannel.read(buf);
// 文件全部读完后,循环退出。这里体现出非阻塞的好处,不管有没有数据,不管是否有48个字节的数据,读操作立刻返回,线程可以做其它的事情。
while (bytesRead != -1) {
    // 输出读到的字节数
    System.out.println("Read " + bytesRead);
    // 当调用channel的read方法后,buffer处于被channel写入的状态,处于写模式。
    // 调用下边这句话后,buffer就要切换状态到读模式,此时用户可从buffer中取出数据。
    buf.flip();
    // 将buffer中的数据按字节全部取出
    while(buf.hasRemaining()){
        System.out.print((char) buf.get());
    }
    // 清空buffer中的数据
    buf.clear();
    // 继续让channel从文件读数据并写入buffer。
    bytesRead = inChannel.read(buf);
}
// 关闭文件
aFile.close();

3、Java NIO Buffer

Buffer在NIO中是另一个极其重要的概念,非常关键。在NIO模式下,用户主要与channel与buffer打交道,相对来说与buffer打交道的机会更多一些。Channel相当于是一个双向通道,将后端的数据源如文件、socket等与buffer连接起来。一旦通道建立完成,其它时间则主要与buffer打交道,此时可以把buffer看成是数据源的代理或者是用户与数据源之间的中间商,操作buffer就相当于间接操作数据源,并且操作是非阻塞的。

Buffer首先是内存中的一块存储空间,当然它不可能只是一块单纯的内存,为了协调数据源与用户,NIO中的buffer封装了一些特有的成员与方法。

Basic Buffer Usage

使用buffer读写数据,无论是读还是写都涉及四个具体的小步骤,下边分别详细说明。

读操作:

  1. 向buffer写入数据。这一步通过调用channel的read()方法实现,例如本文第二节中出现的代码:bytesRead = inChannel.read(buf);,要求channel从它后端的数据源中read出数据并写入buffer,注意channel的非阻塞特性,此时buffer处于写模式。
  2. 调用buffer的flip()方法,实现写模式到读模式的转换,表示用户接下来将从buffer中读数据。
  3. 用户从buffer中将数据读出,例如调用buffer的get()方法等。
  4. 调用buffer的clear()方法,彻底清空buffer中的数据,既使buffer中存在着用户尚未读出来的数据。或者compact()方法,只清空已经读出来的数据,未读出的数据则继续保留并移动到buffer的开始处。假如我们还需要继续从文件读数据,则继续调用channel的read()方法,并循环以上过程,只到在某个条件满足退出循环。

写操作:

  1. 用户向buffer中写入或者追加数据,此时buffer处于写模式。
  2. 调用buffer的flip()方法,实现写模式到读模式的转换,表示接下来会要求channel将buffer中数据读出并写入到后端的数据源。
  3. 从buffer中读出数据。这一步通过调用channel的write()方法实现,这要求channel从buffer中读出数据并写入到后端的数据源,注意channel的非阻塞特性。
  4. 用户判断buffer的状态,确认写入的进度,如buffer中的数据有多少已经被channel写入到后端,有多少尚未写入。用户可持续这四个步骤,直到写入全部数据。

读操作的简单代码可参考第二节。

Buffer Capacity, Position and Limit

要理解buffer如何工作,需要熟悉它的三个属性。

  • capacity
  • position
  • limit

Capacity表示buffer的容量,容量在创建buffer时指定,它是一成不变的。通过前边的介绍可知,buffer有模式,通过flip()方法可在读、写模式之间切换。模式不同,则position与limit的含义也不同。先看一张图,稍后解释它。

                                      Java NIO: Buffer capacity, position and limit in write and read mode.

看一下上图中右边的图,它表示写模式下的buffer。此时,postion代表下一个可以写入的位置,最开始时这个值是0,随着数据的增加position会持续变大。而limit此时与capacity相同,表示position的最大值限制,postion不能超过limit的限制。

将buffer由写模式切换到读模式后,postion与limit的含义及值均发生变化。postion指向buffer开始的位置,也就是0。而limit则指向写模式下的postion的位置。随着读操作的进行,postion的值增加,但它一定小于limit的值,因为limit及其以后的空间是无效的,还没有写入数据。

假如再将buffer由读模式切换到写模式,有三种可能。

  1. 如果在读模式下buffer中的数据即没有通过clear()方法彻底清空,也没有通过compact()方法将已经读取的数据清空,则buffer切换回写模式后什么都不会变,相当于没有切换。
  2. 如果在读模式下buffer中的数据通过clear()方法彻底清空,则切换回写模式后,postion将指向0的位置,相当于这是一个空的buffer。
  3. 如果在读模式下buffer中的数据通过调用compact()方法,只是将已经读取过的数据清空。则切换回写模式后,buffer中剩余的未读数据将会向buffer的顶端移动,同时postion的位置也会向顶端方向移动,也就是未读数据仍然保留。

Buffer Types

Buffer的具体实现有几下几种:

  • ByteBuffer
  • MappedByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

Allocating a Buffer

ByteBuffer buf = ByteBuffer.allocate(48);
CharBuffer buf = CharBuffer.allocate(1024);

Writing Data to a Buffer

在读操作时,由channel将后端数据写入到buffer,在写操作时,由用户将数据写入到buffer。

读操作下channel向buffer写入数据:

int bytesRead = inChannel.read(buf); //read into buffer.

这里向buffer中写入数据,但channel的方法却是read(),有点奇怪。对于channel的read()方法而言,更细致的说法是read then write,先从数据源读,然后再写入buffer。

用户向buffer与入数据:

buf.put(127);  

buffer的put()方法有很多版本,用户可以以多种方式向buffer中写入数据,如果需要可查询Java提供的随机文档。

flip()方法

调用此方法后,如果buffer处于写模式则切换到读模式,反之亦然,它内部的实现原理在介绍buffer如何工作时已经说明。

Reading Data from a Buffer

写操作下channel从buffer读数据:

//read from buffer
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值