新的输入/输出 (NIO) 库是在 JDK 1.4 中引入的。NIO 弥补了原来的 I/O 的不足,它在标准 Java 代码中提供了高速的、面向块的 I/O。通过定义包含数据的类,以及通过以块的形式处理这些数据。
二.通道和缓冲区
通道
和 缓冲区
是 NIO 中的核心对象,几乎在每一个 I/O 操作中都要使用它们。
通道是对原 I/O 包中的流的模拟。到任何目的地(或来自任何地方)的所有数据都必须通过一个 Channel 对象。一个 Buffer 实质上是一个容器对象。发送给一个通道的所有对象都必须首先放到缓冲区中;同样地,从通道中读取的任何数据都要读到缓冲区中。
在本节中,您会了解到 NIO 中通道和缓冲区是如何工作的。
什么是缓冲区?
Buffer
是一个对象, 它包含一些要写入或者刚读出的数据。 在 NIO 中加入 Buffer
对象,体现了新库与原 I/O 的一个重要区别。在面向流的 I/O 中,您将数据直接写入或者将数据直接读到 Stream
对象中。
在 NIO 库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的。在写入数据时,它是写入到缓冲区中的。任何时候访问 NIO 中的数据,您都是将它放到缓冲区中。
缓冲区实质上是一个数组。通常它是一个字节数组,但是也可以使用其他种类的数组。但是一个缓冲区不 仅仅 是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。
缓冲区类型
最常用的缓冲区类型是 ByteBuffer
。一个 ByteBuffer
可以在其底层字节数组上进行 get/set 操作(即字节的获取和设置)。
ByteBuffer
不是 NIO 中唯一的缓冲区类型。事实上,对于每一种基本 Java 类型都有一种缓冲区类型:
ByteBuffer
CharBuffer
ShortBuffer
IntBuffer
LongBuffer
FloatBuffer
DoubleBuffer
每一个 Buffer
类都是 Buffer
接口的一个实例。 除了 ByteBuffer
,每一个 Buffer 类都有完全一样的操作,只是它们所处理的数据类型不一样。因为大多数标准 I/O 操作都使用 ByteBuffer
,所以它具有所有共享的缓冲区操作以及一些特有的操作。
现在您可以花一点时间运行 UseFloatBuffer.java,它包含了类型化的缓冲区的一个应用例子。
什么是通道?
Channel
是一个对象,可以通过它读取和写入数据。拿 NIO 与原来的 I/O 做个比较,通道就像是流。
正如前面提到的,所有数据都通过 Buffer
对象来处理。您永远不会将字节直接写入通道中,相反,您是将数据写入包含一个或者多个字节的缓冲区。同样,您不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。
三.读写文件示例
读文件
而在 NIO 中,情况稍有不同:我们首先从 FileInputStream
获取一个 Channel
对象,然后使用这个通道来读取数据。
在 NIO 系统中,任何时候执行一个读操作,您都是从通道中读取,但是您不是 直接 从通道读取。因为所有数据最终都驻留在缓冲区中,所以您是从通道读到缓冲区中。
因此读取文件涉及三个步骤:(1) 从 FileInputStream
获取 Channel
,(2) 创建 Buffer
,(3) 将数据从 Channel
读到 Buffer
中。
第一步是获取通道。我们从 FileInputStream
获取通道:
FileInputStream fin = new FileInputStream( "readandshow.txt" );
FileChannel fc = fin.getChannel();
|
下一步是创建缓冲区:
ByteBuffer buffer = ByteBuffer.allocate( 1024 );
|
最后,需要将数据从通道读到缓冲区中,如下所示:
fc.read( buffer );
|
您会注意到,我们不需要告诉通道要读 多少数据 到缓冲区中。每一个缓冲区都有复杂的内部统计机制,它会跟踪已经读了多少数据以及还有多少空间可以容纳更多的数据。我们将在 缓冲区内部细节 中介绍更多关于缓冲区统计机制的内容。
写文件
在 NIO 中写入文件类似于从文件中读取。首先从 FileOutputStream
获取一个通道:
FileOutputStream fout = new FileOutputStream( "writesomebytes.txt" );
FileChannel fc = fout.getChannel();
|
下一步是创建一个缓冲区并在其中放入一些数据 - 在这里,数据将从一个名为 message
的数组中取出,这个数组包含字符串 "Some bytes" 的 ASCII 字节(本教程后面将会解释 buffer.flip()
和 buffer.put()
调用)。
ByteBuffer buffer = ByteBuffer.allocate( 1024 );
for (int i=0; i<message.length; ++i) {
buffer.put( message[i] );
}
buffer.flip();
|
最后一步是写入缓冲区中:
fc.write( buffer );
|
注意在这里同样不需要告诉通道要写入多数据。缓冲区的内部统计机制会跟踪它包含多少数据以及还有多少数据要写入。