• java中的高级流


    java.io定义的输入/输出类列于下表:

    ObjectInputStream.GetField和ObjectOutputStream.PutField是Java2新添的内部类。

    java.io包还包含两个不受java2欢迎的类,这两个类没有在上表中列出:LineNumberInputStream和StringBufferInputStream。新代码不应该使用两个类。

    下面是由java.io定义的接口:

    FileFilter接口是Java2新增的。

    java.io包中有很多类和接口。包括字节和字符流,对象序列化(对象的存储和释放)。

    本章讲述几个最常用的I/O成员,从最独特的File开始。

    File(文件类)

     

    尽管java.io定义的大多数类是实行流式操作的,File类不是。它直接处理文件和文件系统。也就是说,File类没有指定信息怎样从文件读取或向文件存储;它描述了文件本身的属性。File对象用来获取或处理与磁盘文件相关的信息,例如权限,时间,日期和目录路径。此外,File还浏览子目录层次结构。很多程序中文件是数据的根源和目标。尽管它们在小应用程序中因为安全原因而受到严格限制,文件仍是存储固定和共享信息的主要资源。Java中的目录当成File对待,它具有附加的属性——一个可以被list()方法检测的文件名列表。

    下面的构造函数可以用来生成File对象:

    File(StringdirectoryPath)

    File(StringdirectoryPath,Stringfilename)

    File(FiledirObj,Stringfilename)

    这里,directoryPath是文件的路径名,filename是文件名,dirObj一个指定目录的File对象。

    下面的例子创建了三个文件:f1,f2,和f3。第一个File对象是由仅有一个目录路径参数的构造函数生成的。第二个对象有两个参数——路径和文件名。第三个File对象的参数包括指向f1文件的路径及文件名。f3和f2指向相同的文件。

    Filef1=newFile("/");

    Filef2=newFile("/","autoexec.bat");

    Filef3=newFile(f1,"autoexec.bat");

    注意:Java能正确处理UNIX和Windows/DOS约定路径分隔符。如果在Windows版本的Java下用斜线(/),路径处理依然正确。记住,如果你用Windows/DOS使用反斜线()的约定,你需要在字符串内使用它的转义序列(\)。Java约定是用UNIX和URL风格的斜线来作路径分隔符。

    File定义了很多获取File对象标准属性的方法。例如getName()返回文件名,getParent()返回父目录名,exists()在文件存在的情况下返回true,反之返回false。然而File类是不对称的。说它不对称,意思是虽然存在允许验证一个简单文件对象属性的很多方法,但是没有相应的函数来改变这些属性。下面的例子说明了几个File方法:

    //DemonstrateFile.

    importjava.io.File;

    classFileDemo{

    staticvoidp(Strings){

    System.out.println(s);

    }

    publicstaticvoidmain(Stringargs[]){

    Filef1=newFile("/java/COPYRIGHT");

    p("FileName:"+f1.getName());

    p("Path:"+f1.getPath());

    p("AbsPath:"+f1.getAbsolutePath());

    p("Parent:"+f1.getParent());

    p(f1.exists()?"exists":"doesnotexist");

    p(f1.canWrite()?"iswriteable":"isnotwriteable");

    p(f1.canRead()?"isreadable":"isnotreadable");

    p("is"+(f1.isDirectory()?"":"not"+"adirectory"));

    p(f1.isFile()?"isnormalfile":"mightbeanamedpipe");

    p(f1.isAbsolute()?"isabsolute":"isnotabsolute");

    p("Filelastmodified:"+f1.lastModified());

    p("Filesize:"+f1.length()+"Bytes");

    }

    }

    运行该程序,你将看到下面的结果:

    FileName:COPYRIGHT

    Path:/java/COPYRIGHT

    AbsPath:/java/COPYRIGHT

    Parent:/java

    exists

    iswriteable

    isreadable

    isnotadirectory

    isnormalfile

    isabsolute

    Filelastmodified:812465204000

    Filesize:695Bytes

    大多数File方法是自说明的,但isFile()和isAbsolute()不是。isFile()在被文件调用时返回true,在被目录调用时返回false。并且,isFile()被一些专用文件调用时返回false,例如设备驱动程序和命名管道,所以该方法可用来判定文件是否作为文件执行。isAbsolute()方法在文件拥有绝对路径时返回true,若是相对路径则返回false。

    File还包括两个有用的实用工具方法。第一个是renameTo(),显示如下:

    booleanrenameTo(FilenewName)

    这里,由newName指定的文件名变成了所调用的File对象的新的名称。如果更名成功则返回ture,文件不能被重命名(例如,你试图重命名文件以使它从一个目录转到另一个目录,或者你使用了一个已经存在的文件名),则返回false。

    第二个实用工具方法是delete(),该方法删除由被调用的File对象的路径指定的磁盘文件。它的形式如下:

    booleandelete()

    同样可以在目录为空时用delete()删除目录。如果删除了文件,delete()返回true,如果文件不能被删除则返回false。

    Java2为File类增添了一些新的方法,你会发现在某些场合这些新增方法很有用。一些最有趣的方法显示如下:

    并且,因为File类现在支持Comparable接口,compareTo()方法也被支持。

    目录操作

     

    目录是一个包含其他文件和路径列表的File类。当你创建一个File对象且它是目录时,isDirectory()方法返回ture。这种情况下,可以调用该对象的list()方法来提取该目录内部其他文件和目录的列表。该方法有两种形式。第一种形式如下:

    String[]list()

    文件列表在一个String对象数组中返回。

    下面显示的程序说明怎样用list()来检查一个目录的内容:

    //Usingdirectories.

    importjava.io.File;

    classDirList{

    publicstaticvoidmain(Stringargs[]){

    Stringdirname="/java";

    Filef1=newFile(dirname);

    if(f1.isDirectory()){

    System.out.println("Directoryof"+dirname);

    Strings[]=f1.list();

    for(inti=0;i<s.length;i++){

    Filef=newFile(dirname+"/"+s[i]);

    if(f.isDirectory()){

    System.out.println(s[i]+"isadirectory");

    }else{

    System.out.println(s[i]+"isafile");

    }

    }

    }else{

    System.out.println(dirname+"isnotadirectory");

    }

    }

    }

    下面是程序的样本输出(当然,目录下的内容不同,输出也不同):

    Directoryof/java

    binisadirectory

    libisadirectory

    demoisadirectory

    COPYRIGHTisafile

    READMEisafile

    index.htmlisafile

    includeisadirectory

    src.zipisafile

    .hotjavaisadirectory

    Srcisadirectory

    使用FilenameFilter

     

    你总是希望能够限制由list()方法返回的文件数目,使它仅返回那些与一定的文件名方式或者过滤(filter)相匹配的文件。为达到这样的目的,必须使用list()的第二种形式:

    String[]list(FilenameFilterFFObj)

    该形式中,FFObj是一个实现FilenameFilter接口的类的对象。

    FilenameFilter仅定义了一个方法,accept()。该方法被列表中的每个文件调用一次。它的通常形式如下:

    booleanaccept(Filedirectory,Stringfilename)

    当被directory指定的目录中的文件(也就是说,那些与filename参数匹配的文件)包含在列表中时,accept()方法返回true,当这些文件没有包括在列表中时,accept()返回false。

    下面显示的OnlyExt类实现FilenameFilter接口,它被用来修饰前面的程序,限制由list()返回的文件名的可见度,把对象被创建时以指定扩展名结束的文件归档。

    importjava.io.*;

    publicclassOnlyExtimplementsFilenameFilter{

    Stringext;

    publicOnlyExt(Stringext){

    this.ext="."+ext;

    }

    publicbooleanaccept(Filedir,Stringname){

    returnname.endsWith(ext);

    }

    }

    修改过的目录列表程序显示如下。现在它只显示以.html为扩展名的文件。

    //Directoryof.HTMLfiles.

    importjava.io.*;

    classDirListOnly{

    publicstaticvoidmain(Stringargs[]){

    Stringdirname="/java";

    Filef1=newFile(dirname);

    FilenameFilteronly=newOnlyExt("html");

    Strings[]=f1.list(only);

    for(inti=0;i<s.length;i++){

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

    }

    }

    }

    listFiles()方法

     

    Java2增加了list()方法的一个变化形式,名为listFiles(),你会发现该方法很有用。

    listFiles()形式如下:

    File[]listFiles()

    File[]listFiles(FilenameFilterFFObj)

    File[]listFiles(FileFilterFObj)

    上述三种形式以File对象矩阵的形式返回文件列表,而不是用字符串形式返回。第一种形式返回所有的文件,第二种形式返回满足指定FilenameFilter接口的文件。除了返回一个File对象数组,这两个listFiles()方法就像list()方法一样工作。

    第三种listFiles()形式返回满足指定FileFilter的路径名的文件。FileFilter只定义了一个accept()方法,该方法被列表中的每个文件调用一次。它的通常形式如下:

    booleanaccept(Filepath)

    如果文件被包括在列表中(即与path参数匹配的文件),accept()方法返回true,如果不被包括,则返回false。

    创建目录

     

    另外两个有用的File类的方法是mkdir()和mkdirs()。mkdir()方法创建了一个目录,创建成功返回true,创建失败返回false。创建失败是指File对象指定的目录已经存在,或者是因为整个路径不存在而不能创建目录。创建路径不存在的目录,用mkdirs()的方法。它创建目录以及该目录所有的父目录。

    Java输入/输出类和接口

     

    Java的流式输入/输出建立在四个抽象类的基础上:InputStream,OutputStream,Reader和Writer。这些类在第12章中有过简要的讨论。它们用来创建具体流式子类。尽管程序通过具体子类执行输入/输出操作,顶层的类定义了所有流类的基础通用功能。

    InputStream和OutputStream设计成字节流类。Reader和Writer为字符流设计。字节流类和字符流类形成分离的层次结构。一般说来,处理字符或字符串时应使用字符流类,处理字节或二进制对象时应用字节流类。

    下面分别讲述字节流和字符流类。

    字节流

     

    字节流类为处理字节式输入/输出提供了丰富的环境。一个字节流可以和其他任何类型的对象并用,包括二进制数据。这样的多功能性使得字节流对很多类型的程序都很重要。

    因为字节流类以InputStream和OutputStream为顶层,我们就从讨论这两个类开始。

    InputStream(输入流)

     

    InputStream是一个定义了Java流式字节输入模式的抽象类。该类的所有方法在出错条件下引发一个IOException异常。表10-1显示了InputStream的方法。

    OutputStream是定义了流式字节输出模式的抽象类。该类的所有方法返回一个void值并且在出错情况下引发一个IOException异常。表10-2显示了OutputStream的方法。

    注意:多数在表17-1和表17-2中描述的方法由InputStream和OutputStream的子类实现,但mark()和reset()方法除外。注意下面讨论的每个子类中它们的使用和不用情况。

    FileInputStream(文件输入流)

     

    FileInputStream类创建一个能从文件读取字节的InputStream类,它的两个常用的构造函数如下:

    FileInputStream(Stringfilepath)

    FileInputStream(FilefileObj)

    它们都能引发FileNotFoundException异常。这里,filepath是文件的全称路径,fileObj是描述该文件的File对象。

    下面的例子创建了两个使用同样磁盘文件且各含一个上述构造函数的FileInputStreams类:

    FileInputStreamf0=newFileInputStream("/autoexec.bat")

    Filef=newFile("/autoexec.bat");

    FileInputStreamf1=newFileInputStream(f);

    尽管第一个构造函数可能更常用到,第二个构造函数允许在把文件赋给输入流之前用File方法更进一步检查文件。当一个FileInputStream被创建时,它可以被公开读取。

    FileInputStream重载了抽象类InputStream的六个方法,mark()和reset()方法不被重载,任何关于使用FileInputStream的reset()尝试都会生成IOException异常。

    下面的例题说明了怎样读取单个字节、字节数组以及字节数组的子界。它同样阐述了怎样运用available()判定剩余的字节个数及怎样用skip()方法跳过不必要的字节。该程序读取它自己的源文件,该源文件必定在当前目录中。

    //DemonstrateFileInputStream.

    importjava.io.*;

    classFileInputStreamDemo{

    publicstaticvoidmain(Stringargs[])throwsException{

    intsize;

    InputStreamf=

    newFileInputStream("FileInputStreamDemo.java");

    System.out.println("TotalAvailableBytes:"+

    (size=f.available()));

    intn=size/40;

    System.out.println("First"+n+

    "bytesofthefileoneread()atatime");

    for(inti=0;i<n;i++){

    System.out.print((char)f.read());

    }

    System.out.println(" StillAvailable:"+f.available());

    System.out.println("Readingthenext"+n+

    "withoneread(b[])");

    byteb[]=newbyte[n];

    if(f.read(b)!=n){

    System.err.println("couldn'tread"+n+"bytes.");

    }

    System.out.println(newString(b,0,n));

    System.out.println(" StillAvailable:"+(size=f.available()));

    System.out.println("Skippinghalfofremainingbyteswithskip()");

    f.skip(size/2);

    System.out.println("StillAvailable:"+f.available());

    System.out.println("Reading"+n/2+"intotheendofarray");

    if(f.read(b,n/2,n/2)!=n/2){

    System.err.println("couldn'tread"+n/2+"bytes.");

    }

    System.out.println(newString(b,0,b.length));

    System.out.println(" StillAvailable:"+f.available());

    f.close();

    }

    }

    下面是该程序的输出:

    TotalAvailableBytes:1433

    First35bytesofthefileoneread()atatime

    //DemonstrateFileInputStream.

    im

    StillAvailable:1398

    Readingthenext35withoneread(b[])

    portjava.io.*;

    classFileInputS

    StillAvailable:1363

    Skippinghalfofremainingbyteswithskip()

    StillAvailable:682

    Reading17intotheendofarray

    portjava.io.*;

    read(b)!=n){

    S

    StillAvailable:665

    这个有些刻意创作的例子说明了怎样读取数据的三种方法,怎样跳过输入以及怎样检查流中可以获得数据的数目。

    FileOutputStream(文件输出流)

     

    FileOutputStream创建了一个可以向文件写入字节的类OutputStream,它常用的构造函数如下:

    FileOutputStream(StringfilePath)

    FileOutputStream(FilefileObj)

    FileOutputStream(StringfilePath,booleanappend)

    它们可以引发IOException或SecurityException异常。这里filePath是文件的全称路径,fileObj是描述该文件的File对象。如果append为true,文件以设置搜索路径模式打开。

    FileOutputStream的创建不依赖于文件是否存在。在创建对象时FileOutputStream在打开输出文件之前创建它。这种情况下你试图打开一个只读文件,会引发一个IOException异常。

    下面的例子创建一个样本字节缓冲器。先生成一个String对象,接着用getBytes()方法提取字节数组对等体。然后创建了三个文件。第一个file1.txt将包括样本中的各个字节。第二个文件是file2.txt,它包括所有字节。第三个也是最后一个文件file3.txt,仅包含最后的四分之一。不像FileInputStream类的方法,所有FileOutputStream类的方法都返回一个void类型值。在出错情况下,这些方法将引发IOException异常。

    //DemonstrateFileOutputStream.

    importjava.io.*;

    classFileOutputStreamDemo{

    publicstaticvoidmain(Stringargs[])throwsException{

    Stringsource="Nowisthetimeforallgoodmen "

    +"tocometotheaidoftheircountry "

    +"andpaytheirduetaxes.";

    bytebuf[]=source.getBytes();

    OutputStreamf0=newFileOutputStream("file1.txt");

    for(inti=0;i<buf.length;i+=2){

    f0.write(buf[i]);

    }

    f0.close();

    OutputStreamf1=newFileOutputStream("file2.txt");

    f1.write(buf);

    f1.close();

    OutputStreamf2=newFileOutputStream("file3.txt");

    f2.write(buf,buf.length-buf.length/4,buf.length/4);

    f2.close();

    }

    }

    下面是运行该程序之后,每个文件的内容,首先是file1.txt:

    Nwihiefralgoetoethiftercutynahiuae.

    接着,是file2.txt:

    Nowisthetimeforallgoodmen

    tocometotheaidoftheircountry

    andpaytheirduetaxes.

    最后,file3.txt

    ndpaytheirduetaxes.

    ByteArrayInputStream(字节数组输入流)

     

    ByteArrayInputStream是把字节数组当成源的输入流。该类有两个构造函数,每个构造函数需要一个字节数组提供数据源:

    ByteArrayInputStream(bytearray[])

    ByteArrayInputStream(bytearray[],intstart,intnumBytes)

    这里,array是输入源。第二个构造函数创建了一个InputStream类,该类从字节数组的子集生成,以start指定索引的字符为起点,长度由numBytes决定。

    下面的例子创建了两个ByteArrayInputStream,用字母表的字节表示初始化它们:

    //DemonstrateByteArrayInputStream.

    importjava.io.*;

    classByteArrayInputStreamDemo{

    publicstaticvoidmain(Stringargs[])throwsIOException{

    Stringtmp="abcdefghijklmnopqrstuvwxyz";

    byteb[]=tmp.getBytes();

    ByteArrayInputStreaminput1=newByteArrayInputStream(b);

    ByteArrayInputStreaminput2=newByteArrayInputStream(b,0,3);

    }

    }

    input1对象包含整个字母表中小写字母,input2仅包含开始的三个字母。

    ByteArrayInputStream实现mark()和reset()方法。然而,如果mark()不被调用,reset()在流的开始设置流指针——该指针是传递给构造函数的字节数组的首地址。下面的例子说明了怎样用reset()方法两次读取同样的输入。这种情况下,我们读取数据,然后分别用小写和大写字母打印“abc”。

    importjava.io.*;

    classByteArrayInputStreamReset{

    publicstaticvoidmain(Stringargs[])throwsIOException{

    Stringtmp="abc";

    byteb[]=tmp.getBytes();

    ByteArrayInputStreamin=newByteArrayInputStream(b);

    for(inti=0;i<2;i++){

    intc;

    while((c=in.read())!=-1){

    if(i==0){

    System.out.print((char)c);

    }else{

    System.out.print(Character.toUpperCase((char)c));

    }

    }

    System.out.println();

    in.reset();

    }

    }

    }

    该例先从流中读取每个字符,然后以小写字母形式打印。然后重新设置流并从头读起,这次在打印之前先将字母转换成大写字母。下面是输出:

    abc

    ABC

    ByteArrayOutputStream(字节数组输出流)

     

    ByteArrayOutputStream是一个把字节数组当作输出流的实现。ByteArrayOutputStream有两个构造函数,如下:

    ByteArrayOutputStream()

    ByteArrayOutputStream(intnumBytes)

    在第一种形式里,一个32位字节的缓冲器被生成。第二个构造函数生成一个跟指定numBytes相同位数的缓冲器。缓冲器保存在ByteArrayOutputStream的受保护的buf成员里。缓冲器的大小在需要的情况下会自动增加。缓冲器保存的字节数是由ByteArrayOutputStream的受保护的count域保存的。

    下面的例子说明了ByteArrayOutputStream:

    //DemonstrateByteArrayOutputStream.

    importjava.io.*;

    classByteArrayOutputStreamDemo{

    publicstaticvoidmain(Stringargs[])throwsIOException{

    ByteArrayOutputStreamf=newByteArrayOutputStream();

    Strings="Thisshouldendupinthearray";

    bytebuf[]=s.getBytes();

    f.write(buf);

    System.out.println("Bufferasastring");

    System.out.println(f.toString());

    System.out.println("Intoarray");

    byteb[]=f.toByteArray();

    for(inti=0;i<b.length;i++){

    System.out.print((char)b[i]);

    }

    System.out.println(" ToanOutputStream()");

    OutputStreamf2=newFileOutputStream("test.txt");

    f.writeTo(f2);

    f2.close();

    System.out.println("Doingareset");

    f.reset();

    for(inti=0;i<3;i++)

    f.write('X');

    System.out.println(f.toString());

    }

    }

    运行程序后,生成下面的输出。注意在调用reset()之后,三个X怎样结束。

    Bufferasastring

    Thisshouldendupinthearray

    Intoarray

    Thisshouldendupinthearray

    ToanOutputStream()

    Doingareset

    XXX

    该例用writeTo()这一便捷的方法将f的内容写入test.txt,检查在前面例子中生成的test.txt文件内容,结果如下:

    Thisshouldendupinthearray

    过滤字节流

     

    过滤流(filteredstream)仅仅是底层透明地提供扩展功能的输入流(输出流)的包装。

    这些流一般由普通类的方法(即过滤流的一个超类)访问。典型的扩展是缓冲,字符转换和原始数据转换。这些过滤字节流是FilterInputStream和FilterOutputStream。它们的构造函数如下:

    FilterOutputStream(OutputStreamos)

    FilterInputStream(InputStreamis)

    这些类提供的方法和InputStream及OutputStream类的方法相同。

    缓冲字节流

     

    对于字节流,缓冲流(bufferedstream),通过把内存缓冲器连到输入/输出流扩展一个过滤流类。该缓冲器允许Java对多个字节同时进行输入/输出操作,提高了程序性能。因为缓冲器可用,所以可以跳过、标记和重新设置流。缓冲字节流类是BufferedInputStream和BufferedOutputStream。PushbackInputStream也可实现缓冲流。

    BufferedInputStream(缓冲输入流)

    缓冲输入/输出是一个非常普通的性能优化。Java的BufferedInputStream类允许把任何InputStream类“包装”成缓冲流并使它的性能提高。

    BufferedInputStream有两个构造函数:

    BufferedInputStream(InputStreaminputStream)

    BufferedInputStream(InputStreaminputStream,intbufSize)

    第一种形式生成了一个默认缓冲长度的缓冲流。第二种形式缓冲器大小是由bufSize传入的。使用内存页或磁盘块等的若干倍的缓冲区大小可以给执行性能带来很大的正面影响。但这是依赖于执行情况的。最理想的缓冲长度一般与主机操作系统、可用内存空间及机器配置有关。合理利用缓冲不需要特别复杂的操作。一般缓冲大小为8192个字节,给输入/输出流设定一个更小的缓冲器通常是好的方法。用这样的方法,低级系统可以从磁盘或网络读取数据块并在缓冲器中存储结果。因此,即使你在InputStream外同时读取字节数据时,也可以在超过99.9%的时间里获得快速存储操作。

    缓冲一个输入流同样提供了在可用缓冲器的流内支持向后移动的必备基础。除了在任何InputStream类中执行的read()和skip()方法外,BufferedInputStream同样支持mark()和reset()方法。BufferedInputStream.markSupported()返回true是这一支持的体现。

    下面的例子设计了一种情形,该情形下,我们可以使用mark()来记忆我们在输入流中的位置,然后用reset()方法返回该位置。这个例子分析了HTML实体的引用为版权信息的情况。这个引用以一个(&)符号开始以分号(;)结束,没有任何空格。例子输入由两个&符号来说明何处reset()发生,何处不发生的情况。

    //Usebufferedinput.

    importjava.io.*;

    classBufferedInputStreamDemo{

    publicstaticvoidmain(Stringargs[])throwsIOException{

    Strings="Thisisa&copy;copyrightsymbol"+

    "butthisis&copynot. ";

    bytebuf[]=s.getBytes();

    ByteArrayInputStreamin=newByteArrayInputStream(buf);

    BufferedInputStreamf=newBufferedInputStream(in);

    intc;

    booleanmarked=false;

    while((c=f.read())!=-1){

    switch(c){

    case'&':

    if(!marked){

    f.mark(32);

    marked=true;

    }else{

    marked=false;

    }

    break;

    case';':

    if(marked){

    marked=false;

    System.out.print("(c)");

    }else

    System.out.print((char)c);

    break;

    case'':

    if(marked){

    marked=false;

    f.reset();

    System.out.print("&");

    }else

    System.out.print((char)c);

    break;

    default:

    if(!marked)

    System.out.print((char)c);

    break;

    }

    }

    }

    }

    注意该例运用mark(32),该方法保存接下来所读取的32个字节(这个数量对所有的实体引用都足够)。下面是程序的输出:

    Thisisa(c)copyrightsymbolbutthisis&copynot.

    警告:在缓冲器中使用mark()是受限的。意思是说你只能给mark()定义一个小于流缓冲大小的参数。

    BufferedOutputStream(缓冲输出流)

    BufferedOutputStream与任何一个OutputStream相同,除了用一个另外的flush()方法来保证数据缓冲器被写入到实际的输出设备。因为BufferedOutputStream是通过减小系统写数据的时间而提高性能的,可以调用flush()方法生成缓冲器中待写的数据。不像缓冲输入,缓冲输出不提供额外的功能,Java中输出缓冲器是为了提高性能的。

    下面是两个可用的构造函数:

    BufferedOutputStream(OutputStreamoutputStream)

    BufferedOutputStream(OutputStreamoutputStream,intbufSize)

    第一种形式创建了一个使用512字节缓冲器的缓冲流。第二种形式,缓冲器的大小由bufSize参数传入。

    PushbackInputStream(推回输入流)

    缓冲的一个新颖的用法是实现推回(pushback)。Pushback用于输入流允许字节被读取然后返回(即“推回”)到流。PushbackInputStream类实现了这个想法。它提供了一种机制来“窥视”在没有受到破坏的情况下输入流生成了什么。

    PushbackInputStream有两个构造函数:

    PushbackInputStream(InputStreaminputStream)

    PushbackInputStream(InputStreaminputStream,intnumBytes)

    第一种形式创建了一个允许一个字节推回到输入流的流对象。第二种形式创建了一个具有numBytes长度缓冲区的推回缓冲流。它允许多个字节推回到输入流。

    除了具有与InputStream相同的方法,PushbackInputStream提供了unread()方法,表示如下:

    voidunread(intch)

    voidunread(bytebuffer[])

    voidunread(bytebuffer,intoffset,intnumChars)

    第一种形式推回ch的低位字节,它将是随后调用read()方法所返回的下一个字节。第二种形式返回buffer缓冲器中的字节。第三种形式推回buffer中从offset处开始的numChars个字节。如果在推回缓冲器为满时试图返回一个字节,IOException异常将被引发。

    Java2对PushbackInputStream作了一些小的修改:它实现skip()方法。

    下面的例子演示一个编程语言解析器怎样用PushbackInputStream和unread()来处理==操作符和=操作符之间的不同的。

    //Demonstrateunread().

    importjava.io.*;

    classPushbackInputStreamDemo{

    publicstaticvoidmain(Stringargs[])throwsIOException{

    Strings="if(a==4)a=0; ";

    bytebuf[]=s.getBytes();

    ByteArrayInputStreamin=newByteArrayInputStream(buf);

    PushbackInputStreamf=newPushbackInputStream(in);

    intc;

    while((c=f.read())!=-1){

    switch(c){

    case'=':

    if((c=f.read())=='=')

    System.out.print(".eq.");

    else{

    System.out.print("<-");

    f.unread(c);

    }

    break;

    default:

    System.out.print((char)c);

    break;

    }

    }

    }

    }

    下面是例子程序的输出。注意==被“.eq”代替而=被“<-”代替。

    if(a.eq.4)a<-0;

    注意:PushbackInputStream具有使InputStream生成的mark()或reset()方法失效的副作用。用markSupported()来检查你运用mark()/reset()的任何流类。

    SequenceInputStream(顺序输入流)

     

    SequenceInputStream类允许连接多个InputStream流。SequenceInputStream的构造不同于任何其他的InputStream。SequenceInputStream构造函数要么使用一对InputStream,要么用InputStream的一个Enumeration,显示如下:

    SequenceInputStream(InputStreamfirst,InputStreamsecond)

    SequenceInputStream(EnumerationstreamEnum)

    操作上来说,该类满足读取完第一个InputStream后转去读取第二个流的读取要求。使用Enumeration的情况下,它将继续读取所有InputStream流直到最后一个被读完。

    下面是用SequenceInputStream输出两个文件内容的例子程序:

    //Demonstratesequencedinput.

    importjava.io.*;

    importjava.util.*;

    classInputStreamEnumeratorimplementsEnumeration{

    privateEnumerationfiles;

    publicInputStreamEnumerator(Vectorfiles){

    this.files=files.elements();

    }

    publicbooleanhasMoreElements(){

    returnfiles.hasMoreElements();

    }

    publicObjectnextElement(){

    try{

    returnnewFileInputStream(files.nextElement().toString());

    }catch(Exceptione){

    returnnull;

    }

    }

    }

    classSequenceInputStreamDemo{

    publicstaticvoidmain(Stringargs[])throwsException{

    intc;

    Vectorfiles=newVector();

    files.addElement("/autoexec.bat");

    files.addElement("/config.sys");

    InputStreamEnumeratore=newInputStreamEnumerator(files);

    InputStreaminput=newSequenceInputStream(e);

    while((c=input.read())!=-1){

    System.out.print((char)c);

    }

    input.close();

    }

    }

    该例创建了一个Vector向量并向它添加了两个文件名。它把名字向量传给

    InputStreamEnumerator类,设计该类是为了提供向量包装器,向量返回的元素不是文件名,而是用这些名称打开FileInputStream流。SequenceInputStream依次打开每个文件,该程序打印了两个文件的内容。

    PrintStream(打印流)

     

    PrintStream具有本书开始以来我们在System文件句柄使用过的System.out所有的格式化性能。PrintStream有两个构造函数:

    PrintStream(OutputStreamoutputStream)

    PrintStream(OutputStreamoutputStream,booleanflushOnNewline)

    当flushOnNewline控制Java每次刷新输出流时,输出一个换行符( )。如果flushOnNewline为true,自动刷新。若为false,刷新不能自动进行。第一个构造函数不支持自动刷新。

    Java的PrintStream对象支持包括Object在内的各种类型的print()和println()方法。如果参数不是一个简单类型,PrintStream方法将调用对象的toString()方法,然后打印结果。

    RandomAccessFile(随机访问文件类)

    RandomAccessFile包装了一个随机访问的文件。它不是派生于InputStream和OutputStream,而是实现定义了基本输入/输出方法的DataInput和DataOutput接口。它同样支持定位请求——也就是说,可以在文件内部放置文件指针。它有两个构造函数:

    RandomAccessFile(FilefileObj,Stringaccess)

    throwsFileNotFoundException

    RandomAccessFile(Stringfilename,Stringaccess)

    throwsFileNotFoundException

    第一种形式,fileObj指定了作为File对象打开的文件的名称。第二种形式,文件名是由filename参数传入的。两种情况下,access都决定允许访问何种文件类型。如果是“r”,那么文件可读不可写,如果是“rw”,文件以读写模式打开。

    下面所示的seek()方法,用来设置文件内部文件指针的当前位置:

    voidseek(longnewPos)throwsIOException

    这里,newPos指文件指针从文件开始以字节方式指定新位置。调用seek()方法后,接下来的读或写操作将在文件的新位置发生。

    RandomAccessFile实现了用来读写随机访问文件的标准的输入和输出方法。下面是Java2增添的新方法setLength()。它有下面的形式:

    voidsetLength(longlen)throwsIOException

    该方法通过指定的len设置正在调用的文件的长度。该方法可以增长或缩短一个文件。

    如果文件被加长,增加的部分是未定义的。

    字符流

     

    尽管字节流提供了处理任何类型输入/输出操作的足够的功能,它们不能直接操作Unicode字符。既然Java的一个主要目的是支持“只写一次,到处运行”的哲学,包括直接第17章输入/输出:探究java.jo383的字符输入/输出支持是必要的。本节将讨论几个字符输入/输出类。如前所述,字符流层次结构的顶层是Reader和Writer抽象类。我们将从它们开始。

    注意:如第12章讨论过的,字符输入/输出类是在java的1.1版本中新加的。由此,你仍然可以发现遗留下的程序代码在应该使用字符流时却使用了字节流。当遇到这种代码,最好更新它。

    Reader

     

    Reader是定义Java的流式字符输入模式的抽象类。该类的所有方法在出错情况下都将引发IOException异常。表10-3给出了Reader类中的方法。

     

    Writer

     

    Writer是定义流式字符输出的抽象类。所有该类的方法都返回一个void值并在出错条件下引发IOException异常。表10-4给出了Writer类中方法。

    FileReader类创建了一个可以读取文件内容的Reader类。它最常用的构造函数显示如下:

    FileReader(StringfilePath)

    FileReader(FilefileObj)

    每一个都能引发一个FileNotFoundException异常。这里,filePath是一个文件的完整路径,fileObj是描述该文件的File对象。

    下面的例子演示了怎样从一个文件逐行读取并把它输出到标准输入流。例子读它自己的源文件,该文件一定在当前目录。

    //DemonstrateFileReader.

    importjava.io.*;

    classFileReaderDemo{

    publicstaticvoidmain(Stringargs[])throwsException{

    FileReaderfr=newFileReader("FileReaderDemo.java");

    BufferedReaderbr=newBufferedReader(fr);

    Strings;

    while((s=br.readLine())!=null){

    System.out.println(s);

    }

    fr.close();

    }

    }

    FileWriter

     

    FileWriter创建一个可以写文件的Writer类。它最常用的构造函数如下:

    FileWriter(StringfilePath)

    FileWriter(StringfilePath,booleanappend)

    FileWriter(FilefileObj)

    它们可以引发IOException或SecurityException异常。这里,filePath是文件的完全路径,fileObj是描述该文件的File对象。如果append为true,输出是附加到文件尾的。

    FileWriter类的创建不依赖于文件存在与否。在创建文件之前,FileWriter将在创建对象时打开它来作为输出。如果你试图打开一个只读文件,将引发一个IOException异常。

    下面的例子是前面讨论FileOutputStream时用到例子的字符流形式的版本。它创建了一个样本字符缓冲器,开始生成一个String,然后用getChars()方法提取字符数组。然后该例创建了三个文件。第一个file1.txt,包含例子中的隔个字符。第二个file2.txt,包含所有的字符。最后,第三个文件file3.txt,只含有最后的四分之一。

    //DemonstrateFileWriter.

    importjava.io.*;

    classFileWriterDemo{

    publicstaticvoidmain(Stringargs[])throwsException{

    Stringsource="Nowisthetimeforallgoodmen "

    +"tocometotheaidoftheircountry "

    +"andpaytheirduetaxes.";

    charbuffer[]=newchar[source.length()];

    source.getChars(0,source.length(),buffer,0);

    FileWriterf0=newFileWriter("file1.txt");

    for(inti=0;i<buffer.length;i+=2){

    f0.write(buffer[i]);

    }

    f0.close();

    FileWriterf1=newFileWriter("file2.txt");

    f1.write(buffer);

    f1.close();

    FileWriterf2=newFileWriter("file3.txt");

    f2.write(buffer,buffer.length-buffer.length/4,buffer.length/4);

    f2.close();

    }

    }

    CharArrayReader

     

    CharArrayReader是一个把字符数组作为源的输入流的实现。该类有两个构造函数,每一个都需要一个字符数组提供数据源:

    CharArrayReader(chararray[])

    CharArrayReader(chararray[],intstart,intnumChars)

    这里,array是输入源。第二个构造函数从你的字符数组的子集创建了一个Reader,该子集以start指定的索引开始,长度为numChars。

    下面的例子用到了上述CharArrayReader的两个构造函数:

    //DemonstrateCharArrayReader.

    importjava.io.*;

    publicclassCharArrayReaderDemo{

    publicstaticvoidmain(Stringargs[])throwsIOException{

    Stringtmp="abcdefghijklmnopqrstuvwxyz";

    intlength=tmp.length();

    charc[]=newchar[length];

    tmp.getChars(0,length,c,0);

    CharArrayReaderinput1=newCharArrayReader(c);

    CharArrayReaderinput2=newCharArrayReader(c,0,5);

    inti;

    System.out.println("input1is:");

    while((i=input1.read())!=-1){

    System.out.print((char)i);

    }

    System.out.println();

    System.out.println("input2is:");

    while((i=input2.read())!=-1){

    System.out.print((char)i);

    }

    System.out.println();

    }

    }

    input1对象由全部的小写字母构造,而input2值包含最初的5个字符。下面是输出:

    input1is:

    abcdefghijklmnopqrstuvwxyz

    input2is:

    abcde

    CharArrayWriter

     

    CharArrayWriter实现了以数组作为目标的输出流。CharArrayWriter有两个构造函数:

    CharArrayWriter()

    CharArrayWriter(intnumChars)

    第一种形式,创建了一个默认长度的缓冲器。第二种形式,缓冲器长度由numChars指定。缓冲器保存在CharArrayWriter的buf成员中。缓冲器大小在需要的情况下可以自动增长。缓冲器保持的字符数包含在CharArrayWriter的count成员中。buf和count都是受保护的域。

    下面的例子阐述了CharArrayWriter,我们继续使用前面显示的ByteArrayOutputStream例子中演示的程序。它的输出与以前的例子输出相同:

    //DemonstrateCharArrayWriter.

    importjava.io.*;

    classCharArrayWriterDemo{

    publicstaticvoidmain(Stringargs[])throwsIOException{

    CharArrayWriterf=newCharArrayWriter();

    Strings="Thisshouldendupinthearray";

    charbuf[]=newchar[s.length()];

    s.getChars(0,s.length(),buf,0);

    f.write(buf);

    System.out.println("Bufferasastring");

    System.out.println(f.toString());

    System.out.println("Intoarray");

    charc[]=f.toCharArray();

    for(inti=0;i<c.length;i++){

    System.out.print(c[i]);

    }

    System.out.println(" ToaFileWriter()");

    FileWriterf2=newFileWriter("test.txt");

    f.writeTo(f2);

    f2.close();

    System.out.println("Doingareset");

    f.reset();

    for(inti=0;i<3;i++)

    f.write('X');

    System.out.println(f.toString());

    }

    }

    BufferedReader

     

    BufferedReader通过缓冲输入提高性能。它有两个构造函数:

    BufferedReader(ReaderinputStream)

    BufferedReader(ReaderinputStream,intbufSize)

    第一种形式创建一个默认缓冲器长度的缓冲字符流。第二种形式,缓冲器长度由bufSize传入。

    和字节流的情况相同,缓冲一个输入字符流同样提供支持可用缓冲器中流内反向移动的基础。为支持这点,BufferedReader实现了mark()和reset()方法,并且BufferedReader.markSupported()返回true.。

    下面的例子改写了前面的BufferedInputStream例子,它用一个BufferedReader字符流而不是用一个缓冲字节流。和以前一样,它用mark()和reset()方法解析一个作为版权记号的HTML实体引用的流。这样的引用以&符号开始,以分号(;)结束,没有任何空格。例子输入有两个&字符,用来显示何处reset()发生,何处不发生的情况。输出与前面的输出相同。

    //Usebufferedinput.

    importjava.io.*;

    classBufferedReaderDemo{

    publicstaticvoidmain(Stringargs[])throwsIOException{

    Strings="Thisisa&copy;copyrightsymbol"+

    "butthisis&copynot. ";

    charbuf[]=newchar[s.length()];

    s.getChars(0,s.length(),buf,0);

    CharArrayReaderin=newCharArrayReader(buf);

    BufferedReaderf=newBufferedReader(in);

    intc;

    booleanmarked=false;

    while((c=f.read())!=-1){

    switch(c){

    case'&':

    if(!marked){

    f.mark(32);

    marked=true;

    }else{

    marked=false;

    }

    break;

    case';':

    if(marked){

    marked=false;

    System.out.print("(c)");

    }else

    System.out.print((char)c);

    break;

    case'':

    if(marked){

    marked=false;

    f.reset();

    System.out.print("&");

    }else

    System.out.print((char)c);

    break;

    default:

    if(!marked)

    System.out.print((char)c);

    break;

    }

    }

    }

    }

    BufferedWriter

     

    BufferedWriter是一个增加了flush()方法的Writer。flush()方法可以用来确保数据缓冲器确实被写到实际的输出流。用BufferedWriter可以通过减小数据被实际的写到输出流的次数而提高程序的性能。

    BufferedWriter有两个构造函数:

    BufferedWriter(WriteroutputStream)

    BufferedWriter(WriteroutputStream,intbufSize)

    第一种形式创建了使用默认大小缓冲器的缓冲流。第二种形式中,缓冲器大小是由bufSize参数传入的。

    PushbackReader

     

    PushbackReader类允许一个或多个字符被送回输入流。这使你可以对输入流进行预测。

    下面是它的两个构造函数:

    PushbackReader(ReaderinputStream)

    PushbackReader(ReaderinputStream,intbufSize)

    第一种形式创建了一个允许单个字节被推回的缓冲流。第二种形式,推回缓冲器的大小由bufSize参数传入。

    PushbackReader提供了unread()方法。该方法返回一个或多个字符到调用的输入流。

    它有下面的三种形式:

    voidunread(intch)

    voidunread(charbuffer[])

    voidunread(charbuffer[],intoffset,intnumChars)

    第一种形式推回ch传入的字符。它是被并发调用的read()返回的下一个字符。第二种形式返回buffer中的字符。第三种形式推回buffer中从offset开始的numChars个字符。如果在推回缓冲器为满的条件下试图返回一个字符,一个IOException异常将被引发。

    下面的例子重写了前面的PushBackInputStream例子,用PushbackReader代替了PushBackInputStream。和以前一样,它演示了一个编程语言解析器怎样用一个推回流处理用于比较的==操作符和用于赋值的=操作符之间的不同。

    //Demonstrateunread().

    importjava.io.*;

    classPushbackReaderDemo{

    publicstaticvoidmain(Stringargs[])throwsIOException{

    Strings="if(a==4)a=0; ";

    charbuf[]=newchar[s.length()];

    s.getChars(0,s.length(),buf,0);

    CharArrayReaderin=newCharArrayReader(buf);

    PushbackReaderf=newPushbackReader(in);

    intc;

    while((c=f.read())!=-1){

    switch(c){

    case'=':

    if((c=f.read())=='=')

    System.out.print(".eq.");

    else{

    System.out.print("<-");

    f.unread(c);

    }

    break;

    default:

    System.out.print((char)c);

    break;

    }

    }

    }

    }

    PrintWriter

     

    PrintWriter本质上是PrintStream的字符形式的版本。它提供格式化的输出方法print()和println()。PrintWriter有四个构造函数:

    PrintWriter(OutputStreamoutputStream)

    PrintWriter(OutputStreamoutputStream,booleanflushOnNewline)

    PrintWriter(WriteroutputStream)

    PrintWriter(WriteroutputStream,booleanflushOnNewline)

    flushOnNewline控制Java是否在每次输出换行符( )时刷新输出流。如果flushOnNewline为true,刷新自动发生。若为false,不进行自动刷新。第一个和第三个构造函数不能自动刷新。

    Java的PrintWriter对象支持包括用于Object在内的各种类型的print()和println()方法。如果语句不是一个简单类型,PrintWriter的方法将调用对象的toString()方法,然后输出结果。

    使用流式输入/输出

     

    下面的例子演示了几个Java的输入/输出字符流类和方法。该程序执行标准wc(字数统计)命令。程序有两个模式:如果没有语句提供的文件名存在,程序对标准输入流进行操作。如果一个或多个文件名被指定,程序对每一个文件进行操作。

    //Awordcountingutility.

    importjava.io.*;

    classWordCount{

    publicstaticintwords=0;

    publicstaticintlines=0;

    publicstaticintchars=0;

    publicstaticvoidwc(InputStreamReaderisr)

    throwsIOException{

    intc=0;

    booleanlastWhite=true;

    StringwhiteSpace=" ";

    while((c=isr.read())!=-1){

    //Countcharacters

    chars++;

    //Countlines

    if(c==' '){

    lines++;

    }

    //Countwordsbydetectingthestartofaword

    intindex=whiteSpace.indexOf(c);

    if(index==-1){

    if(lastWhite==true){

    ++words;

    }

    lastWhite=false;

    }

    else{

    lastWhite=true;

    }

    }

    if(chars!=0){

    ++lines;

    }

    }

    publicstaticvoidmain(Stringargs[]){

    FileReaderfr;

    try{

    if(args.length==0){//We'reworkingwithstdin

    wc(newInputStreamReader(System.in));

    }

    else{//We'reworkingwithalistoffiles

    for(inti=0;i<args.length;i++){

    fr=newFileReader(args[i]);

    wc(fr);

    }

    }

    }

    catch(IOExceptione){

    return;

    }

    System.out.println(lines+""+words+""+chars);

    }

    }

    wc()方法对任何输入流进行操作并且计算字符数,行数和字数。它在lastNotWhite里追踪字数的奇偶和空格。当在没有参数的情况下执行时,WordCount以System.in为源流生成一个InputStreamReader对象。该流然后被传递到实际计数的wc()方法。当在有一个或多个参数的情况下执行时,WordCount假设这些文件名存在并给每一个文件创建FileReader,传递保存结果的FileReader对象给wc()方法。两种情况下,在退出之前都打印结果。

    用StreamTokenizer(流标记)来改善wc()

     

    在输入流中一个更好的寻找模式的方法使用Java的另一个输入/输出类:StreamTokenizer。与第16章的StringTokenizer相似,StreamTokenizer把InputStream拆解到被字符组界定的标记(token)中。它有下面的构造函数:

    StreamTokenizer(ReaderinStream)

    这里,inStream必须具有Reader的某种形式。

    StreamTokenizer定义了几个方法。该例中,我们仅用到少数几个。为重置分隔符的默认设置,我们使用resetSyntax()方法。分隔符的默认设置与标记表征的Java程序完美和谐,而且是为该例专用的。我们规定我们的标记,即“字”,是两边都有空格的明显字符组成的连续的字符串。

    我们用eolIsSignificant()来保证换行符作为标记被传递,所以我们可以和计算字数一样计算行数。它的通常形式如下:

    voideolIsSignificant(booleaneolFlag)

    如果eolFlag为true,行结束符作为标记返回;若为false,行结束符被忽略。

    wordChars()方法用来指定可以用于字的字符范围。它的通常形式如下:

    voidwordChars(intstart,intend)

    这里,start和end指定了有效字符的范围。程序中,从33到255范围内的字符都是有效字符。

    空格符由whitespaceChars()说明。它的一般形式如下:

    voidwhitespaceChars(intstart,intend)

    这里,start和end指定了有效空格符的范围。

    下一个标记通过调用nextToken()从输入流获得,它返回标记的类型。

    StreamTokenizer定义个四个int型常量:TT_EOF,TT_EOL,TT_NUMBER和TT_WORD。

    有三个实例变量。nval是一个公开的double型变量,用来保存可识别的字数的值。sval是一个publicString型变量,用来保存可识别的的字的值。ttype是一个int型变量,说明刚刚被nextToken()方法读取的标记的类型。如果标记是一个字,ttype等于TT_WORD。如果标记为一个数,ttype等于TT_NUMBER。如果标记是单一字符,ttype包含该字符的值。如果遇到一个行结束情况,ttype等于TT_EOL(这假定了参数为true调用eolIsSignificant())。如果遇到流的结尾,ttype等于TT_EOF。

    用StreamTokenizer修改过的字数计算程序显示如下:

    //EnhancedwordcountprogramthatusesaStreamTokenizer

    importjava.io.*;

    classWordCount{

    publicstaticintwords=0;

    publicstaticintlines=0;

    publicstaticintchars=0;

    publicstaticvoidwc(Readerr)throwsIOException{

    StreamTokenizertok=newStreamTokenizer(r);

    tok.resetSyntax();

    tok.wordChars(33,255);

    tok.whitespaceChars(0,'');

    tok.eolIsSignificant(true);

    while(tok.nextToken()!=tok.TT_EOF){

    switch(tok.ttype){

    casetok.TT_EOL:

    lines++;

    chars++;

    break;

    casetok.TT_WORD:

    words++;

    default://FALLSTHROUGH

    chars+=tok.sval.length();

    break;

    }

    }

    }

    publicstaticvoidmain(Stringargs[]){

    if(args.length==0){//We'reworkingwithstdin

    try{

    wc(newInputStreamReader(System.in));

    System.out.println(lines+""+words+""+chars);

    }catch(IOExceptione){};

    }else{//We'reworkingwithalistoffiles

    inttwords=0,tchars=0,tlines=0;

    for(inti=0;i<args.length;i++){

    try{

    words=chars=lines=0;

    wc(newFileReader(args[i]));

    twords+=words;

    tchars+=chars;

    tlines+=lines;

    System.out.println(args[i]+":"+

    lines+""+words+""+chars);

    }catch(IOExceptione){

    System.out.println(args[i]+":error.");

    }

    }

    System.out.println("total:"+

    tlines+""+twords+""+tchars);

    }

    }

    }

    序列化

     

    序列化(serialization)是把一个对象的状态写入一个字节流的过程。当你想要把你的程序状态存到一个固定的存储区域例如文件时,它是很管用的。稍后一点时间,你就可以运用序列化过程存储这些对象。

    序列化也要执行远程方法调用(RMI)。RMI允许一台机器上的Java对象调用不同机器上的Java对象方法。对象可以作为一个参数提供给那个远程方法。发送机序列化该对象并传送它。接受机反序列化它(关于RMI的更多内容请参看第24章)。

    假设一个被序列化的对象引用了其他对象,同样,其他对象又引用了更多的对象。这一系列的对象和它们的关系形成了一个顺序图表。在这个对象图表中也有循环引用。也就是说,对象X可以含有一个对象Y的引用,对象Y同样可以包含一个对象X的引用。对象同样可以包含它们自己的引用。对象序列化和反序列化的工具被设计出来并在这一假定条件下运行良好。如果你试图序列化一个对象图表中顶层的对象,所有的其他的引用对象都被循环的定位和序列化。同样,在反序列化过程中,所有的这些对象以及它们的引用都被正确的恢复。

    下面是支持序列化的接口和类的概述。

    Serializable接口

     

    只有一个实现Serializable接口的对象可以被序列化工具存储和恢复。Serializable接口没有定义任何成员。它只用来表示一个类可以被序列化。如果一个类可以序列化,它的所有子类都可以序列化。

    声明成transient的变量不被序列化工具存储。同样,static变量也不被存储。

    Externalizable接口

     

    Java的序列化和反序列化的工具被设计出来,所以很多存储和恢复对象状态的工作自动进行。然而,在某些情况下,程序员必须控制这些过程。例如,在需要使用压缩或加密技术时,Externalizable接口为这些情况而设计。

    Externalizable接口定义了两个方法:

    voidreadExternal(ObjectInputinStream)

    throwsIOException,ClassNotFoundException

    voidwriteExternal(ObjectOutputoutStream)

    throwsIOException

    这些方法中,inStream是对象被读取的字节流,outStream是对象被写入的字节流。

    ObjectOutput接口

     

    ObjectOutput继承DataOutput接口并且支持对象序列化。它定义的方法显示于表10-5中。特别注意writeObject()方法,它被称为序列化一个对象。所有这些方法在出错情况下引发IOException异常。

     

    ObjectOutputStream类

     

    ObjectOutputStream类继承OutputStream类和实现ObjectOutput接口。它负责向流写入对象。该类的构造函数如下:

    ObjectOutputStream(OutputStreamoutStream)throwsIOException

    参数outStream是序列化的对象将要写入的输出流。

    该类中最常用的方法列于表10-6。它们在出错情况下引发IOException异常。Java2给ObjectOuputStream增加了一个名为PutField的内部类。该类有助于持久域的编写,它的用法超出了本书的范围。

    ObjectInput接口继承DataInput接口并且定义了表10-7中的方法。它支持对象序列化。

    特别注意readObject()方法,它叫反序列化对象。所有这些方法在出错情况下引发IOException异常。

    ObjectInputStream继承InputStream类并实现ObjectInput接口。ObjectInputStream负责从流中读取对象。该类的构造函数如下:

    ObjectInputStream(InputStreaminStream)

    throwsIOException,StreamCorruptedException

    参数inStream是序列化对象将被读取的输入流。

    该类中最常用的方法列于表10-8。它们在出错情况下将引发IOException异常。Java2为ObjectInputStream增加了一个名为GetField的内部类。它帮助读取持久域,它的用途超出了本书的范围。并且,Java2中不被赞成的readLine()方法不应该继续使用。

    下面的程序说明了怎样实现对象序列化和反序列化。它由实例化一个MyClass类的对象开始。该对象有三个实例变量,它们的类型分别是String,int和double。这是我们希望存储和恢复的信息。

    FileOutputStream被创建,引用了一个名为“serial”的文件。为该文件流创建一个ObjectOutputStream。ObjectOutputStream的writeObject()方法用来序列化对象。对象的输出流被刷新和关闭。

    然后,引用名为“serial”的文件创建一个FileInputStream类并为该文件创建一个ObjectInputStream类。ObjectInputStream的readObject()方法用来反序列化对象。然后对象输入流被关闭。

    注意MyClass被定义成实现Serializable接口。如果不这样做,将会引发一个NotSerializableException异常。试图做一些把MyClass实例变量声明成transient的实验。那些数据在序列化过程中不被保存。

    importjava.io.*;

    publicclassSerializationDemo{

    publicstaticvoidmain(Stringargs[]){

    //Objectserialization

    try{

    MyClassobject1=newMyClass("Hello",-7,2.7e10);

    System.out.println("object1:"+object1);

    FileOutputStreamfos=newFileOutputStream("serial");

    ObjectOutputStreamoos=newObjectOutputStream(fos);

    oos.writeObject(object1);

    oos.flush();

    oos.close();

    }

    catch(Exceptione){

    System.out.println("Exceptionduringserialization:"+e);

    System.exit(0);

    }

    //Objectdeserialization

    try{

    MyClassobject2;

    FileInputStreamfis=newFileInputStream("serial");

    ObjectInputStreamois=newObjectInputStream(fis);

    object2=(MyClass)ois.readObject();

    ois.close();

    System.out.println("object2:"+object2);

    }

    catch(Exceptione){

    System.out.println("Exceptionduringdeserialization:"+e);

    System.exit(0);

    }

    }

    }

    classMyClassimplementsSerializable{

    Strings;

    inti;

    doubled;

    publicMyClass(Strings,inti,doubled){

    this.s=s;

    this.i=i;

    this.d=d;

    }

    publicStringtoString(){

    return"s="+s+";i="+i+";d="+d;

    }

    }

    该程序说明了object1和object2的实例变量是一样的。输出如下:

    object1:s=Hello;i=-7;d=2.7E10

    object2:s=Hello;i=-7;d=2.7E10

    流的益处

     

    Java的输入输出的流式接口为复杂而繁重的任务提供了一个简洁的抽象。过滤流类的组合允许你动态建立客户端流式接口来配合数据传输要求。继承高级流类InputStream、InputStreamReader、Reader和Writer类的Java程序在将来(即使创建了新的和改进的具体类)也能得到合理运用。就像你将在下一章看到的,这种模式在我们从基于文件系统的流转换到网络和套接字流时工作良好。最后,对象序列化有望在未来的Java编程中扮演一个越来越重要的角色。Java的序列化输入/输出类为这些有时显得十分复杂的任务提供了便携的解决方法。



    实践问题:

     

    1.      如果要把水库中的水输送到千家万户,需要构造各种管道;这些管道有粗有细、有又长又短,且型号各不同;如果是输送暖气,那么中途还需要有加压站等设备;这些管道设备和本章学习的IO流的异同之处在哪里?

    2,同样是一件事情,如果对于不同人处理的方法不同;同样是一件事物,人和人的看法不相同;古代诗人有诗曰:“横看成岭侧成峰,远近高低各不同”;这些和我们本章所学的知识能不能联系起来呢?



    小结:

     

    在本章中,我们主要学习了:

    u      高级IO的处理流;

    u      不同的IO的不同处理方法;



    英语词汇:

     

     

    英文             全文                                         中文

     

    Java       Java              一种高效流行的编程语言

    JDK    JAVADevelopkit       Java开发工具包



    练习项目:

     

    用两种方式读一个文件并使用第一个文件的内容写出另一个文件;1.使用纯的字符流处理读写。2.使用纯字节处理读写。并总结两种的利弊。写出自己的感受;(提示:以上所说“处理”都指中间的流的过程处理)

  • 相关阅读:
    混淆
    【虚拟DOM】√
    fn
    notebook
    sourceMappingURL
    十进制转换为十六进制
    关于this和super的区别
    类中访问级别
    Java中字符串解析
    IWorkspace操作
  • 原文地址:https://www.cnblogs.com/gavin110-lgy/p/5715974.html
Copyright © 2020-2023  润新知