首页
会员中心
到顶部
到尾部
计算机

Java I/O 系统

时间:2020/10/14 13:43:28  作者:  来源:  查看:0  评论:0
内容摘要: 对编程语言的设计者来说,创建一套好的输入输出(I/O)系统,是一项难度极高的任务。这一点可以从解决方案的数量之多上看出端倪。这个问题难就难在它要面对的可能性太多了。不仅是因为有那么多I/O的源和目地(文件,控制台,网络连接等等),而且还有很多方法(顺序的『sequenti...

对编程语言的设计者来说,创建一套好的输入输出(I/O)系统,是一项难度极高的任务。

这一点可以从解决方案的数量之多上看出端倪。这个问题难就难在它要面对的可能性太多了。不仅是因为有那么多I/O的源和目地(文件,控制台,网络连接等等),而且还有很多方法(顺序的『sequential』,随机的『random-access』,缓存的『buffered』,二进制的『binary』,字符方式的『character』,行的『by lines』,字的『by words』,等等)。

Java类库的设计者们用"创建很多类"的办法来解决这个问题。坦率地说Java I/O系统的类实在是太多了,以至于初看起来会把人吓着(但是,具有讽刺意味的是,这种设计实际上是限制了类的爆炸性增长)。此外,Java在1.0版之后又对其I/O类库作了重大的修改,原先是面向byte的,现在又补充了面向Unicode字符的类库。为了提高性能,完善功能,JDK 1.4又加了一个nio(意思是"new I/O"。这个名字会用上很多年)。这么以来,如果你想对Java的I/O类库有个全面了解,并且做到运用自如,你就得先学习大量的类。此外,了解I/O类库的演化的历史也是相当重要的。可能你的第一反应是"别拿什么历史来烦我了,告诉我怎么用就可以了!"但问题是,如果你对这段历史一无所知,很快就会被一些有用或是没用的类给搞糊涂了。

本章会介绍Java标准类库中的各种I/O类,及其使用方法。

File 类

在介绍直接从流里读写数据的类之前,我们先介绍一下处理文件和目录的类。

File类有一个极具欺骗性的名字;或许你会认为这是一个关于文件的类,但它不是。你可以用它来表示某个文件的名字,也可以用它来表示目录里一组文件的名字。如果它表示的是一组文件,那么你还可以用list( )方法来进行查询,让它会返回String数组。由于元素数量是固定的,因此数组会比容器更好一些。如果你想要获取另一个目录的清单,再建一个File对象就是了。实际上,叫它 "FilePath"可能会更好一些。下面我们举例说明怎样使用这个类及其相关的FilenameFilter接口。

目录列表器

假设你想看看这个目录。有两个办法。一是不带参数调用list( )。它返回的是File对象所含内容的完整清单。但是,如果你要的是一个"限制性列表(restricted list)"的话 —— 比方说,你想看看所有扩展名为.java的文件 —— 那么你就得使用"目录过滤器"了。这是一个专门负责挑选显示File对象的内容的类。

下面就是源代码。看看,用了java.utils.Arrays.sort( )和11章的AlphabeticComparator之后,我们没费吹灰之力就对结果作了排序(按字母顺序):

//: c12:DirList.java

// Displays directory listing using regular expressions.

// {Args: "D.*\.java"}

import java.io.*;

import java.util.*;

import java.util.regex.*;

import com.bruceeckel.util.*;

public class DirList {

public static void main(String[] args) {

File path = new File(".");

String[] list;

if(args.length == 0)

list = path.list();

else

list = path.list(new DirFilter(args[0]));

Arrays.sort(list, new AlphabeticComparator());

for(int i = 0; i < list.length; i++)

System.out.println(list[i]);

}

}

class DirFilter implements FilenameFilter {

private Pattern pattern;

public DirFilter(String regex) {

pattern = Pattern.compile(regex);

}

public boolean accept(File dir, String name) {

// Strip path information, search for regex:

return pattern.matcher(

new File(name).getName()).matches();

}

} ///:~

DirFilter实现了FilenameFilter接口。我们来看看FilenameFilter究竟有多简单:

public interface FilenameFilter {

boolean accept(File dir, String name);

}

也就是说,这类对象的任务就是提供一个accept( )的方法。之所以要创建这个类,就是要给list( )提供一个accept( )方法,这样当list( )判断该返回哪些文件名的时候,能够"回过头来调用"accept( )方法。因此,这种结构通常被称为回调(callback)。更准确地说,由于list( )实现了基本功能,而FilenameFilter提供了"对外服务所需的算法",因此这是一种"策略模式(Strategy Pattern)"。由于list( )拿FilenameFilter对象当参数,因此你可以将任何实现FilenameFilter接口的对象传给它,并以此(甚至是在运行时)控制list( )的工作方式。回调能提高程序的灵活性。

DirFilter还告诉我们,interface只是包含了一些方法,它没说你只能写这些方法。(但是,你至少要定义接口里有的方法。) 这里我们还定义了DirFilter的构造函数。

accept( )方法需要两个参数,一个是File对象,表示这个文件是在哪个目录里面的;另一个是String,表示文件名。虽然你可以忽略它们中的一个,甚至两个都不管,但是你大概总得用一下文件名吧。记住,list( )会对目录里的每个文件调用accept( ),并以此判断是不是把它包括到返回值里;这个判断依据就是accept( )的返回值。

切记,文件名里不能有路径信息。为此你只要用一个String对象来创建File对象,然后再调用这个File对象的getName( )就可以了。它会帮你剥离路径信息(以一种平台无关的方式)。然后再在accept( )里面用正则表达式(regular expression)的matcher对象判断,regex是否与文件名相匹配。兜完这个圈子,list( )方法返回了一个数组。

匿名内部类

这是用匿名内部类(详见第八章)来重写程序的绝佳机会。下面我们先创建一个返回FilenameFilter的filter( )方法。

//: c12:DirList2.java

// Uses anonymous inner classes.

// {Args: "D.*\.java"}

import java.io.*;

import java.util.*;

import java.util.regex.*;

import com.bruceeckel.util.*;

public class DirList2 {

public static FilenameFilter filter(final String regex) {

// Creation of anonymous inner class:

return new FilenameFilter() {

private Pattern pattern = Pattern.compile(regex);

public boolean accept(File dir, String name) {

return pattern.matcher(

new File(name).getName()).matches();

}

}; // End of anonymous inner class

}

public static void main(String[] args) {

File path = new File(".");

String[] list;

if(args.length == 0)

list = path.list();

else

list = path.list(filter(args[0]));

Arrays.sort(list, new AlphabeticComparator());

for(int i = 0; i < list.length; i++)

System.out.println(list[i]);

}

} ///:~

注意,filter( )的参数必须是final的。要想在匿名内部类里使用其作用域之外的对象,只能这么做。

这是对前面所讲的代码的改进,现在FilenameFilter类已经与DirList2紧紧地绑在一起了。不过你还可以更进一步,把这个匿名内部类定义成list( )的参数,这样代码会变得更紧凑:

//: c12:DirList3.java

// Building the anonymous inner class "in-place."

// {Args: "D.*\.java"}

import java.io.*;

import java.util.*;

import java.util.regex.*;

import com.bruceeckel.util.*;

public class DirList3 {

public static void main(final String[] args) {

File path = new File(".");

String[] list;

if(args.length == 0)

list = path.list();

else

list = path.list(new FilenameFilter() {

private Pattern pattern = Pattern.compile(args[0]);

public boolean accept(File dir, String name) {

return pattern.matcher(

new File(name).getName()).matches();

}

});

Arrays.sort(list, new AlphabeticComparator());

for(int i = 0; i < list.length; i++)

System.out.println(list[i]);

}

} ///:~

现在该轮到main( )的参数成final了,因为匿名内部类要用它的arg[0]了。

这个例子告诉我们,可以用匿名内部类来创建专门供特定问题用的,一次性的类。这种做法的好处是,它能把解决某个问题的代码全都集中到一个地方。但是从另一角度来说,这样做会使代码的可读性变差,所以要慎重。

查看与创建目录

File类的功能不仅限于显示文件或目录。它还能帮你创建新的目录甚至是目录路径(directory path),如果目录不存在的话。此外它还能用来检查文件的属性(大小,上次修改的日期,读写权限等),判断File对象表示的是文件还是目录,以及删除文件。下面这段程序演示了File类的一些其他方法(请查阅JDK文档,以了解其全部功能):

//: c12:MakeDirectories.java

// Demonstrates the use of the File class to

// create directories and manipulate files.

// {Args: MakeDirectoriesTest}

import com.bruceeckel.simpletest.*;

import java.io.*;

public class MakeDirectories {

private static Test monitor = new Test();

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(

"Absolute path: " + f.getAbsolutePath() +

"\n Can read: " + f.canRead() +

"\n Can write: " + f.canWrite() +

"\n getName: " + f.getName() +

"\n getParent: " + f.getParent() +

"\n getPath: " + f.getPath() +

"\n length: " + f.length() +

"\n lastModified: " + f.lastModified());

if(f.isFile())

System.out.println("It's a file");

else if(f.isDirectory())

System.out.println("It's a directory");

}

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;

booleandel = 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);

}

