概述
I/O是计算机与外部世界或者一个程序与计算机的其余部分的之间的接口, Java标准IO类库是io面向对象的一种抽象,基于本地方法的底层实现,我们无须关注底层实现。 根据数据的传输格式,可以分为以下两种: InputStream\OutputStream(字节流,一次传送一个字节)和Reader\Writer(字符流,一次一个字符)。
NIO是在JDK 1.4中引入的。NIO弥补了原来I/O的不足,它在标准Java代码中提供了高速的、面向块的I/O。原来的I/O库与NIO最重要的区别是数据打包和传输的方式的不同,原来的 I/O以流的方式处理数据,而 NIO以块的方式处理数据。 NIO与原来的I/O有同样的作用和目的,但是它使用块I/O的处理方式。每一个操作都在一步中产生或者消费一个数据块。按块处理数据比按(流式的)字节处理数据要快得多,但面向块的I/O缺少一些面向流的I/O所具有的优雅性和简单性。
基本概念
通道和缓冲区是NIO中的核心对象,通道Channel是对原I/O包中的流的模拟。到任何目的地(或来自任何地方)的所有数据都必须通过一个Channel对象。缓冲区Buffer实质上是一个容器对象。发送给一个通道的所有对象都必须首先放到缓冲区中;同样地,从通道中读取的任何数据都要读到缓冲区中。
通道
Channel是对原I/O包中的流的模拟,可以通过它读取和写入数据。所有数据都通过Buffer对象来处理,我们不会将字节直接写入通道中,而是将数据写入包含一个或者多个字节的缓冲区。同样,不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。 通道与流的不同之处在于通道是双向的,而流只是在一个方向上移动,而通道可以用于读、写或者同时用于读写。
缓冲区
Buffer是一个容器对象,它包含一些要写入或者刚读出的数据。在面向流的I/O中,您将数据直接写入或者将数据直接读到Stream对象中。 在NIO库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的。在写入数据时,它是写入到缓冲区中的。任何时候访问NIO中的数据,您都是将它放到缓冲区中。
nio使用-读写文件
读和写是I/O的基本过程,从一个通道中读取时,只需创建一个缓冲区,然后让通道将数据读到这个缓冲区中;写入时创建一个缓冲区,用数据填充它,然后让通道用这些数据来执行写入操作。
下面的示例使用nio读写结合,将一个文件的所有内容拷贝到另一个文件中。
import
java.io.FileInputStream;
import
java.io.FileOutputStream;
import
java.nio.ByteBuffer;
import
java.nio.channels.FileChannel;
public
class
CopyFile {
public
static
void
main(String[] args)
throws
Exception {
String inputfile =
"C:\\service.log"
;
String outfile =
"C:\\service.log.bak"
;
// 获取源文件和目标文件的输入输出流
FileInputStream fin =
new
FileInputStream(inputfile);
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);
}
}
}
|
文件拷贝过程如下:
1. 获取文件输入流。
2 从输入流获取通道。
3. 创建缓冲区。
4. 从通道中读取数据到缓冲区。
5. 在输出通道中写入缓冲区数据。
6. 循环步骤4-5,直至读完全部数据。
缓冲区源码学习
索引
Buffer可以理解为一组基本数据类型的元素列表,它通过几个变量来保存这个数据的当前位置状态,也就是有四个索引,如下表所示:
索引 | 说明 |
---|---|
capacity | 缓冲区数组的总长度 |
position | 下一个要操作的数据元素的位置 |
limit | 缓冲区数组中不可操作的下一个元素的位置,limit<=capacity |
mark | 用于记录当前 position 的前一个位置或者默认是0 |
主要方法
1. flip()
public
final
Buffer flip() {
limit = position;
position =
0
;
mark = -
1
;
return
this
;
}
|
将数据写到输出通道之前,调用flip()方法可以将limit设置为position,这样写数据时不会浪费时间遍历无内容的buffer;将position设置为0,便于开始写数据。
2. clear()
public
final
Buffer clear() {
position =
0
;
limit = capacity;
mark = -
1
;
return
this
;
}
|
clear()方法将position和limit复位,通常用于读取或写入数据完成后,需要将缓冲区复位时使用。
3. rewind()
public
final
Buffer rewind() {
position =
0
;
mark = -
1
;
return
this
;
}
|
rewind()方法将position复位,一般用于重复读取数据。
场景说明
假设我们需要从一个文件中读取10字节,写入到另一个文件中,缓冲区大小为20字节。采用上面的文件拷贝示例,下面看一下索引的变化过程。
1. 缓冲区初始化状态。 position=0,limit=20,capacity=20。
2. 从通道中读取6字节数据。 position=6,limit=20,capacity=20。
3. 调用flip()方法准备写入数据。 position=0, limit=6, capacity=20。
4. 向通道中写入数据。 position=6,limit=6,capacity=20。
5. 调用clear()方法复位,准备下一次读取。 position=0,limit=20,capacity=20。
参考资料
1. java编程思想,第四版。
2. 深入分析Java I/O的工作机制,http://www.ibm.com/developerworks/cn/java/j-lo-javaio/index.html?ca=drs-。
3. java nio简介,http://www.iteye.com/topic/834447。
4. NIO学习系列,http://zhangshixi.iteye.com/blog/681704。