第10章 Java IO系统Word文件下载.docx
《第10章 Java IO系统Word文件下载.docx》由会员分享,可在线阅读,更多相关《第10章 Java IO系统Word文件下载.docx(41页珍藏版)》请在冰豆网上搜索。
一般情况下,我们都是将多个对象重叠在一起,提供自己期望的功能。
我们之所以感到Java的流库(StreamLibrary)异常复杂,正是由于为了创建单独一个结果流,却需要创建多个对象的缘故。
很有必要按照功能对类进行分类。
库的设计者首先决定与输入有关的所有类都从InputStream继承,而与输出有关的所有类都从OutputStream继承。
10.1.1InputStream的类型
InputStream的作用是标志那些从不同起源地产生输入的类。
这些起源地包括(每个都有一个相关的InputStream子类):
(1)字节数组
(2)String对象
(3)文件
(4)“管道”,它的工作原理与现实生活中的管道类似:
将一些东西置入一端,它们在另一端出来。
(5)一系列其他流,以便我们将其统一收集到单独一个流内。
(6)其他起源地,如Internet连接等(将在本书后面的部分讲述)。
除此以外,FilterInputStream也属于InputStream的一种类型,用它可为“破坏器”类提供一个基础类,以便将属性或者有用的接口同输入流连接到一起。
这将在以后讨论。
表10.1InputStream的类型
类功能构建器参数/如何使用
ByteArrayInputStream允许内存中的一个缓冲区作为InputStream使用从中提取字节的缓冲区/作为一个数据源使用。
通过将其同一个FilterInputStream对象连接,可提供一个有用的接口
StringBufferInputStream将一个String转换成InputStream一个String(字串)。
基础的实施方案实际采用一个StringBuffer(字串缓冲)/作为一个数据源使用。
FileInputStream用于从文件读取信息代表文件名的一个String,或者一个File或FileDescriptor对象/作为一个数据源使用。
PipedInputString产生为相关的PipedOutputStream写的数据。
实现了“管道化”的概念PipedOutputStream/作为一个数据源使用。
SequenceInputStream将两个或更多的InputStream对象转换成单个InputStream使用两个InputStream对象或者一个Enumeration,用于InputStream对象的一个容器/作为一个数据源使用。
FilterInputStream对作为破坏器接口使用的类进行抽象;
那个破坏器为其他InputStream类提供了有用的功能。
参见表10.3参见表10.3/参见表10.3
10.1.2OutputStream的类型
这一类别包括的类决定了我们的输入往何处去:
一个字节数组(但没有String;
假定我们可用字节数组创建一个);
一个文件;
或者一个“管道”。
除此以外,FilterOutputStream为“破坏器”类提供了一个基础类,它将属性或者有用的接口同输出流连接起来。
表10.2OutputStream的类型
ByteArrayOutputStream在内存中创建一个缓冲区。
我们发送给流的所有数据都会置入这个缓冲区。
可选缓冲区的初始大小/用于指出数据的目的地。
若将其同FilterOutputStream对象连接到一起,可提供一个有用的接口
FileOutputStream将信息发给一个文件用一个String代表文件名,或选用一个File或FileDescriptor对象/用于指出数据的目的地。
PipedOutputStream我们写给它的任何信息都会自动成为相关的PipedInputStream的输出。
实现了“管道化”的概念PipedInputStream/为多线程处理指出自己数据的目的地/将其同FilterOutputStream对象连接到一起,便可提供一个有用的接口
FilterOutputStream对作为破坏器接口使用的类进行抽象处理;
那个破坏器为其他OutputStream类提供了有用的功能。
参见表10.4参见表10.4/参见表10.4
10.2增添属性和有用的接口
利用层次化对象动态和透明地添加单个对象的能力的做法叫作“装饰器”(Decorator)方案——“方案”属于本书第16章的主题(注释①)。
装饰器方案规定封装于初始化对象中的所有对象都拥有相同的接口,以便利用装饰器的“透明”性质——我们将相同的消息发给一个对象,无论它是否已被“装饰”。
这正是在JavaI/O库里存在“过滤器”(Filter)类的原因:
抽象的“过滤器”类是所有装饰器的基础类(装饰器必须拥有与它装饰的那个对象相同的接口,但装饰器亦可对接口作出扩展,这种情况见诸于几个特殊的“过滤器”类中)。
子类处理要求大量子类对每种可能的组合提供支持时,便经常会用到装饰器——由于组合形式太多,造成子类处理变得不切实际。
JavaI/O库要求许多不同的特性组合方案,这正是装饰器方案显得特别有用的原因。
但是,装饰器方案也有自己的一个缺点。
在我们写一个程序的时候,装饰器为我们提供了大得多的灵活性(因为可以方便地混合与匹配属性),但它们也使自己的代码变得更加复杂。
原因在于JavaI/O库操作不便,我们必须创建许多类——“核心”I/O类型加上所有装饰器——才能得到自己希望的单个I/O对象。
FilterInputStream和FilterOutputStream(这两个名字不十分直观)提供了相应的装饰器接口,用于控制一个特定的输入流(InputStream)或者输出流(OutputStream)。
它们分别是从InputStream和OutputStream衍生出来的。
此外,它们都属于抽象类,在理论上为我们与一个流的不同通信手段都提供了一个通用的接口。
事实上,FilterInputStream和FilterOutputStream只是简单地模仿了自己的基础类,它们是一个装饰器的基本要求。
10.2.1通过FilterInputStream从InputStream里读入数据
FilterInputStream类要完成两件全然不同的事情。
其中,DataInputStream允许我们读取不同的基本类型数据以及String对象(所有方法都以“read”开头,比如readByte(),readFloat()等等)。
伴随对应的DataOutputStream,我们可通过数据“流”将基本类型的数据从一个地方搬到另一个地方。
这些“地方”是由表10.1总结的那些类决定的。
若读取块内的数据,并自己进行解析,就不需要用到DataInputStream。
但在其他许多情况下,我们一般都想用它对自己读入的数据进行自动格式化。
剩下的类用于修改InputStream的内部行为方式:
是否进行缓冲,是否跟踪自己读入的数据行,以及是否能够推回一个字符等等。
后两种类看起来特别象提供对构建一个编译器的支持(换言之,添加它们为了支持Java编译器的构建),所以在常规编程中一般都用不着它们。
也许几乎每次都要缓冲自己的输入,无论连接的是哪个IO设备。
所以IO库最明智的做法就是将未缓冲输入作为一种特殊情况处理,同时将缓冲输入接纳为标准做法。
表10.3FilterInputStream的类型
DataInputStream与DataOutputStream联合使用,使自己能以机动方式读取一个流中的基本数据类型(int,char,long等等)InputStream/包含了一个完整的接口,以便读取基本数据类型
BufferedInputStream避免每次想要更多数据时都进行物理性的读取,告诉它“请先在缓冲区里找”InputStream,没有可选的缓冲区大小/本身并不能提供一个接口,只是发出使用缓冲区的要求。
要求同一个接口对象连接到一起
LineNumberInputStream跟踪输入流中的行号;
可调用getLineNumber()以及setLineNumber(int)只是添加对数据行编号的能力,所以可能需要同一个真正的接口对象连接
PushbackInputStream有一个字节的后推缓冲区,以便后推读入的上一个字符InputStream/通常由编译器在扫描器中使用,因为Java编译器需要它。
一般不在自己的代码中使用
10.2.2通过FilterOutputStream向OutputStream里写入数据
与DataInputStream对应的是DataOutputStream,后者对各个基本数据类型以及String对象进行格式化,并将其置入一个数据“流”中,以便任何机器上的DataInputStream都能正常地读取它们。
所有方法都以“wirte”开头,例如writeByte(),writeFloat()等等。
若想进行一些真正的格式化输出,比如输出到控制台,请使用PrintStream。
利用它可以打印出所有基本数据类型以及String对象,并可采用一种易于查看的格式。
这与DataOutputStream正好相反,后者的目标是将那些数据置入一个数据流中,以便DataInputStream能够方便地重新构造它们。
System.out静态对象是一个PrintStream。
PrintStream内两个重要的方法是print()和println()。
它们已进行了覆盖处理,可打印出所有数据类型。
print()和println()之间的差异是后者在操作完毕后会自动添加一个新行。
BufferedOutputStream属于一种“修改器”,用于指示数据流使用缓冲技术,使自己不必每次都向流内物理性地写入数据。
通常都应将它应用于文件处理和控制器IO。
表10.4FilterOutputStream的类型
DataOutputStream与DataInputStream配合使用,以便采用方便的形式将基本数据类型(int,char,long等)写入一个数据流OutputStream/包含了完整接口,以便我们写入基本数据类型
PrintStream用于产生格式化输出。
DataOutputStream控制的是数据的“存储”,而PrintStream控制的是“显示”OutputStream,可选一个布尔参数,指示缓冲区是否与每个新行一同刷新/对于自己的OutputStream对象,应该用“final”将其封闭在内。
可能经常都要用到它
BufferedOutputStream用它避免每次发出数据的时候都要进行物理性的写入,要求它“请先在缓冲区里找”。
可调用flush(),对缓冲区进行刷新OutputStream,可选缓冲区大小/本身并不能提供一个接口,只是发出使用缓冲区的要求。
需要同一个接口对象连接到一起
10.3本身的缺陷:
RandomAccessFile
RandomAccessFile用于包含了已知长度记录的文件,以便我们能用seek()从一条记录移至另一条;
然后读取或修改那些记录。
各记录的长度并不一定相同;
只要知道它们有多大以及置于文件何处即可。
首先,我们有点难以相信RandomAccessFile不属于InputStream或者OutputStream分层结构的一部分。
除了恰巧实现了DataInput以及DataOutput(这两者亦由DataInputStream和DataOutputStream实现)接口之外,它们与那些分层结构并无什么关系。
它甚至没有用到现有InputStream或OutputStream类的功能——采用的是一个完全不相干的类。
该类属于全新的设计,含有自己的全部(大多数为固有)方法。
之所以要这样做,是因为RandomAccessFile拥有与其他IO类型完全不同的行为,因为我们可在一个文件里向前或向后移动。
不管在哪种情况下,它都是独立运作的,作为Object的一个“直接继承人”使用。
从根本上说,RandomAccessFile类似DataInputStream和DataOutputStream的联合使用。
其中,getFilePointer()用于了解当前在文件的什么地方,seek()用于移至文件内的一个新地点,而length()用于判断文件的最大长度。
此外,构建器要求使用另一个自变量(与C的fopen()完全一样),指出自己只是随机读("
r"
),还是读写兼施("
rw"
)。
这里没有提供对“只写文件”的支持。
也就是说,假如是从DataInputStream继承的,那么RandomAccessFile也有可能能很好地工作。
还有更难对付的。
很容易想象我们有时要在其他类型的数据流中搜索,比如一个ByteArrayInputStream,但搜索方法只有RandomAccessFile才会提供。
而后者只能针对文件才能操作,不能针对数据流操作。
此时,BufferedInputStream确实允许我们标记一个位置(使用mark(),它的值容纳于单个内部变量中),并用reset()重设那个位置。
但这些做法都存在限制,并不是特别有用。
10.4File类
File类有一个欺骗性的名字——通常会认为它对付的是一个文件,但实情并非如此。
它既代表一个特定文件的名字,也代表目录内一系列文件的名字。
若代表一个文件集,便可用list()方法查询这个集,返回的是一个字串数组。
之所以要返回一个数组,而非某个灵活的集合类,是因为元素的数量是固定的。
而且若想得到一个不同的目录列表,只需创建一个不同的File对象即可。
事实上,“FilePath”(文件路径)似乎是一个更好的名字。
本节将向大家完整地例示如何使用这个类,其中包括相关的FilenameFilter(文件名过滤器)接口。
10.4.1目录列表器
现在假设我们想观看一个目录列表。
可用两种方式列出File对象。
若在不含自变量(参数)的情况下调用list(),会获得File对象包含的一个完整列表。
然而,若想对这个列表进行某些限制,就需要使用一个“目录过滤器”,该类的作用是指出应如何选择File对象来完成显示。
下面是用于这个例子的代码(或在执行该程序时遇到困难,请参考第3章3.1.2小节“赋值”):
449-450页程序
//:
DirList.java
//Displaysdirectorylisting
packagec10;
importjava.io.*;
publicclassDirList{
publicstaticvoidmain(String[]args){
try{
Filepath=newFile("
."
);
String[]list;
if(args.length==0)
list=path.list();
else
list=path.list(newDirFilter(args[0]));
for(inti=0;
i<
list.length;
i++)
System.out.println(list[i]);
}catch(Exceptione){
e.printStackTrace();
}
DirFilter类“实现”了interfaceFilenameFilter(关于接口的问题,已在第7章进行了详述)。
下面让我们看看FilenameFilter接口有多么简单:
publicinterfaceFilenameFilter{
booleanaccept(文件目录,字串名);
它指出这种类型的所有对象都提供了一个名为accept()的方法。
之所以要创建这样的一个类,背后的全部原因就是把accept()方法提供给list()方法,使list()能够“回调”accept(),从而判断应将哪些文件名包括到列表中。
因此,通常将这种技术称为“回调”,有时也称为“算子”(也就是说,DirFilter是一个算子,因为它唯一的作用就是容纳一个方法)。
由于list()采用一个FilenameFilter对象作为自己的自变量使用,所以我们能传递实现了FilenameFilter的任何类的一个对象,用它决定(甚至在运行期)list()方法的行为方式。
回调的目的是在代码的行为上提供更大的灵活性。
通过DirFilter,我们看出尽管一个“接口”只包含了一系列方法,但并不局限于只能写那些方法(但是,至少必须提供一个接口内所有方法的定义。
在这种情况下,DirFilter构建器也会创建)。
accept()方法必须接纳一个File对象,用它指示用于寻找一个特定文件的目录;
并接纳一个String,其中包含了要寻找之文件的名字。
可决定使用或忽略这两个参数之一,但有时至少要使用文件名。
记住list()方法准备为目录对象中的每个文件名调用accept(),核实哪个应包含在内——具体由accept()返回的“布尔”结果决定。
为确定我们操作的只是文件名,其中没有包含路径信息,必须采用String对象,并在它的外部创建一个File对象。
然后调用getName(),它的作用是去除所有路径信息(采用与平台无关的方式)。
随后,accept()用String类的indexOf()方法检查文件名内部是否存在搜索字串"
afn"
。
若在字串内找到afn,那么返回值就是afn的起点索引;
但假如没有找到,返回值就是-1。
注意这只是一个简单的字串搜索例子,未使用常见的表达式“通配符”方案,比如"
fo?
.b?
r*"
;
这种方案更难实现。
list()方法返回的是一个数组。
可查询这个数组的长度,然后在其中遍历,选定数组元素。
与C和C++的类似行为相比,这种于方法内外方便游历数组的行为无疑是一个显著的进步。
1.匿名内部类
下例用一个匿名内部类(已在第7章讲述)来重写显得非常理想。
首先创建了一个filter()方法,它返回指向FilenameFilter的一个句柄:
451-452页程序
注意filter()的自变量必须是final。
这一点是匿名内部类要求的,使其能使用来自本身作用域以外的一个对象。
之所以认为这样做更好,是由于FilenameFilter类现在同DirList2紧密地结合在一起。
然而,我们可采取进一步的操作,将匿名内部类定义成list()的一个参数,使其显得更加精简。
如下所示:
452页下程序
main()现在的自变量是final,因为匿名内部类直接使用args[0]。
这展示了如何利用匿名内部类快速创建精简的类,以便解决一些复杂的问题。
由于Java中的所有东西都与类有关,所以它无疑是一种相当有用的编码技术。
它的一个好处是将特定的问题隔离在一个地方统一解决。
但在另一方面,这样生成的代码不是十分容易阅读,所以使用时必须慎重。
2.顺序目录列表
经常都需要文件名以排好序的方式提供。
由于Java1.0和Java1.1都没有提供对排序的支持(从Java1.2开始提供),所以必须用第8章创建的SortVector将这一能力直接加入自己的程序。
就象下面这样:
453-454页程序
这里进行了另外少许改进。
不再是将path(路径)和list(列表)创建为main()的本地变量,它们变成了类的成员,使它们的值能在对象“生存”期间方便地访问。
事实上,main()现在只是对类进行测试的一种方式。
大家可以看到,一旦列表创建完毕,类的构建器就会自动开始对列表进行排序。
这种排序不要求区分大小写,所以最终不会得到一组全部单词都以大写字母开头的列表,跟着是全部以小写字母开头的列表。
然而,我们注意到在以相同字母开头的一组文件名中,大写字母是排在前面的——这对标准的排序来说仍是一种不合格的行为。
Java1.2已成功解决了这个问题。
10.4.2检查与创建目录
File类并不仅仅是对现有目录路径、文件或者文件组的一个表示。
亦可用一个File对象新建一个目录,甚至创建一个完整的目录路径——假如它尚不存在的话。
亦可用它了解文件的属性(长度、上一次修改日期、读/写属性等),检查一个File对象到底代表一个文件还是一个目录,以及删除一个文件等等。
下列程序完整展示了如何运用File类剩下的这些方法:
454-456页程序
在fileData()中,可看到应用了各种文件调查方法来显示与文件或目录路径有关的信息。
main()应用的第一个方法是renameTo(),利用它可以重命名(或移动)一个文件至一个全新的路径(该路径由参数决定),它属于另一个File对象。
这也适用于任何长度的目录。
若试验上述程序,就可发现自己能制作任意复杂程度的一个目录路径,因为mkdirs()会帮我们完成所有工作。
在Java1.0中,-d标志报告目录虽然已被删除,但它依然存在;
但在Java1.1中,目录会被实际删除。
10.5IO流的典型应用
尽管库内存在大量IO流类,可通过多种不同的方式组合到一起,但实际上只有几种方式才会经常用到。
然而,必须小心在意才能得到正确的组合。
下面这个相