if(args.length == 1 &&

args[0].equals("MakeDirectoriesTest"))

monitor.expect(new String[] {

"%% (MakeDirectoriesTest exists"+

"|created MakeDirectoriesTest)",

"%% Absolute path: "

+ "\\S+MakeDirectoriesTest",

"%%  Can read: (true|false)",

"%%  Can write: (true|false)",

" getName: MakeDirectoriesTest",

" getParent: null",

" getPath: MakeDirectoriesTest",

"%%  length: \\d+",

"%%  lastModified: \\d+",

"It's a directory"

});

}

} ///:~

在fileData( )演示了全套查询文件和目录路径信息的方法。

main( )的第一条指令就是执行renameTo( )。它会把文件重命名成(或者说移动到)新的目录,也就是参数所给出的目录。而参数本身就是一个File对象。这个方法也适用于目录。

如果你试过上面那段程序,就会发现,你能用它创建任意复杂的目录路径,因为mkdirs( )已经帮你打理好了。

输入与输出

I/O类库常使用"流(stream)"这种抽象。所谓"流"是一种能生成或接受数据的,代表数据的源和目标的对象。流把I/O设备内部的具体操作给隐藏起来了。

正如JDK文档所显示的,Java的I/O类库分成输入和输出两大部分。所有InputStream和Reader的派生类都有一个基本的,继承下来的,能读取单个或byte数组的read( )方法。同理,所有OutputStream和Writer的派生类都有一个基本的,能写入单个或byte数组的write( )方法。但通常情况下,你是不会去用这些方法的;它们是给其它类用的 —— 而后者会提供一些更实用的接口。因此,你很少会碰到只用一个类就能创建一个流的情形,实际上你得把多个对象叠起来,并以此来获取所需的功能。Java的流类库之所以会那么让人犯晕,最主要的原因就是"你必须为创建一个流而动用多个对象"。

