第十八章:Java I/O系统
对程序语言的设计者而言,创建一个好的输入/输出(I/O)系统是一项艰难的任务。
- File类既能代表一个特定文件的名称,又能代表一个目录下一组文件的名称。下面展示了如何使用“目录过滤器”显示我们符合条件的File对象
// Args: "D.*\.java"
public class DirList {
public static void main(String[] args) {
File path = new File("G:\\demo");
String[] list;
// File的list()方法会为此目录对象下的每个文件名调用accept(),来判断该文件是否包含在内,判断结果由accept()返回的布尔值表示
if (args.length == 0) {
list = path.list();
} else {
list = path.list(new DirFilter(args[0]));
}
// 按照字母排序
Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);
for (String dirItem : list) {
System.out.println(dirItem);
}
}
}
class DirFilter implements FilenameFilter {
private Pattern pattern;
public DirFilter(String regex) {
this.pattern = Pattern.compile(regex);
}
@Override
public boolean accept(File dir, String name) {
return pattern.matcher(name).matches();
}
}
/* Output:
DirectoryDemo.java
DirList.java
DirList2.java
DirList3.java
*///:~
- File类不仅仅只代表存在的文件或目录,也可以用File对象来创建新的目录或尚不存在的整个目录路径。我们还可以查看文件的特性(如:大小,最后修改日期,读/写),检查某个File对象代表的是一个文件还是一个目录,并可以删除文件。下面展示了File类的一些其它方法:
// {Args: MakeDirectoriesTest}
public class MakeDirectories {
private static void usage() {
System.err.println(
"Usage:MakeDirectories path1 ...\n" +
"Creates each path\n" +
"Usage:MakeDirectories -d path1 ...\n" +
"Deletes each path\n" +
"Usage:MakeDirectories -r path1 path2\n" +
"Renames from path1 to path2");
System.exit(1);
}
private static void fileData(File f) {
System.out.println(
"绝对路径: " + f.getAbsolutePath() +
"\n 可读: " + f.canRead() +
"\n 可写: " + f.canWrite() +
"\n 文件名称: " + f.getName() +
"\n 上级目录: " + f.getParent() +
"\n 文件路径: " + f.getPath() +
"\n 文件大小: " + f.length() +
"\n 最后修改时间: " + f.lastModified());
if(f.isFile()) {
System.out.println("这是一个文件");
} else if(f.isDirectory()) {
System.out.println("这是一个目录");
}
}
public static void main(String[] args) {
if(args.length < 1) {
usage();
}
if(args[0].equals("-r")) {
if(args.length != 3) {
usage();
}
File old = new File(args[1]), rname = new File(args[2]);
old.renameTo(rname);
fileData(old);
fileData(rname);
return; // Exit main
}
int count = 0;
boolean del = false;
if(args[0].equals("-d")) {
count++;
del = true;
}
count--;
while(++count < args.length) {
File f = new File(args[count]);
if(f.exists()) {
System.out.println(f + " exists");
if(del) {
System.out.println("deleting..." + f);
f.delete();
}
}
else { // Doesn't exist
if(!del) {
f.mkdirs();
System.out.println("created " + f);
}
}
fileData(f);
}
}
}
输入和输出
编程语言的I/O类库中常使用流这个抽象概念,它代表任何有能力产出数据的数据源对象或者是有能力接收数据的接收端对象。“流”屏蔽了实际的I/O设备中处理数据的细节。Java类库中的I/O类分成输入和输出两部分,可以在JDK文档里的类层次结构中查看到。通过继承,任何自Inputstream或Reader派生而来的类都含有名为read()的基本方法,用于读取单个字节或者字节数组。同样,任何自OutputStream或Writer派生而来的类都含有名为write()的基本方法,用于写单个字节或者字节数组。但是,我们通常不会用到这些方法,它们之所以存在是因为别的类可以使用它们,以便提供更有用的接口。因此,我们很少使用单一的类来创建流对象,而是通过叠合多个对象来提供所期望的功能(这是装饰器设计模式,你将在本节中看到它)。实际上,Java中“流”类库让人迷惑的主要原因就在于∶创建单一的结果流,却需要创建多个对象。
在Java1.0中,类库的设计者首先限定与输入有关的所有类都应该从InputStream继承,而与输出有关的类都应该从OutputStream继承。但Java 1.1对基本的I/O流类库进行了重大的修改。当我们初次看见Reader和Writer类时,可能会以为这是两个用来替代InputStream和OutputStreamt的类;但实际上并非如此。尽管一些原始的“流”类库不再被使用(如果使用它们,则会收到编译器的警告信息),但是ImputStream 和OutputStreamt在以面向字节形式的I/O中仍可以提供极有价值的功能,Reader和Writer则提供兼容Unicode与面向字符的I/O功能。另外∶
1)Java 1.1向InputStream和OutputStreamt继承层次结构中添加了一些新类,所以显然这两个类是不会被取代的。
2)有时我们必须把来自于“字节”层次结构中的类和“字符”层次结构中的类结合起来使用。为了实现这个目的,要用到“适配器”(adapter)类∶InputStreamReader可以把InputStream转换为Reader,而OutputStreamWriter可以把OutputStream转换为Writer。
设计Reader和Writer继承层次结构主要是为了国际化。老的I/O流继承层次结构仅支持8位字节流,并且不能很好地处理16位的Unicode字符。由于Unicode用于字符国际化(Java本身的char也是16位的Unicode),所以添加Reader和Writer继承层次结构就是为了在所有的I/O操作中都支持Unicode。另外,新类库的设计使得它的操作比旧类库更快。