NIO浅析

来源:互联网 发布:网络企业资质怎么办理 编辑:程序博客网 时间:2024/06/06 14:00

1、NIO概述

         java.nio是在jdk1.4中新引入的类库,nio是java new io的简称,其提供了高速的、面向块的 I/O,与面向流的io相比,面向块的操作明显比面向流的快许多,面向流的I/O系统一次一个字节或者一个字符的处理数据,输入流生产一个字节/字符的数据,输出流消耗一个字节/字符的数据,而面向块的I/O系统的处理则是每一个操作都在一步中产生或者消费一个数据块,所以按块处理数据比按(流式的)字节处理数据要快得多,NIO以通道Channel和缓冲区Buffer为基础来实现面向块的IO数据处理,通道和缓冲区是NIO中的核心对象,几乎在每一个I/O操作中都要使用它们。 通道Channel是对原I/O包中的流的模拟。到任何目的地(或来自任何地方)的所有数据都必须通过一个Channel对象。缓冲区Buffer实质上是一个容器对象。发送给一个通道的所有对象都必须首先放到缓冲区中;同样地,从通道中读取的任何数据都要读到缓冲区中。

2、NIO缓冲区

NIO 中两个重要的缓冲区组件:状态变量和访问方法 (accessor)。

状态变量:每一个读/写操作都会改变缓冲区的状态。通过记录和跟踪这些变化,缓冲区就可能够内部地管理自己的资源。从Buffer的源码中可以得到其定义了三个属性:

private int position = 0;private int limit;private int capacity;


可以根据这三个属性指定缓冲区在任意时刻的状态

  1) position

缓冲区实际上就是美化了的数组。在从通道读取时,您将所读取的数据放到底层的数组中。 position 变量跟踪已经写了多少数据。更准确地说,它指定了下一个字节将放到数组的哪一个元素中。因此,如果您从通道中读三个字节到缓冲区中,那么缓冲区的position 将会设置为3,指向数组中第四个元素。

同样,在写入通道时,您是从缓冲区中获取数据。 position 值跟踪从缓冲区中获取了多少数据。更准确地说,它指定下一个字节来自数组的哪一个元素。因此如果从缓冲区写了5个字节到通道中,那么缓冲区的position 将被设置为5,指向数组的第六个元素。

 2) limixt

limit 变量表明还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。

position 总是小于或者等于 limit

3)capacity

缓冲区的 capacity 表明可以储存在缓冲区中的最大数据容量。实际上,它指定了底层数组的大小 ― 或者至少是指定了准许我们使用的底层数组的容量。

limit 决不能大于 capacity

接下来我们看一下他们是如何协同工作的:

  初始变量:
   我们首先观察一个新创建的缓冲区,以ByteBuffer为例,假设缓冲区的大小为8个字节,ByteBuffer初始状态如下:

NIO缓冲区内部实现机制
   回想一下 ,limit决不能大于capacity,此例中这两个值都被设置为8。我们通过将它们指向数组的尾部之后(第8个槽位)来说明这点。
NIO缓冲区内部实现机制
   我们再将position设置为0。表示如果我们读一些数据到缓冲区中,那么下一个读取的数据就进入 slot 0。如果我们从缓冲区写一些数据,从缓冲区读取的下一个字节就来自slot 0。position设置如下所示:
NIO缓冲区内部实现机制
   由于缓冲区的最大数据容量capacity不会改变,所以我们在下面的讨论中可以忽略它。

   第一次读取:
   现在我们可以开始在新创建的缓冲区上进行读/写操作了。首先从输入通道中读一些数据到缓冲区中。第一次读取得到三个字节。它们被放到数组中从position开始的位置,这时position被设置为0。读完之后,position就增加到了3,如下所示,limit没有改变。
NIO缓冲区内部实现机制

   第二次读取:
   在第二次读取时,我们从输入通道读取另外两个字节到缓冲区中。这两个字节储存在由position所指定的位置上, position因而增加2,limit没有改变。
NIO缓冲区内部实现机制

   flip:
   现在我们要将数据写到输出通道中。在这之前,我们必须调用flip()方法。 其源代码如下:

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


 

   这个方法做两件非常重要的事:
   i  它将limit设置为当前position。
   ii 它将position设置为0。

   上一个图显示了在flip之前缓冲区的情况。下面是在flip之后的缓冲区:

NIO缓冲区内部实现机制

   我们现在可以将数据从缓冲区写入通道了。position被设置为0,这意味着我们得到的下一个字节是第一个字节。limit已被设置为原来的position,这意味着它包括以前读到的所有字节,并且一个字节也不多。

   第一次写入:
   在第一次写入时,我们从缓冲区中取四个字节并将它们 写入输出通道。这使得position增加到4,而limit不变,如下所示:

NIO缓冲区内部实现机制


   第二次写入:
   我们只剩下一个字节可写了。limit在我们调用flip()时被设置为5,并且position不能超过limit。 所以最后一次写入操作从缓冲区取出一个字节并将它写入输出通道。这使得position增加到5,并保持limit不变,如下所示:
NIO缓冲区内部实现机制

   clear:
   最后一步是调用缓冲区的clear()方法。这个方法重设缓冲区以便接收更多的字节。其源代码如下:

public final Buffer clear() {    osition = 0;    limit = capacity;    mark = -1;    return this;}


 

   clear做两种非常重要的事情:
   i 它将limit设置为与capacity相同。
   ii 它设置position为0。
   下图显示了在调用clear()后缓冲区的状态, 此时缓冲区现在可以接收新的数据了。

NIO缓冲区内部实现机制

 

下面我们来看一个使用nio输入输出的例子:

/** * 将一个文件的所有内容拷贝到另一个文件中。 *  * CopyFile.java 执行三个基本操作: * 首先创建一个 Buffer,然后从源文件中将数据读到这个缓冲区中,然后将缓冲区写入目标文件。 * 程序不断重复 — 读、写、读、写 — 直到源文件结束。 *  * @version 1.00 2010-5-19, 10:49:46 * @since 1.5 * @author ZhangShixi */public class CopyFile {    public static void main(String[] args) throws Exception {        String infile = "C:\\copy.sql";        String outfile = "C:\\copy.txt";        // 获取源文件和目标文件的输入输出流        FileInputStream fin = new FileInputStream(infile);        FileOutputStream fout = new FileOutputStream(outfile);        // 获取输入输出通道        FileChannel fcin = fin.getChannel();        FileChannel fcout = fout.getChannel();        // 创建缓冲区        ByteBuffer buffer = ByteBuffer.allocate(1024);        while (true) {            // clear方法重设缓冲区,使它可以接受读入的数据            buffer.clear();            // 从输入通道中将数据读到缓冲区            int r = fcin.read(buffer);            // read方法返回读取的字节数,可能为零,如果该通道已到达流的末尾,则返回-1            if (r == -1) {                break;            }            // flip方法让缓冲区可以将新读入的数据写入另一个通道            buffer.flip();            // 从输出通道中将数据写入缓冲区            fcout.write(buffer);        }    }}

参考文章:NIO入门