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初始状态如下:
回想一下 ,limit决不能大于capacity,此例中这两个值都被设置为8。我们通过将它们指向数组的尾部之后(第8个槽位)来说明这点。
我们再将position设置为0。表示如果我们读一些数据到缓冲区中,那么下一个读取的数据就进入 slot 0。如果我们从缓冲区写一些数据,从缓冲区读取的下一个字节就来自slot 0。position设置如下所示:
由于缓冲区的最大数据容量capacity不会改变,所以我们在下面的讨论中可以忽略它。
第一次读取:
现在我们可以开始在新创建的缓冲区上进行读/写操作了。首先从输入通道中读一些数据到缓冲区中。第一次读取得到三个字节。它们被放到数组中从position开始的位置,这时position被设置为0。读完之后,position就增加到了3,如下所示,limit没有改变。
第二次读取:
在第二次读取时,我们从输入通道读取另外两个字节到缓冲区中。这两个字节储存在由position所指定的位置上, position因而增加2,limit没有改变。
flip:
现在我们要将数据写到输出通道中。在这之前,我们必须调用flip()方法。 其源代码如下:
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
上一个图显示了在flip之前缓冲区的情况。下面是在flip之后的缓冲区:
我们现在可以将数据从缓冲区写入通道了。position被设置为0,这意味着我们得到的下一个字节是第一个字节。limit已被设置为原来的position,这意味着它包括以前读到的所有字节,并且一个字节也不多。
第一次写入:
在第一次写入时,我们从缓冲区中取四个字节并将它们 写入输出通道。这使得position增加到4,而limit不变,如下所示:
第二次写入:
我们只剩下一个字节可写了。limit在我们调用flip()时被设置为5,并且position不能超过limit。 所以最后一次写入操作从缓冲区取出一个字节并将它写入输出通道。这使得position增加到5,并保持limit不变,如下所示:
clear:
最后一步是调用缓冲区的clear()方法。这个方法重设缓冲区以便接收更多的字节。其源代码如下:
public final Buffer clear() {
osition = 0;
limit = capacity;
mark = -1;
return this;
}
下面我们来看一个使用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入门