第14章 流式IO和文件.docx
《第14章 流式IO和文件.docx》由会员分享,可在线阅读,更多相关《第14章 流式IO和文件.docx(19页珍藏版)》请在冰豆网上搜索。
![第14章 流式IO和文件.docx](https://file1.bdocx.com/fileroot1/2023-4/16/7c99dabc-5e4d-4fb4-8cee-06c59ad62187/7c99dabc-5e4d-4fb4-8cee-06c59ad621871.gif)
第14章流式IO和文件
第14章流式I/O和文件
本模块讨论文件,socket和其他数据源使用的流式I/O机制。
第一节相关问题
讨论-以下为与本模块内容有关的问题:
●Java编程语言中使用什么机制来读写文件?
第二节目标
在完成了本模块的学习后,你应当能够:
●描述和使用java.io包的流式思想
●构造文件和过滤器流,并恰当地使用它们
●区别流与读者和作者,并进行合适的选择
●考察并操作文件和目录
●读、写和更新文本和数据文件
●使用Serialization接口来保持对象的状态
第三节流式I/O
流式I/O
●流是字节的源或目的。
●两种基本的流是:
●输入流
●输出流
●结点流对特定的地方读写
●过滤流使用结点流作为输入或输出
本模块考察了Java编程语言如何使用流来处理字节和字符I/O(包括stdio,stdout和stderr)。
下面几节将考察有关处理文件和操作它们所包含的数据的特定细节。
14.3.1流的基础知识
一个流是字节的源或目的。
次序是有意义的。
例如,一个需要键盘输入的程序可以用流来做到这一点。
两种基本的流是:
输入流和输出流。
你可以从输入流读,但你不能对它写。
要从输入流读取字节,必须有一个与这个流相关联的字符源。
在java.io包中,有一些流是结点流,即它们可以从一个特定的地方读写,例如磁盘或者一块内存。
其他流称作过滤器。
一个过滤器输入流是用一个到已存在的输入流的连接创建的。
此后,当你试图从过滤输入流对象读时,它向你提供来自另一个输入流对象的字符。
14.3.2InputStream方法
InputStream方法
●三个基本的read()方法
●intread()
●intread(byte[])
●intread(byte[],int,int)
●其他方法
●voidclose()
●intavailable()
●skip(long)
●booleanmarkSupported()
●voidmark(int)
●voidreset(int)
●intread()
●intread(byte[])
●intread(byte[],int,int)
这三个方法提供对输入管道数据的存取。
简单读方法返回一个int值,它包含从流里读出的一个字节或者-1,其中后者表明文件结束。
其它两种方法将数据读入到字节数组中,并返回所读的字节数。
第三个方法中的两个int参数指定了所要填入的数组的子范围。
注-考虑到效率,总是在实际最大的块中读取数据。
voidclose()
你完成流操作之后,就关闭这个流。
如果你有一个流所组成的栈,使用过滤器流,就关闭栈顶部的流。
这个关闭操作会关闭其余的流。
intavailable()
这个方法报告立刻可以从流中读取的字节数。
在这个调用之后的实际读操作可能返回更多的字节数。
skip(long)
这个方法丢弃了流中指定数目的字符。
booleanmarkSupported()
voidmark(int)
voidreset()
如果流支持“回放”操作,则这些方法可以用来完成这个操作。
如果mark()和reset()方法可以在特定的流上操作,则markSupported()方法将返回ture。
mark(int)方法用来指明应当标记流的当前点和分配一个足够大的缓冲区,它最少可以容纳参数所指定数量的字符。
在随后的read()操作完成之后,调用reset()方法来返回你标记的输入点。
14.3.3OutputStream方法
OutputStream方法
●三个基本的write()方法
●intwrite()
●intwrite(byte[])
●intwrite(byte[],int,int)
●其他方法
●voidclose()
●voidflush()
●voidwrite(int)
●voidwrite(byte[])
●voidwrite(byte[],int,int)
这些方法写输出流。
和输入一样,总是尝试以实际最大的块进行写操作。
voidclose()
当你完成写操作后,就关闭输出流。
如果你有一个流所组成的栈,就关闭栈顶部的流。
这个关闭操作会关闭其余的流。
voidflush()
有时一个输出流在积累了若干次之后才进行真正的写操作。
flush()方法允许你强制执行写操作。
第四节基本的流类
在java.io包中定义了一些流类。
下图表明了包中的类层次。
一些更公共的类将在后面介绍。
基本的流类
●FileInputStream和FileOutputStream
●BufferInputStream和BufferOutputStream
●DataInputStream和DataOutputStream
●PipedInputStream和PipedOutputStream
14.4.1FileInputStream和FileOutputStream
这些类是结点流,而且正如这个名字所暗示的那样,它们使用磁盘文件。
这些类的构造函数允许你指定它们所连接的文件。
要构造一个FileInputStream,所关联的文件必须存在而且是可读的。
如果你要构造一个FileOutputStream而输出文件已经存在,则它将被覆盖。
FileInputStreaminfile=
newFileInputStream("myfile.dat");
FileOutputStreamoutfile=
newFileOutputStream("results.dat");
14.4.2BufferInputStream和BufferOutputStream
这些是过滤器流,它们可以提高I/O操作的效率。
14.4.3DataInputStream和DataOutputStream
这些过滤器通过流来读写Java基本类。
例如:
DataInputStream方法
bytereadByte()
longreadLong()
doublereadDouble()
DataOutputStream方法
voidwriteByte(byte)
voidwriteLong(long)
voidwriteDouble(double)
注意DataInputStream和DataOutputStream的方法是成对的。
这些流都有读写字符串的方法,但不应当使用这些方法。
它们已经被后面所讨论的读者和作者所取代。
14.4.4PipedInputStream和PipedOutputStream
管道流用来在线程间进行通信。
一个线程的PipedInputStream对象从另一个线程的PipedOutputStream对象读取输入。
要使管道流有用,必须有一个输入方和一个输出方。
第五节URL输入流
URL输入流
.URLimageSource;
try{
imageSource=newURL("
}catch(MalformedURLExceptione){}
images[0]=getImage(imageSource,"Duke/T1.gif");
除了基本的文件访问之外,Java技术提供了使用统一资源定位器(URL)来访问网络上的文件。
当你使用Applet的getDocumentBase()方法来访问声音和图象时,你已经隐含地使用了URL对象。
StringimageFile=newString("images/Duke/T1.gif");
images[0]=getImage(getDocumentBase(),imageFile);
然而,你必须象下面的程序那样提供一个直接的URL
.URLimageSource;
try{
imageSource=newURL("
}catch(MalformedURLExceptione){}
images[0]=getImage(imageSource,"Duke/T1.gif");
14.5.1打开一个输入流
你可以通过存储文档基目录下的一个数据文件来打开一个合适的URL输入流。
1.InputStreamis=null;
2.Stringdatafile=newString("Data/data.1-96");
3.bytebuffer[]=newbyte[24];
4.try{
5.//newURLthrowsaMalformedURLException
6.//URL.openStream()throwsanIOException
7.is=(newURL(getDocumentBase(),
datafile)).openStream();
8.}catch(Exceptione){}
现在,你可以就象使用FileInputStream对象那样来用it来读取信息:
1.try{
2.is.read(buffer,0,buffer.length);
3.}catch(IOExceptione1){}
警告-记住大多数用户进行了浏览器的安全设置,以防止Applet存取文件。
第六节读者和作者
14.6.1Unicode
Java技术使用Unicode来表示字符串和字符,而且它提供了16位版本的流,以便用类似的方法来处理字符。
这些16位版本的流称为读者和作者。
和流一样,它们都在java.io包中。
读者和作者中最重要的版本是InputStreamReader和OutputStreamWriter。
这些类用来作为字节流与读者和作者之间的接口。
当你构造一个InputStreamReader或OutputStreamWriter时,转换规则定义了16位Unicode和其它平台的特定表示之间的转换。
14.6.2字节和字符转换
缺省情况下,如果你构造了一个连接到流的读者和作者,那么转换规则会在使用缺省平台所定义的字节编码和Unicode之间切换。
在英语国家中,所使用的字节编码是:
ISO8859-1。
你可以使用所支持的另一种编码形式来指定其它的字节编码。
在native2ascii工具中,你可以找到一个关于所支持的编码形式的列表。
使用转换模式,Java技术能够获得本地平台字符集的全部灵活性,同时由于内部使用Unicode,所以还能保持平台独立性。
14.6.3缓冲读者和作者
因为在各种格式之间进行转换和其它I/O操作很类似,所以在处理大块数据时效率最高。
在InputStreamReader和OutputStreamWriter的结尾链接一个BufferedReader和BufferedWriter是一个好主意。
记住对BufferedWriter使用flush()方法。
14.6.4读入字符串输入
下面这个例子说明了从控制台标准输入读取字符串所应当使用的一个技术。
1.importjava.io.*;
2.publicclassCharInput{
3.publicstaticvoidmain(Stringargs[])throws
4.java.io.IOException{
5.Strings;
6.InputStreamReaderir;
7.BufferedReaderin;
8.ir=newInputStreamReader(System.in);
9.in=newBufferedReader(ir);
10.
11.while((s=in.readLine())!
=null){
12.System.out.println("Read:
"+s);
13.}
14.}
15.}
14.6.5使用其它字符转换
如果你需要从一个非本地(例如,从连接到一个不同类型的机器的网络连接读取)的字符编码读取输入,你可以象下面这个程序那样,使用显式的字符编码构造ir=newInputStreamReader(System.in,“8859_1”);
注-如果你通过网络连接读取字符,就应该使用这种形式。
否则,你的程序会总是试图将所读取的字符当作本地表示来进行转换,而这并不总是正确的。
ISO8859-1是映射到ASCII的Latin-1编码模式。
第七节文件
14.7.1创建一个新的File对象
创建一个新的File对象
●FilemyFile;
myFile=newFile("mymotd");
●myFile=newFile("/","mymotd");
//moreusefulifthedirectoryorfilenameis
//avariable
●FilemyDir=newFile("/");
myFile=newFile(myDir,"mymotd");
File类提供了若干处理文件和获取它们基本信息的方法。
●FilemyFile;
myFile=newFile("mymotd");
●myFile=newFile("/","mymotd");
//moreusefulifthedirectoryorfilenameis
//avariable
●FilemyDir=newFile("/");
myFile=newFile(myDir,"mymotd");
你所使用的构造函数经常取决于你所使用的其他文件对象。
例如,如果你在你的应用程序中只使用一个文件,那么就会使用第一个构造函数。
如果你使用一个公共目录中的若干文件,那么使用第二个或者第三个构造函数可能更容易。
File类提供了独立于平台的方法来操作由本地文件系统维护的文件。
然而它不允许你存取文件的内容。
注-你可以使用一个File对象来代替一个String作为FileInputStream和FileOutputStream对象的构造函数参数。
这是一种推荐方法,因为它独立于本地文件系统的约定。
第八节文件测试和工具
文件测试和工具
●文件名
StringgetName()
StringgetPath()
StringgetAbsolutePath()
StringgetParent()
booleanrenameTo(FilenewName)
●文件测试
booleanexists()
booleancanWrite()
booleancanRead()
booleanisFile()
booleanisDirectory()
booleanisAbsolute()
当你创建一个File对象时,你可以使用下面任何一种方法来获取有关文件的信息:
14.8.1文件名
●StringgetName()
●StringgetPath()
●StringgetAbsolutePath()
●StringgetParent()
●booleanrenameTo(FilenewName)
14.8.2文件测试
●booleanexists()
●booleancanWrite()
●booleancanRead()
●booleanisFile()
●booleanisDirectory()
●booleanisAbsolute()
14.8.3通用文件信息和工具
●longlastModified()
●longlength()
●booleandelete()
14.8.4目录工具
●booleanmkdir()
●String[]list()
第九节随机存取文件
14.9.1创建一个随机存取文件
创建一个随机存取文件
●用文件名
myRAFile=newRandomAccessFile(Stringname,Stringmode);
●用文件对象
myRAFile=newRandomAccessFile(Filefile,Stringmode);
你经常会发现你只想读取文件的一部分数据,而不需要从头至尾读取整个文件。
你可能想访问一个作为数据库的文本文件,此时你会移动到某一条记录并读取它的数据,接着移动到另一个记录,然后再到其他记录――每一条记录都位于文件的不同部分。
Java编程语言提供了一个RandomAccessFile类来处理这种类型的输入输出。
你可以用如下两种方法来打开一个随机存取文件:
●用文件名
myRAFile=newRandomAccessFile(Stringname,Stringmode);
●用文件对象
myRAFile=newRandomAccessFile(Filefile,Stringmode);
mode参数决定了你对这个文件的存取是只读(r)还是读/写(rw)。
例如,你可以打开一个打开一个数据库文件并准备更新:
RandomAccessFilemyRAFile;
myRAFile=newRandomAccessFile("db/stock.dbf","rw");
14.9.2存取信息
随机存取文件
●longgetFilePointer();
●voidseek(longpos);
●longlength();
RandomAccessFile对象按照与数据输入输出对象相同的方式来读写信息。
你可以访问在DataInputStrem和DataOutputStream中所有的read()和write()操作。
Java编程语言提供了若干种方法,用来帮助你在文件中移动。
●longgetFilePointer();
返回文件指针的当前位置。
●voidseek(longpos);
设置文件指针到给定的绝对位置。
这个位置是按照从文件开始的字节偏移量给出的。
位置0标志文件的开始。
●longlength()
返回文件的长度。
位置length()标志文件的结束。
14.9.3添加信息
你可以使用随机存取文件来得到文件输出的添加模式。
myRAFile=newRandomAccessFile("java.log","rw");
myRAFile.seek(myRAFile.length());
//Anysubsequentwrite()swillbeappendedtothefile
第十节串行化
串行化
●将一个对象存放到永久存储器上称为保持。
●只有对象的数据被串行化。
●标记为transient关键字的数据不被串行化。
从JDK1.1开始具有的新特性包括导入java.io.Serializable接口和改变JVM使之支持将一个Java技术对象存放到一个流的能力。
将一个对象存放到某种类型的永久存储器上称为保持。
如果一个对象可以被存放到磁盘或磁带上,或者可以发送到另外一台机器并存放到存储器或磁盘上,那么这个对象就被称为可保持的。
java.io.Serializable接口没有任何方法,它只作为一个“标记者”,用来表明实现了这个接口的类可以考虑串行化。
类中没有实现Serializable的对象不能保存或恢复它们的状态。
14.10.1对象图
当一个对象被串行化时,只有对象的数据被保存;方法和构造函数不属于串行化流。
如果一个数据变量是一个对象,那么这个对象的数据成员也会被串行化。
树或者对象数据的结构,包括这些子对象,构成了对象图。
因为有些对象类所表示的数据在不断地改变,所以它们不会被串行化;例如,java.io.FileInputStream、java.io.FileOutputStream和java.lang.Thread等流。
如果一个可串行化对象包含对某个不可串行化元素的引用,那么整个串行化操作就会失败,而且会抛出一个NotSerializableException。
如果对象图包含一个不可串行化的引用,只要这个引用已经用transient关键字进行了标记,那么对象仍然可以被串行化。
publicclassMyClassimplementsSerializable{
publictransientThreadmyThread;
privateStringcustomerID;
privateinttotal;
域存取修饰符对于被串行化的对象没有任何作用。
写入到流的数据是字节格式,而且字符串被表示为UTF(文件系统安全的通用字符集转换格式)。
transient关键字防止对象被串行化。
publicclassMyClassimplementsSerializable{
publictransientThreadmyThread;
privatetransientStringcustomerID;
privateinttotal;
第十一节读写一个对象流
14.11.1写
对一个文件流读写对象是一个简单的过程。
考虑如下代码段,它将一个java.util.Data对象的实例发送到一个文件:
1.publicclassSerializeDate{
2.SerializeDate(){
3.Dated=newDate();
4.try{
5.FileOutputStreamf=new
6.FileOutputStream("date.ser");
7.ObjectOutputStreams=new
8.ObjectOutputStream(f);
9.s.writeObject(d);
10.f.close();
11.}catch(IOExceptione){
12.e.printStackTrace();
13.}
14.}
15.
16.publicstaticvoidmain(Stringargs[]){
17.newSerializeDate();
18.}
19.}
14.11.2读
读对象和写对象一样简单,只需要说明一点-readObject()方法将流作为一个Object类型返回,而且在使用那个类的方法之前,必须把它转换成合适的类名。
1.publicclassUnSerializeDate{
2.UnSerializeDate(){
3.Dated=null;
4.try{
5.FileInputStreamf=new
6.FileInputStream("