我们最好还是根据其功能为这些class归个类。Java 1.0的类库设计者们是从决定"让所有与输入相关的类去继承InputStream"入手的。同理,所有与输出相关的类就该继承OutputStream了。

添加属性与适用的接口

使用"分层对象(layered objects)",为单个对象动态地,透明地添加功能的做法,被称为Decorator Pattern。(模式[61]是Thinking in Patterns (with Java)的主题。)Decorator模式要求所有包覆在原始对象之外的对象,都必须具有与之完全相同的接口。这使得decorator的用法变得非常的透明--无论对象是否被decorate过,传给它的消息总是相同的。这也是Java I/O类库要有"filter(过滤器)"类的原因:抽象的"filter"类是所有decorator的基类。(decorator必须具有与它要包装的对象的全部接口,但是decorator可以扩展这个接口,由此就衍生出了很多"filter"类)。

Decorator模式常用于如下的情形:如果用继承来解决各种需求的话,类的数量会多到不切实际的地步。Java的I/O类库需要提供很多功能的组合,于是decorator模式就有了用武之地。 但是decorator有个缺点,在提高编程的灵活性的同时(因为你能很容易地混合和匹配属性),也使代码变得更复杂了。Java的I/O类库之所以会这么怪,就是因为它"必须为一个I/O对象创建很多类",也就是为一个"核心"I/O类加上很多decorator。

为InputStream和OutputStream定义decorator类接口的类,分别是FilterInputStream和FilterOutputStream。这两个名字都起得不怎么样。FilterInputStream和FilterOutputStream都继承自I/O类库的基类InputStream和OutputStream,这是decorator模式的关键(惟有这样decorator类的接口才能与它要服务的对象的完全相同)。

用FilterInputStream读取InputStream

FilterInputStream及其派生类有两项重要任务。DataInputStream可以读取各种primitive及String。(所有的方法都以"read"打头,比如readByte( ), readFloat( ))。它,以及它的搭档DataOutputStream,能让你通过流将primitive数据从一个地方导到另一个地方。这些"地方"都列在表12-1里。

其它的类都是用来修改InputStream的内部行为的:是不是做缓冲,是不是知道它所读取的行信息(允许你读取行号或设定行号),是不是会弹出单个字符。后两个看上去更像是给编译器用的(也就是说,它们大概是为Java编译器设计的),所以通常情况下,你是不大会用到它们的。

不论你用哪种I/O设备,输入的时候,最好都做缓冲。所以对I/O类库来说,比较明智的做法还是把不缓冲当特例(或者去直接调用方法),而不是像现在这样把缓冲当作特例。

  


相关评论
广告联系QQ:45157718 点击这里给我发消息 电话:13516821613 杭州余杭东港路118号雷恩国际科技创新园  网站技术支持:黄菊华互联网工作室 浙ICP备06056032号