Java 2 平台标准版(Java 2 Platform Standard Edition,J2SE)1.4 对 Java 平台的 I/O 处理能力做了大量更改。它不仅用流到流的链接方式继续支持以前 J2SE 发行版的基于流的 I/O 操作,而且 Merlin 还添加了新的功能 — 称之为新 I/O 类(NIO),现在这些类位于
java.nio 包中。
I/O 执行输入和输出操作,将数据从文件或系统控制台等传送至或传送出应用程序。(有关 Java I/O 的其它信息,请参阅 参考资料)。
抽象的
Buffer 类是
java.nio 包支持缓冲区的基础。
Buffer
的工作方式就象内存中用于读写基本数据类型的
RandomAccessFile 。象
RandomAccessFile
一样,使用
Buffer ,所执行的下一个操作(读/写)在当前某个位置发生。执行这两个操作中的任一个都会改变那个位置,所以在写操作之后进行读操作不会读到刚才所写的内容,而会读到刚才所写内容之后的数据。
Buffer 提供了四个指示方法,用于访问线性结构(从最高值到最低值):
-
capacity():表明缓冲区的大小 -
limit():告诉您到目前为止已经往缓冲区填了多少字节,或者让您用:limit(int newLimit)来改变这个限制 -
position():告诉您当前的位置,以执行下一个读/写操作 -
mark():为了稍后用reset()进行重新设置而记住某个位置
缓冲区的基本操作是
get() 和
put() ;然而,这些方法在子类中都是针对每种数据类型的特定方法。为了说明这一情况,让我们研究一个简单示例,该示例演示了从同一个缓冲区读和写一个字符。在清单
1 中,
flip() 方法交换限制和位置,然后将位置置为 0,并废弃标记,让您读刚才所写的数据:
清单 1. 读/写示例
import java.nio.*;
...
CharBuffer buff = ...;
buff.put('A');
buff.flip();
char c = buff.get();
System.out.println("An A: " + c);
|
现在让我们研究一些具体的
Buffer 子类。
Merlin 具有 7 种特定的
Buffer 类型,每种类型对应着一个基本数据类型(不包括
boolean):
-
ByteBuffer -
CharBuffer -
DoubleBuffer -
FloatBuffer -
IntBuffer -
LongBuffer -
ShortBuffer
在本文后面,我将讨论第 8 种类型
MappedByteBuffer ,它用于内存映射文件。如果您必须使用的类型不是这些基本类型,则可以先从
ByteBuffer 获得字节类型,然后将其转换成
Object 或其它任何类型。
正如前面所提到的,每个缓冲区包含
get() 和
put() 方法,它们可以提供类型安全的版本。通常,需要重载这些
get() 和
put() 方法。例如,有了
CharBuffer ,可以用
get() 获得下一个字符,用
get(int index) 获得某个特定位置的字符,或者用
get(char[] destination) 获得一串字符。静态方法也可以创建缓冲区,因为不存在构造函数。那么,仍以
CharBuffer
为例,用
CharBuffer.wrap(aString) 可以将
String
对象转换成
CharBuffer 。为了演示,清单 2 接受第一个命令行参数,将它转换成
CharBuffer ,并显示参数中的每个字符:
清单 2. CharBuffer 演示
import java.nio.*;
public class ReadBuff {
public static void main(String args[]) {
if (args.length != 0) {
CharBuffer buff = CharBuffer.wrap(args[0]);
for (int i=0, n=buff.length(); i<n; i++) {
System.out.println(i + " : " + buff.get());
}
}
}
}
|
请注意,这里我使用了
get() ,而没有使用
get(index) 。我这样做的原因是,在每次执行
get() 操作之后,位置都会移动,所以不需要手工来声明要检索的位置。
既然已经了解了典型的缓冲区,那么让我们研究直接缓冲区与间接缓冲区之间的差别。在创建缓冲区时,可以要求创建直接缓冲区,创建直接缓冲区的成本要比创建间接缓冲区高,但这可以使运行时环境直接在该缓冲区上进行较快的本机 I/O 操作。因为创建直接缓冲区所增加的成本,所以直接缓冲区只用于长生存期的缓冲区,而不用于短生存期、一次性且用完就丢弃的缓冲区。而且,只能在
ByteBuffer 这个级别上创建直接缓冲区,如果希望使用其它类型,则必须将
Buffer 转换成更具体的类型。为了演示,清单
3 中代码的行为与清单 2 的行为一样,但清单 3 使用直接缓冲区:
清单 3. 列出网络接口
import java.nio.*;
public class ReadDirectBuff {
public static void main(String args[]) {
if (args.length != 0) {
String arg = args[0];
int size = arg.length();
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(size*2);
CharBuffer buff = byteBuffer.asCharBuffer();
buff.put(arg);
buff.rewind();
for (int i=0, n=buff.length(); i<n; i++) {
System.out.println(i + " : " + buff.get());
}
}
}
}
|
在上面的代码中,请注意,不能只是将
String 包装在直接
ByteBuffer
中。必须首先创建一个缓冲区,先填充它,然后将位置倒回起始点,这样才能从头读。还要记住,字符长度是字节长度的两倍,因此示例中会有
size*2 。
第 8 种
Buffer 类型
MappedByteBuffer 只是一种特殊的
ByteBuffer 。
MappedByteBuffer 将文件所在区域直接映射到内存。通常,该区域包含整个文件,但也可以只映射部分文件。所以,必须指定要映射文件的哪部分。而且,与其它
Buffer 对象一样,这里没有构造函数;必须让
java.nio.channels.FileChannel
的
map() 方法来获取
MappedByteBuffer 。此外,无需过多涉及通道就可以用
getChannel() 方法从
FileInputStream 或
FileOutputStream
获取
FileChannel 。通过从命令行传入文件名来读取文本文件的内容,清单 4 显示了
MappedByteBuffer :
清单 4. 读取内存映射文本文件
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;
public class ReadFileBuff {
public static void main(String args[]) throws IOException {
if (args.length != 0) {
String filename = args[0];
FileInputStream fis = new FileInputStream(filename);
FileChannel channel = fis.getChannel();
int length = (int)channel.size();
MappedByteBuffer byteBuffer =
channel.map(FileChannel.MapMode.READ_ONLY, 0, length);
Charset charset = Charset.forName("ISO-8859-1");
CharsetDecoder decoder = charset.newDecoder();
CharBuffer charBuffer = decoder.decode(byteBuffer);
for (int i=0, n=charBuffer.length(); i<n; i++) {
System.out.print(charBuffer.get());
}
}
}
}
|
正如我在“字符集”(请参阅
参考资料)这篇文章中所解释的,由于文件有内容,必须告诉系统如何将字节转换成字符。因此需要使用
Charset 。
由
Buffer 支撑的 J2SE 新的 I/O 包从根本上改变了 Java 技术处理 I/O 操作的方式。在阅读完本文之后,您应该了解了 NIO 从基本的 get 和 put 操作到读取内存映射文件方面的知识。然而,这不能说,学习
NIO 就到此为止了。使用这里所提及的参考资料来研读 Java 平台内的 I/O。在以后的文章中,我将把这里所提到的概念应用到套接字通道的使用当中。
- 您可以参阅本文在 developerWorks 全球站点上的
英文原文.
- 请参加本文的
论坛。(您也可以单击本文顶部或底部的
讨论来访问论坛)。
- 请阅读整个
Merlin 的魔力专栏系列
,其中详细讲述了如何告诉系统
Merlin 的魔力:字符集(
developerWorks,2002 年 10 月)。
- 请阅读
java.nio包的 API 文档。
- 有关 NIO 的更多信息,请阅读“
New I/O APIs”(Sun Microsystems,2002)。
- 从“
Merlin 给 Java 平台带来了非阻塞 I/O”(
developerWorks,2002 年 3 月)了解有关非阻塞 I/O 操作的更多信息。
- 在
developerWorksXML 专区,阅读另一篇有关 NIO 的相关知识的文章“
Working XML: Wrestling with Java NIO”(
developerWorks,2002 年 6 月)。
- 在 Merlin Hughes 的文章“
彻底转变流,第 2 部分:优化 Java 内部 I/O”(
developerWorks,2002 年 9 月)中,他向您展示了如何加速您的应用程序。
- 在
developerWorksJava 技术专区
,还可以找到数百篇与 Java 技术相关的参考资料。

John Zukowski 为 JZ Ventures, Inc.做战略性的 Java 咨询,同时还是许多由 jGuru社区推动的 Java FAQ的常客,充当解答高手。他最近的著作有 Apress 出版的 Learn Java with JBuilder 6 和 Sybex 出版的 Mastering Java 2: J2SE 1.4 。可以通过 jaz@zukowski.net与他联系。