Java IO系统 总结
- File类:
既能代表一个特定文件的名称,又能代表一个目录下的一组文件的名称。
如果它指的是一个文件集,我们就可以对此集合调用list方法,这个方法会返回一个字符数组,从而列出目录列表:
list = path.list(new FilenameFilter() {
private Pattern pattern = Pattern.compile(args[0]);
public boolean accept(File dir, String name) {
return pattern.matcher(name) .matches () ;
}
});
使用匿名内部类实现,其中list使用的是策略设计模式,list接受FilenameFilter接口,并且回调accept方法。
目录实用工具(Directory实用工具放在net.mindview.util包中):
(1)local方法产生由本地目录中的文件构成的File对象数组
(2)walk方法产生给定目录下的由整个目录树中所有文件构成的List<File>
for(File file : Directory.local(". ", "T.*"))
print(file) ;
for(File file : Directory.walk( ".", "T.*\\.java"))
print (file) ;
(3)P531一个工具,它可以在目录中穿行,并且根据Strategy对象来处理这些目录中的文件
File类不仅仅只代表存在的文件或目录。也可以用File对象来创建新的目录或尚不存在的整个目录路径。我们还可以查看文件的特性(如:大小,最后修改日期,读/写),检查某个File对象代表的是一个文件还是一个目录,并可以删除文件。P533
- 输入与输出:
装饰器设计模式:

Java I/O系统就是使用装饰器设计模式:
InputStream/OutputStream继承体系(面向字节形式)
其中Component类对应InputStream/OutputStream类,
ConcreteComponent类对应:(用来表示具体的输入数据源/输出目标)
ByteArraylnputStream/ByteArrayOutputStream 内存的缓冲区
StringBufferlnputStream 字符串,底层实现实际使用StringBuffer
FilelnputStream/FileOutputStream 字符串,表示文件名,文件或FileDescriptor对象
PipedlnputStream/ PipedOutputStream 多线程(互为源和目标)
SequencelnputStream两个InputStream对象或一个容纳InputStream对象的容器
Decorator类对应FilterInputStream/FilterOutputStream类
ConcreteDecorator类对应:(添加读/写方法)
DatalnputStream/DataOutputStream 按照可移植方式从流中读/写基本数据类型
BufferedlnputStream/BufferedOutputStream
代表“使用缓冲区”,InputStream/OutputStream可以指定缓冲区大小,可调用flush清空缓冲区
LineNumberlnputStream、PushbacklnputStream(了解)
PrintStream 用于产生格式化输出,区分DataOutputStream,主要方法:print、println,但该类存在问题,用PrintWriter替代
Reader/Writer继承体系(兼容Unicode和面向字符形式)
其中Component类对应Reader/Writer类,
ConcreteComponent类对应:(用来表示具体的输入数据源/输出目标)
CharArrayWriter/CharArrayReader
BufferedWriter/BufferedReader
StringWriter/StringReader
FileWriter/FileReader
PipedWriter/PipedReader
Decorator类对应FilterReader/FilterWriter类(抽象类,不能使用)
ConcreteDecorator类对应:(添加读/写方法)
BufferedWriter/BufferedReader
无论我们何时使用readLine,都不应该使用DatalnputStream(这会遭到
编译器的强烈反对),而应该使用BufferedReader。除了这一点,DatalnputStream仍是I/O类库的首选成员
LineNumberReader、PushbackReader、StreamTokenizer(了解)
PrintWriter
为了更容易地过渡到使用PrintWriter,它提供了一个既能接受Writer对象又能接受任何OutputStream对象的构造器。 PrintWriter的格式化接口实际上与PrintStream相同
注意点:
(1)有时我们必须把来自干“字节”层次结构中的类和“字符”层次结构中的类结合起来使用。为了实现这个目的,要用到“适配器”( adapter)类:InputStreamReader可以把
InputStream转换为Reader,而OutputStreamWriter可以把OutputStream转换为Writer。
(2)几乎所有原始的Java I/O流类都有相应的Reader和Writer类来提供天然的Unicode操作。然而在某些场合,面向字节的InputStream和OutputStream才是正确的解决方案;特别是,java.util.zip类库就是面向字节的而不是面向字符的。因此,最明智的做法是尽量尝试使用Reader和Writer,一旦程序代码无法成功编译,我们就会发现自己不得不使用面向字节的类库。
独立的体系:RandomAccessFile(随机读写)
在JDK 1.4中,RandomAccessFile的大多数功能(但不是全部)由nio存储映射文件所取代
I/O流的典型使用方式:
(1)缓冲输入文件 BufferedReader+FileReader
(2)从内存输入 利用(1)读取的String结果,StringReader
(3)格式化的内存输入 利用(1)读取的String结果,用getBytes转换成字节数组,DatalnputStream+BufferedlnputStream+ByteArraylnputStream
(4)基本的文件输出 PrintWriter+BufferedWriter+FileWriter
(5)文本文件输出的快捷方式 PrintWriter
Java SE5在PrintWriter中添加了一个辅助构造器,使得你不必在每次希望创建文本文件并向其中写入时,都去执行所有的装饰工作(仅此一列)
(6)存储和恢复数据
DataOutputStream+BufferedOutputStream+FileOutputStream
DatalnputStream+BufferedlnputStream+FilelnputStream
如果我们使用DataOutputStream写入数据,Java保证我们可以使用DatalnputStream准确地读取数据——无论读和写数据的平台多么不同
注意:
当我们使用DataOutputStream时,写字符串并且让DatalnputStream能够恢复它的唯一可靠的做法就是使用UTF-8编码
如果我们用一个非Java程序读取用writeUTF所写的字符串时,必须编写一些特殊代码才能正确读取字符串
必须要么为文件中的数据采用固定的格式,要么将额外的信息保存到文件中,以便能够对其进行解析以确定数据的存放位置。
对象序列化和XML可能是更容易的存储和读取复杂数据结构的方式
(7)读写随机访问文件 RandomAccessFile
3.文件读写的实用工具:P546 简化读写操作,使结果像ArrayList一样被对待
4.标准I/O:
标准I/O的意义在于:我们可以很容易地把程序串联起来,一个程序的标准输出可以成为另一程序的标准输入
Java提供了System.in、System.out和System.err,System.out和System.err都已被包装成PrintStream,而System.in仍是InputStream;因此对于前两者最好使用PrintWriter(System.out,true)转换,后者使用BufferedReader+InputStreamReader转换。
重定向:setln(InputStream)、setOut(PrintStream)、setErr(PrintStream)
I/O重定向操纵的是字节流,而不是字符流;因此我们使用的是InputStream和OutputStream,而不是Reader和Writer。
5.进程控制:在Java内部执行其他操作系统的程序,并且要控制这些程序的输入和输出
6.新I/O:(java.nio.*包中引入新的Java I/O类库,其目的在于提高速度)
综述:
速度的提高来自于所使用的结构更接近于操作系统执行I/O的方式:通道和缓冲器
唯一直接与通道交互的缓冲器是ByteBuffer(可以存储未加工字节的缓冲器)
ByteBuffer是相当基础的类:通过告知分配多少存储空间来创建一个ByteBuffer对象,还有一个方法选择集,用于以原始的字节形式或基本数据类型输出和读取数据。但是,没办法输出或读取对象,即使是字符串对象也不行。
FilelnputStream、FileOutputStream以及RandomAccessFile可以产生FileChannel,注意这些是字节操纵流,与低层的nio性质一致。Reader和Writer这种字符模式类不能用于产生通道,但是 java.nio.channels.Channels类提供了实用方法,用以在通道中产生Reader和Writer。
实现举例:
ByteBuffer buff = ByteBuffer.allocate(BSIZE);
ByteBuffer的大小尤为重要,必须通过实际运行应用程序来找到最佳尺寸;甚至达到更高的速度也有可能,方法就是使用allocateDirect,而不是allocate,以产生一个与操作系统有更高耦合性的“直接一缓冲器”。但是,这种分配的开支会更大,并且具体实现随操作系统的不同而不同,因此必须再次实际运行应用程序来查看直接缓冲是否可以使我们获得速度上的优势
FileChannel out = new FileOutputStream (" data. txt") . getChannel();
FileChannel in = new FilelnputStream(" data . txt") . getChannel () ;
特殊方法:in.transferTo (0, in.size () ,out) ;
out.transferFrom(in,0 ,in.size()) ;
视图缓冲器:
视图缓冲器可以让我们通过某个特定的基本数据类型的视窗查看其底层的ByteBuffer. ByteBuffer依然是实际存储数据的地方,支持着前面的视图。
视图允许我们从ByteBuffer一次一个地(与ByteBuffer所支持的方式相同)或者成批地(放人数组中)读取基本类型值。
利用asCharBuffer、asShortBuffer等获得该缓冲器上的视图
ByteBuffer bb = ByteBuffer.allocate(BSIZE);
bb. asCharBuffer().put( "Howdy ! "); //插入
While((c = bb.getChar()) !=e) //注意:ByteBuffer此时未转换成视图,所以用getChar
printnb(c +" ");
IntBuffer ib= bb.aslntButter();
ib.put(new int[]{ 11, 42, 47, 99, 143, 811, 1016 }); //视图中重载的put方法
System.out .println(ib . get(3)); //此时ByteBuffer已转换成视图,所以用get
ByteBuffer bb = ByteBuffer.wrap(new byte[]{ 0, 0, 0, 0, 0, 0, 0, 'a' });
//wrap()方法将已存在的字节数组“包装到ByteBuffer中,不再复制底层的数组,而是把它作为所产生的ByteBuffer的存储器,我们称之为数组支持的ByteBuffer
LongBuffer lb = ((ByteBuffer) bb.rewind()).asLongBuffer();
print(lb.array()); //array显示视图底层的细节,它是“可选的”,并且我们只能对由数组支持的缓冲器调用此方法
注意:使用视图时的字节存放顺序对数据的影响P559
使用缓冲器操纵数据:
Buffer由数据和可以高效地访问及操纵这些数据的四个索引组成,这四个索引是:mark
(标记),position(位置),limit(界限)和capacity(容量)
方法:capacity、clear、flip、limit、limit(int lim)、mark、position、position(int pos)、remaining、hasRemaining、get、put、clear、reset、rewind
注意:(1)flip将limit设置为position,position设置为0.此方法用于准备从缓冲区读取已经写入的数据
(2)为下次读取数据做准备,要先用clear
(3)可以调用绝对的、包含一个索引参数的get和put方法(参数指明get或put的发生位置。不过,这些方法不会改变缓冲器的position指针
(4)rewind把position设置到缓冲器的开始位置
(5)mark将mark的值设为当前的position,reset将position设为mark的值
内存映射文件:
内存映射文件允许我们创建和修改那些因为太大而不能放入内存的文件。有了内存映射文件,我们就可以假定整个文件都放在内存中,而且可以完全把它当作非常大的数组来访问。
FileChannel fc = new RandomAccessFile( “temp.tmp”,”rw”).getChannel();
IntBuffer ib = fc.map(FileChannel, MapMode.READ_WRITE ,0, fc.size()). aslntBuffer () ;
//map产生MappedByteBuffer,由ByteBuffer继承而来
for(int i = e; i < numOflnts; i++)
ib.put(i) ;
fc.close () ;
注意:映射文件中的所有输出必须使用RandomAccessFile,输入可以FilelnputStream或RandomAccessFile
即使建立映射文件的花费很大,但整体受益比起I/O流来说还是很显著的。
综合例子:
FileChannel fc = new FileOutputStream ( "data2 . txt ") .getChannel () ;
ByteBuffer buff = ByteBuffer.allocate(24);
buff.asCharBuffer().put(“Some text" ) ;
fc.write(buff) ;
fc.close () :
fc = new FileInputStream ( “data2 . txt") . getChannel() ;
buff.clear() :
fc.read(buff) ;
buff.flip();
System. out .println (buff.asCharBuffer());
压缩:
Java I/O类库中的类支持读写压缩格式的数据流。可以用它们对其他的I/O类进行封装,以提供压缩功能
这些类不是从Reader和Writer类派生而来的,而是属于InputStream和OutputStream继承层次结构的一部分。这样做是因为压缩类库是按字节方式而不是字符方式处理的。不过有时我们可能会被迫要混合使用两种类型的数据流(注意我们可以使用InputStreamReader和OutputStreamWriter在两种类型间方便地进行转换)
CheckedlnputStream GetCheckSum()为任何InputStream产生校验和(不仅是解压缩)
CheckedOutputStream GetCheckSum()为任何OutputStream产生校验和(不仅是压缩)
DeflaterOutputStream 压缩类的基类
ZipOutputStream 一个DeflaterOutputStream,用于将数据压缩成Zip文件格式
GZIPOutputStream 一个DeflaterOutputStream,用于将数据压缩成GZIP文件格式
InflaterlnputStream 解压缩类的基类
ZiplnputStream 一个InflaterlnputStream,用于解压缩Zip文件格式的数据
GZIPlnputStream 一个InflaterlnputStream,用于解压缩GZIP文件格式的数据
压缩类的使用非常直观,直接将输出流封装成GZIPOutputStream或ZipOutputStream,并将输入流封装成GZIPlnputStream或ZiplnputStream即可(也是个装饰器类)
用Zip可以进行多文件保存P569
Java档案文件:
Zip格式也被应用于JAR (Java ARchive,Java档案文件)文件格式中。这种文件格式就像Zip一样,可以将一组文件压缩到单个压缩文件中。同Java中其他任何东西一样.JAR文件也是跨平台的,所以不必担心跨平台的问题。
一个JAR文件由一组压缩文件构成,同时还有一张描述了所有这些文件的“文件清单一(可自行创建文件清单,也可以由jar程序自动生成)
Sun的JDK自带的jar程序可根据我们的选择自动压缩文件。可以用命令行的形式调用它: jar [options] destination [manifest] inputfile(s) 具体P571
对象序列化:
概念及意义:
Java的对象序列化将那些实现了Serializable接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象。这一过程甚至可通过网络进行.这意味着序列化机制能自动弥补不同操作系统之间的差异。
对象序列化的概念加入到语言中是为了支持两种主要特性:一是Java的远程方法调用(RMI),二是对Java Beans来说,对象的序列化也是必需的。
只要对象实现了Serializable接口(该接口仅是一个标记接口,不包括任何方法),对象的序列化处理就会非常简单。
具体方法:
要序列化一个对象,首先要创建某些OutputStream对象,然后将其封装在一个ObjectOutputStream对象内。这时,只需调用writeObject即可将对象序列化,并将其发送给OutputStream(对象化序列是基于字节的,因要使用InputStream和OutputStream继承层次结构)。要反向进行该过程(即将一个序列还原为一个对象),需要将一个InputStream封装在ObjectlnputStream内,然后调用readObject和往常一样,最后获得的是一个引用,它指向一个向上转型的Object,所以必须向下转型才能直接设置它们。
注意:
(1)对象序列化保存了“对象网”
(2)从序列化中还原对象时,Java虚拟机必须要找得到该对象相关的.class文件(即在类路径中存在),否则抛出ClassNotFoundException异常。
代码:
public class Worm implements Serializable {...}
Worm w = new Worm(6, 'a');
ObjectOutputStream out= new ObjectOutputStream(new FileOutputStream( "worm .out")) ;
out.writeObject ( "Worm storage\n ") ;
out .writeObject (w) ;
out.close(); //Also flushes output
ObjectlnputStream in = new ObjectlnputStream(new FileInputStream( "worm .out ''));
String s = (String)in.readObject();
worm w2 = (Worm)in.readObject();
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream out2 = new ObjectOutputStream(bout) :
out2 .writeObject( "Worm storage\n ") :
out2 .writeObject(w) :
out2.flush() ;
ObjectlnputStream in2=new ObjectlnputStream(
new ByteArraylnputStream(bout. toByteArray())):
S = (String)in2. readObject():
Worm w3=(Worm)in2.readObject();
代码注意:
可以为一个String调用writeObject。也可以用与DataOutputStream相同的方法写入所有基本数据类型(它们具有同样的接口)。
对一个Serializable对象进行还原的过程中,没有调用任何构造器,包括默认的构造器。整个对象都是通过从InputStream中取得数据恢复而来的。
序列化的控制:
不希望对象的某一部分被序列化,或者一个对象被还原以后,某子对象需要重新创建,从而不必将该子对象序列化,在这些特殊情况下,可通过实现Externalizable接口--代替实现Serializable接口--来对序列化过程进行控制。这个Externalizable接口继承了Serializable接口,同时增添了两个方法:writeExternal和readExternal这两个方法会在序列化和反序列化还原的过程中被自动调用,以便执行一些特殊操作。
区别:
对于Serializable对象,对象完全以它存储的二进制位为基础来构造,而不调用构造器。而对于一个Externalizable对象,所有普通的默认构造器都会被调用(包括在字段定义时的初始化),然后调用readExternal。
代码:
public void writeExternal(ObjectOutput out) throws IOException {
print ("Biip3.writeExternal") ;
// You must do this:
out.writeObject (s) ;
out.writelnt (i) ;
}
public void readExternal(Objectlnput in) throws IOException, ClassNotFoundException {
print ("Blip3. readExternal") ;
// You must do this:
s = (String)in.readObject() ;
i = in.readlnt () ;
}
解释:为了正常运行,我们不仅需要在writeExternal方法(没有任何默认行为来为Externalizable对象写入任何成员对象)中将来自对象的重要信息写入,还必须在readExternal方法中恢复数据。
如果我们正在操作的是一个Serializable对象,那么所有序列化操作都会自动进行。为了能够予以控制,可以用transient(瞬时)关键字逐个字段地关闭序列化。
private transient String password;
如果不是特别坚持实现Externalizable接口,那么还有另一种方法。可以实现Serializable接口,并添加(注意是“添加”,而非“覆盖”或者“实现”)名为writeObject和readObject的方法。这样一旦对象被序列化或者被反序列化还原,就会自动地分别调用这两个方法。也就是说,只要提供了这两个方法,就会使用它们而不是默认的序列化机制。(内部机制很混乱,知道就好)
代码:
public class SerialCtl implements Serializable{
...
private void writeObject(ObjectOutputStream stream) throws IOException {
stream.defaultWriteObject() ;
//执行默认的writeObject方法,即默认机制反序列化(忽略transient)
stream.writeObject(b) ;
}
...
}
使用“持久性”:
一个比较诱人的使用序列化技术的想法是:存储程序的一些状态,以便我们随后可以很容易地将程序恢复到当前状态。
注意:
只要将任何对象序列化到单一流中,就可以恢复出与我们写出时一样的对象网,并且没有任何意外重复复制出的对象。
如果我们想保存系统状态,最安全的做法是将其作为“原子”操作进行序列化,即将构成系统状态的所有对象都置入单一容器内,并在一个操作中将该容器直接写出。然后同样只需一次方法调用,即可以将其恢复。P584代码
想序列化static值,必须自己动手去实现,因为static数据是在构造器中被初始化的。
XML:
对象序列化的一个重要限制是它只是Java的解决方案:只有Java程序才能反序列化这种对象。一种更具互操作性的解决方案是将数据转换为XML格式,这可以使其被各种各样的平台和语言使用。简述:P587
Preferences:
Preferences API与对象序列化相比,前者与对象持久性更密切,因为它可以自动存储
和读取信息。不过,它只能用于小的、受限的数据集合——我们只能存储基本类型和字符串,并且每个字符串的存储长度不能超过8K(不是很小,但我们也并不想用它来创建任何重要的东西)。顾名思义,Preferences API用于存储和读取用户的偏好(preferences)以及程序配置项的设置。Preferences是一个键一值集合(类似映射),存储在一个节点层次结构中。

浙公网安备 33010602011771号