第二个程序比第一个大约快20%,代价是不得不写一些技巧性的底层代码。
StreamTokenizer是一个杂和的类,因为它从基于字符的流中读取(象BufferedReader)数据,但同时又以字节进 行操作,即尽管它们是字母,也要用两字节的值来处理所有的字符(大于0xff)。
序列化
序列化使用一个标准格式,将任意一个Java数据结构转换为字节流。例如,如下程序输出一个随机的整数数 组:
import java.io.*;
import java.util.*;
public class serial1 {
public static void main(String args[]) {
ArrayList al = new ArrayList();
Random rn = new Random();
final int N = 100000;
for (int i = 1; i <= N; i++)
al.add(new Integer(rn.nextInt()));
try {
FileOutputStream fos =
new FileOutputStream("test.ser");
BufferedOutputStream bos =
new BufferedOutputStream(fos);
ObjectOutputStream oos =
new ObjectOutputStream(bos);
oos.writeObject(al);
oos.close();
}
catch (Throwable e) {
System.err.println(e);
}
}
}
而下面的程序又将该数组重新读入:
import java.io.*;
import java.util.*;
public class serial2 {
public static void main(String args[]) {
ArrayList al = null;
try {
FileInputStream fis =
new FileInputStream("test.ser");
BufferedInputStream bis =
new BufferedInputStream(fis);
ObjectInputStream ois =
new ObjectInputStream(bis);
al = (ArrayList)ois.readObject();
ois.close();
}
catch (Throwable e) {
System.err.println(e);
}
}
}
请注意我们使用了缓冲技术来加速I/O操作。
这里是否存在比序列化更快的方法来输出大容量的数据,然后又读取回来?除了极个别的例子外,可能没 有。例如,假定您需要以文本形式输出一个64位长的整数,而不是8字节的集合。作为文本形式的一个长整数 的最大长度大约是20个字符,也就是二进制表示的2.5倍。因此,看上去这种格式似乎不可能更快。然而,在 某些情况下,例如位图,一种特定的格式可能是一种改进的方法。然而,使用自己的方案会与序列化提供的 标准相冲突,因此,这样做不得不涉及一些权衡。
除了序列化(采用DataInputStream和DataOutputStream)实际的I/O和格式化开销外,还有其他代价,例如,当解 除序列化时需要创建新的对象。
请注意DataOutputStream的方法可能被用来开发半定制(semi-custom)的数据格式,例如:
import java.io.*;
import java.util.*;
public class binary1 {
public static void main(String args[]) {
try {
FileOutputStream fos =
new FileOutputStream("outdata");
BufferedOutputStream bos =
new BufferedOutputStream(fos);
DataOutputStream dos =
new DataOutputStream(bos);
Random rn = new Random();
final int N = 10;
dos.writeInt(N);
for (int i = 1; i <= N; i++) {
int r = rn.nextInt();
System.out.println(r);
dos.writeInt(r);
}
dos.close();
}
catch (IOException e) {
System.err.println(e);
}
}
}
和:
import java.io.*;
public class binary2 {
public static void main(String args[]) {
try {
FileInputStream fis =
new FileInputStream("outdata");
BufferedInputStream bis =
new BufferedInputStream(fis);
DataInputStream dis =
new DataInputStream(bis);
int N = dis.readInt();
for (int i = 1; i <= N; i++) {
int r = dis.readInt();
System.out.println(r);
}
dis.close();
}
catch (IOException e) {
System.err.println(e);
}
}
}
这些程序向文件写入10个整数,然后读出。
获取文件信息
到目前为止,讨论仅涉及对单个文件的输入和输出。但是,还存在加速I/O性能的另一方面,这与查找文件属 性有关。例如,考虑一个打印文件长度的小程序:
import java.io.*;
public class length1 {
public static void main(String args[]) {
if (args.length != 1) {
System.err.println("missing filename");
System.exit(1);
}
File f = new File(args[0]);
long len = f.length();
System.out.println(len);
}
}
Java运行时系统自身是无法知道文件长度的,因而必须询问底层操作系统以获得相关信息。对于其他文件信 息也是如此,例如文件是否是一个目录,最后修改时间等等。在java.io包中的File类提供了一套查询这些信 息的方法。这样的查询通常在需要很长的时间,因此应该尽量少用。
查询文件信息的一个较长的例子是从文件系统的根目录开始进行递归遍历,以输出系统中所有的文件及其路 径,该例子看上去象这样:
import java.io.*;
public class roots {
public static void visit(File f) {
System.out.println(f);
}
public static void walk(File f) {
visit(f);
if (f.isDirectory()) {
String list[] = f.list();
for (int i = 0; i < list.length; i++)
walk(new File(f, list[i]));
}
}
public static void main(String args[]) {
File list[] = File.listRoots();
for (int i = 0; i < list.length; i++) {
if (list[i].exists())
walk(list[i]);
else
System.err.println("not accessible: "
+ list[i]);
}
}
}
这个例子使用了File类的方法,例如isDirectory和exists,来遍历目录结构。每个文件依照其类型(普通文件 或目录)仅被查询一次。
更多的信息
站点:
Javatm I/O Performance Tuning
举例说明了提高I/O性能的一些方法。部分技术与在上面所提到的比较相似,其他的则涉及到底层处理问题。
Don Knuth的著作, The Art of Computer Programming, 第3卷,讨论了外部排序和搜索算法,例如对B-树的使用。