探究java.io

JDK8在线Api中文手册

JDK8在线Api英文手册


   本篇研究java.io,该包提供了对I/O操作的支持。我们早就知道,如果不访问外部数据,大多数程序就不能完成它们的目标。数据是从输入源获取的。程序的结果被发送到输出目标。在Java中,这些源或目标的定义非常广泛。例如,网络连接、内存缓冲区或磁盘文件都可以由Java的I/O类操作。尽管在物理上是有区别的,但是这些设备通过相同的抽象实体进行处理:流。流是产生或使用信息的逻辑实体。流是通过Java的I/O系统链接到物理设备。所有流都以相同的方式工作,尽管它们链接的实际物理设备不同。
    注意:
   本篇将介绍打包在java.io中的基于流的I/O系统,它们自Java最初发布以来就已提供且被广泛使用。然而,从1.4版本开始,Java添加了另一套I/O系统,被称为NIO(也就是新I/O系统英文首字母的缩写)。NIO被打包到 java.nio及其子包中。
    注意:
   千万不要混淆这里讨论的I/O系统使用的I/O流与JDK8新增的 流API。虽然它们在概念上相关,但实质上是不同的。因此,本篇使用到术语"流"时,指的是I/O流。

1 I/O 类和接口

   下面列出了java.io定义的I/O类:

BufferedInputStream FileWriter PipedOutputStream
BufferedOutputStream FilterInputStream PipedWriter
BufferedReader FilterOutputStream PipedWriter
BufferedWriter FilterReader PrintStream
ByteArrayInputStream FilterWriter PrintWriter
ByteArrayOutputStream InputStream PushbackReader
CharArrayReader InputStreamReader PushBackReader
CharArrayWriter LineNumberReader RandomAccessFile
Console ObjectInputStream Reader
DataInputStream ObjectInputStream.GetField SequenceInputStream
DataOutputStream ObjectOutputStream SerializablePermission
File ObjectOutputStream.PutField StreamTokenizer
FileDescriptor ObjectStreamClass StringReader
FileInputStream ObjectStreamField StringWriter
FileOutputStream OutputStream Writer
FilePermission OutputStreamWriter
FileReader PipedInputStream

   java.io包还包含两个已经不再赞成使用的类:LineNumberInputStream和StringBufferInputStream,上面没有列出这两个类。对于新代码不应当使用这些类。
   java.io定义了以下接口:

Closeable FileFilter ObjectInputValidation
DataInput FilenameFilter ObjectOutput
DataOutput Flushable ObjectStreamConstants
Extemalizable ObjectInput Serializable

   可以看出,在java.io中有许多类和接口。这些类和接口包含字节流、字符流以及对象串行化(对象的存储和检索)。本篇分析最常用的一些I/O组件。下面首先讨论最特殊的I/O类:

2 File 类

   尽管java.io定义的大多数类用于操作流,但File类却不是。File类直接处理文件和文件系统。也就是说,File类没有指定如何从文件检索信息以及如何向文件中存储信息,而是描述了文件本身的属性。File对象用于获取和操作与磁盘文件关联的信息,例如权限、时间、日期以及目录路径,并且还可以浏览子目录层次。
   注意
   Path接口和Files类是NIO系统的一部分,在许多情况下都为File类提供了强大的替换方案。
   在许多程序中,文件是主要的数据源和目标。尽管在applet中,由于安全原因使用文件有一些限制,但是文件仍然是存储永久信息以及共享信息的主要资源。在Java中,目录被简单地作为带有附加信息的File对象,附加信息是可以通过list()方法检查的一系列文件名。
   下面的构造函数可以用于创建File对象:

File(String directoryPath)
File(String directoryPath,String filename)
File(File dirObj,String filename)
File(URI uriObj)

   其中,directoryPath是文件的路径名;filename是文件或子目录的名称;dirObj是指定目录的File对象;uriObj是描述文件的URI对象。
   下面的示例创建了3个文件:f1、f2以及f3。第1个File对象是使用目录路径作为唯一参数创建的;第2个File对象包含两个参数——路径和文件名。第3个File对象包含指定给f1的文件路径以及文件名,f3和f2引用相同的文件。

File f1 = new File("/");
File f2 = new File("/","autoexec.bat");
File f3 = new File(f1,"autoexec.bat");

   注意
   Java使用介于UNIX和Windows约定之间的路径分隔符。如果在Windows版本的Java中使用正斜杠(/),那么路径仍然会被正确解析。请记住,如果使用Windows约定的反斜杠(),那么在字符串中需要使用转义序列(\)。
   File类定义了获取File对象中标准属性的方法。例如,getName()方法返回文件的名称;getParent()方法返回父目录的名称;并且如果文件存在,exists()方法将返回true,否则返回false,下面的例子演示了File类的一些方法。该例假定存在名为"java"的目录作为根目录,并且在这个根目录下包含文件"COPYRIGHT"。

//Demonstrate File.
import java.io.File;
class FileDemo {
static void p(String s){
      System.out.println(s);
  }
  public static void main(String[] args) {
      File f1 = new File("/java/COPYRIGHT");
      p("File Name: "+f1.getName());
      p("Path: "+f1.getPath());
      p("Abs Path: "+f1.getAbsolutePath());
      p("Parent: "+f1.getParent());
      p(f1.exists()?"exists":"does not exist");
      p(f1.canWrite()?"is writeable":"is not writeable");
      p(f1.canRead()?"is readable":"is not readable");
      p("is "+(f1.isDirectory()?"":"not"+" a directory"));
      p(f1.isFile()?"is normal file":"might be a named pipe");
      p(f1.isAbsolute()?"is absolute":"is not absolute");
      p("File last modified: "+f1.lastModified());
      p("File size: "+f1.length()+" Bytes");
      /**
       * 输出:
       * File Name: COPYRIGHT
       * Path: \java\COPYRIGHT
       * Abs Path: D:\java\COPYRIGHT
       * Parent: \java
       * exists
       * is writeable
       * is readable
       * is not a directory
       * is normal file
       * is not absolute
       * File last modified: 1581307430629
       * File size: 18 Bytes
       */
  }
}

   File类的大多数方法都是自解释型的。不过isFile()和isAbsolute()方法不是自解释型的。如果对某个文件调用isFile()方法,会返回true;如果对某个目录调用isFile()方法,会返回false。此外,对于有些特殊的文件,isFile()方法也返回false,例如设备驱动和命名管道。因此,这个方法可以用于确保使用的文件能像常规文件一样。如果文件具有绝对路径,那么isAbsolute()方法返回true;如果文件的路径是相对的,那么返回false。
   File类还提供了两个有用的实用方法。第一个方法是renameTo(),如下所示:

boolean renameTo(File newName);

   其中,由newName指定的文件名称为调用File对象的新名称。如果操作成功,就返回true,如果文件不能被重命名(例如,如果试图重命名文件,使文件使用已经存在的文件名,就会失败),就返回false。
   第二个实用方法是delete(),该方法删除调用File对象的路径所代表的磁盘文件。该方法如下所示:

boolean delete()

   如果目录为空的话,可以使用delete()方法删除这个目录。如果删除成功,delete()方法将返回true;如果不能删除文件,就返回false。
   表1中是File类定义的其他一些有用方法。

表1 File类定义的其他方法
方 法 描 述
void deleteOnExit() 当Java虚拟机终止时,删除与调用对象关联的文件
long getFreeSpace() 返回在调用对象关联的分区中,剩余存储空间的字节数
long getTotalSpace() 返回在与调用对象关联的分区的存储容量
long getUsableSpace() 返回在与调用对象关联的分区中,剩余可用存储空间的字节数
boolean isHidden() 如果调用文件是隐藏的,就返回true;否则返回false
boolean setLastModified(long millisec) 将调用文件的时间戳设置为由millisec指定的时间,表示从标准时间1970年1月1日(UTC)起到现在经历的毫秒数
boolean setReadOnly() 将调用文件设置为只读的

   File类还提供了将文件标记为可读。可写以及可执行的方法。因为File类实现了Comparable接口,所以也支持compareTo()方法。
   JKD7为File类新增了方法toPath(),该方法如下所示:

Path toPath()

   toPath()方法返回的Path对象表示由调用File对象封装的文件(换句话说,toPath()方法可以将对象转换成Path对象)。Path被打包到java.nio.file包中,是NIO的组成部分。因此,toPath()方法在旧的File类和新的Path接口之间搭建了一座桥梁。

2.1 目录

   目录是包含一系列其他文件和目录的File对象。当为目录创建File对象时,isDirectory()方法就会返回true。在这种情况下,可以对File对象调用list()方法以获取内部的其他文件和目录列表。该方法有两种形式。第一种形式如下所示:

String[] list()

   这种形式返回的文件列表是一个String对象数组。
   下面显示的程序演示了如何使用list()方法检索目录的内容。

//Using directors
import java.io.File;
class DirList {
 public static void main(String[] args) {
      String dirname ="/java";
      File f1 = new File(dirname);
      if(f1.isDirectory()){
          System.out.println("Directory of "+dirname);
          String s[] = f1.list();
          for(int i=0;i<s.length;i++){
              File f = new File(dirname+"/"+s[i]);
              if(f.isDirectory()){
                  System.out.println(s[i]+" is a directory");
              }else{
                  System.out.println(s[i]+" is a file");
              }
          }
      }else{
          System.out.println(dirname+" is not a directory");
      }
      /**
       * 输出:
       * Directory of /java
       * COPYRIGHT is a file
       */
  }
}
2.2 使用FilenameFilter接口

   通常会希望限制list()方法返回的文件数量,使返回结果只包含匹配特定文件名模式(或称为过滤器)的那些文件。为此,必须使用list()方法的第二种形式,如下所示:

String[] list(FilenameFilter FFObj)

   在这种形式中,FFObj是实现了FilenameFilter接口的类的对象。
   FilenameFilter接口只定义了方法accept(),该方法针对列表中的每个文件调用一次,一般形式如下所示:

boolean accept(File directory,String filename)

   在由directory指定的目录中,对于那些应当被包含到列表中的文件(也就是那些能匹配filename参数的文件),accept()方法返回true;对于那些应当排除的文件,accept()方法返回false。
   下面显示的OnlyExt类实现了FilenameFilter接口。再次,将使用该类修改前面的程序,从而显示list()方法返回的文件名的可见性,只包含那些文件名是以指定的文件扩展名结尾的文件。其中,文件扩展名是创建对象时绑定的。

import java.io.File;
import java.io.FilenameFilter;
public class OnlyExt implements FilenameFilter {
    String ext;
    public OnlyExt(String ext){
        this.ext="."+ext;
    }
    @Override
    public boolean accept(File dir, String name) {
        return name.endsWith(ext);
    }
}
//该程序只显示使用.html扩展名的文件
import java.io.File;
import java.io.FilenameFilter;
class DirListOnly {
  public static void main(String[] args) {
      String dirname="/java";
      File f1 = new File(dirname);
      FilenameFilter only = new OnlyExt("html");
      String s[]=f1.list(only);
      for(int i=0;i<s.length;i++){
          System.out.println(s[i]);
      }
  }
}
2.3 listFiles()方法

   list()方法还有一种形式,称为listFiles(),我们可能会发现该方法很有用。listFiles()方法的签名如下所示:

File[] listFiles()
File[] listFiles(FilenameFilter FFObj)
File[] listFiles(FileFilter FObj)

   这些方法将文件列表作为File对象数组而不是字符串数组返回。第1个方法返回所有文件,第2个方法返回那些满足指定的FilenameFilter的文件。除了返回的是File对象数组之外,就工作方式而言,这两个版本的listFiles()方法和它们等价的list()方法类似。
   第3个版本的listFiles()方法返回那些路径名称能满足指定的FileFilter的文件。FileFilter只定义了方法accept(),该方法针对列表中的每个文件调用一次,一般形式如下所示:

boolean accept(File path)

   对于应当包含到列表中的文件(也就是那些匹配path参数的文件),该方法返回true;对于那些应当排除的文件,该方法返回false。

2.4 创建目录

   File类提供的另外两个有用的方法是mkdir()和mkdirs()。mkdir()方法用来创建目录,如果成功,就返回true;否则返回false。有各种原因可能会导致创建失败,例如在File对象中指定的路径已经存在,或者因为整个路径不存在而不能创建目录。要为不存在的路径创建目录,就可以使用mkdirs(),该方法创建目录及其父目录。

3 AutoCloseable、Closeable和Flushable接口

   有3个接口对于流类相当重要。其中两个接口是Closeable和Flushable,它们是在java.io包中定义的,并且是由JDK5添加的。第三个接口是AutoCloseable,它是由JDK7添加的接口,被打包到java.lang包中。
   AutoCloseable接口对带有资源的try语句提供了支持,这种try语句可以自动执行资源关闭过程。只有实现了AutoCloseable接口的类的对象才可以由带资源的try语句进行管理。AutoCloseable接口只定义了close()方法:

void close() throws Exception

   这个方法关闭调用对象,释放可能占用的资源,在带资源的try语句的末尾,会自动调用该方法,因此消除了显示调用close()方法的需要。因为打开流的所有I/O类都实现了这个接口,所以带资源的try语句能够自动关闭所有这种流。自动关闭流可以确保当不再需要时,能够正确的关闭,从而阻止内存泄漏和其他问题产生。
   Closeable接口也定义了close()方法。实现了Closeable接口的类的对象可以被关闭。从JDK7开始,Closeable扩展了AutoCloseable。因此,所有实现了Closeable接口的类也都实现了AutoCloseable接口。
   实现了Flushable接口的类的对象,可以强制将缓冲的输出写入与对象关联的流中。该接口定义了flush()方法,如下所示:

void flush()throws IOException

   刷新流通常会导致缓冲的输出被物理地写入底层设备中。写入流的所有I/O类都实现了Flushable接口。

4 I/O 异常

   在I/O处理中,有两类异常扮演了重要角色。第一类是IOException异常,以为与大多数IO类有关故本篇对其进行介绍。如果发生I/O错误,就会抛出IOException异常。在许多情况下,如果文件无法打开,会抛出FileNotFoundException异常。FileNotFoundException是IOException的子类,所以可以使用用来捕获IOException异常的单条catch子句捕获这两种异常。
   当执行I/O时,另外一种有时很重要的异常是SecurityException。在存在安全管理器的情况下,当试图打开文件时,如果发生安全违规,有些文件类会抛出SecurityException异常。默认情况下,通过java运行的应用程序不使用安全管理器。因此,本篇示例不需要查看是否会抛出SecurityException异常。然而,applet会使用浏览器提供的安全管理器,并且由applet执行的文件I/O可能会产生SecurityException异常。在这种情况下,需要处理这种异常。

5 关闭流的两种方式

   通常,当不再需要时,流必须关闭。如果没有这么做,就可能会导致内存泄漏以及资源紧张。
   从JDK7开始,有两种关闭流的基本方式。第一种是显式地在流上调用close()方法,这是自从Java最初版本发布以来就一直使用的传统方式。对于这种方式,通常是在finally代码中调用close()方法。因此,这种传统的简单框架如下所示:

try{
  //open and access file
}catch(I/O-exception){
//...
}finally{
  //close the file
}

   在JDK7之前的代码中,这种通用技术(以及其他形式)很常见。
   关闭流的第二种形式是使用JDK7新添加的带资源的try语句,从而自动执行这一过程,带资源的try语句是try语句的增强形式,如下所示:

try(resource-sepcification){
//use the resource
}

   其中,resource-specification是声明以及初始化资源(例如文件或其他基于流的资源)的一条或多条语句。其中包含一个标量声明,使用引用(这个引用指向将被管理的对象)初始化该变量。当try代码块结束时,资源被自动释放。对于文件,这以为着文件被自动关闭。因此,不再需要显示地调用close()方法。
   下面是关于带资源的try语句的3个关键点:

  • 由带资源的try语句管理的资源必须是实现了AutoCloseable接口的类的对象。
  • 在try代码中声明的资源被隐式声明为final。
  • 通过使用分号分隔每个声明可以管理多个资源。
       此外请记住,所声明资源的作用域被限制在带资源的try语句中。
       带资源的try语句的主要优点是:当try代码块结束时,资源(在此是流)会被自动关闭。因此,不可能会忘记关闭。使用带资源的try语句,通常可以使资源代码更短、更清晰、更容易维护。
       最后一点:必须通过现代版本的Java编译使用带资源的try语句的例子。在旧的编译器中,它们无法工作,使用传统方式的例子可以由旧版本的Java进行编译。
       请记住:
       因为带资源的try语句流线化了释放资源的过程,并消除了可能在无意中忘记释放资源的风险,所以如果合适的话,推荐将之应用于新代码。

6 流类

   Java中基于流的I/O构建在4个抽象类之上:InputStream、OutputStream、Reader和Wirter。它们用于创建具体的流子类。尽管程序通过具体子类来完成I/O操作,但是顶级类定义了所有流类都通用的基本功能。
   InputStream和OutputStream针对字节流而设计,Reader和Writer针对字符流而设计。字节流类和字符流类形成了不同的层次。通常,当操作字符或字符串时,应当使用字符流,当操作字节或其他二进制对象时,应当使用字节流。

7 字节流

   字节流类为处理面向字节的I/O提供了环境。字节流可以用于任意类型的对象,包括二进制数据。InputStream和OutputStream位于字节流的顶层。

7.1 InputStream类

   InputStream是抽象类,定义了Java的流字节输入模型,并且还实现了AutoCloseable和Closeable接口。当发生I/O错误时,该类中的大部分方法都会抛出IOException异常(方法mark()和markSupported()除外)。表2显示了InputStream类中的方法。

表2 InputStream 类定义的方法
方 法 描 述
int available() 返回当前可读取的字节数
void close() 关闭输入源,如果试图继续进行读取,会产生IOException异常
void mark(int numBytes) 在输入流的当前位置放置标记,该标记在读入numBytes个字节之前一直都有效
boolean markSupported() 如果调用流支持mark()或reset()方法,就返回true
int read() 返回代表下一个可用字节的整数。当到达文件末尾时,返回-1
int read(byte buffer[]) 尝试读取buffer.length个字节到buffer中,并返回实际成功读取的字节数,如果达到文件末尾,就返回-1
void read(byte buffer[],int offset,int numBytes) 尝试读取numBytes个字节到buffer中,从buffer[offset]开始保存读取的字节。该方法返回成功读取的字节数;如果达到文件末尾,就返回-1
void reset() 将输入指针重置为前面设置的标记
void skip(long numBytes) 忽略(即跳过)numBytes个字节的输入,返回实际忽略的字节数
7.2 OutputStream类

   OutputStream是定义流字节输出的抽象类,实现了AutoCloseable和Closeable以及Flushable接口。该类中的大部分方法都返回void。如果发生I/O错误,大部分方法会抛出IOException异常表3显示了OutputStream类中的方法。

表3 OutputStream 类定义的方法
方 法 描 述
void close() 关闭输出流。如果试图继续向流中写入内容,将产生IOException异常
void flush() 结束输出状态,从而清空所有缓冲区,即刷新输出缓冲区
void write(int b) 向输出流中写入单个字节。注意参数是int类型,从而允许使用表达式调用write()方法,而不是表达式强制转换回byte类型
void write(byte buffer[]) 向输出流中写入一个完整的字节数组
void write(byte buffer[],int offset,int numBytes) 将buffer数组中从buffer[offset] 开始的numBytes个字节写入到输出流中
7.3 FileInputStream类

   使用FileInputStream类创建的InputStream对象可以用于从文件读取字节。两个常用的构造函数如下所示:

FileInputStream(String filePath)
FileInputStream(File fileObj)

   这两个构造函数都会抛出FileNotFoundException异常。其中,filePath是文件的完整路径名,fileObj是描述文件的File对象。
   下面的例子创建了两个FileInputStream对象,它们使用相同的磁盘文件,并且分别是使用这两个构造函数创建的:

FileInputStream f0 = new FileInputStream("/autoexec.bat");
File f = new File("autoexec.bat");
FileInputStream f1 = new FileInputStream(f);

   尽管第一个够赞函数可能更常用,但是使用第二个构造函数,在将文件附加到输入流之前,可以使用File类的方法对文件进行进一步的检查。当创建FileInputStream对象时,还可以为读取而打开流。FileInputStream类重写了InputStream抽象类中的6个方法,但没有重写mark()和reset()方法。在FileInputStream对象上试图调用reset()方法时,会抛出IOException。
   下面的例子显示了如何读取单个字节、整个字节数组以及字节数组的一部分。该例该演示了如何使用available()方法确定剩余字节数量,以及如何使用skip()方法略过不希望的字节。该程序读取自己的源文件,文件不再需要时,该程序使用带资源的try语句自动关闭文件。

//Demonstrate FileInputStream.
//This program uses try-with-resources.It requires JDK 7 or later.
import java.io.*;
public class FileInputStreamDemo {
  public static void main(String[] args) {
       int size;
       //Use try-with-resources to close the stream.
       try (FileInputStream f = new FileInputStream("/FileInputStreamDemo.java")) {
           System.out.println("Total Available Bytes: " + (size = f.available()));
           int n = size / 40;
           System.out.println("First " + n + " bytes of the file one read() at a time");
           for (int i = 0; i < n; i++) {
               System.out.print((char) f.read());
           }
           System.out.println("\nStill Available: " + f.available());
           System.out.println("Reading the next " + n + " with one read(b[])");
           byte b[] = new byte[n];
           if (f.read(b) != n) {
               System.err.println("couldn't read " + n + "bytes.");
           }
           System.out.println(new String(b, 0, n));
           System.out.println("\nStill Available: " + (size = f.available()));
           System.out.println("Skipping half of remaining bytes with skip()");
           f.skip(size / 2);
           System.out.println("Still Available: "
                   + f.available());
           System.out.println("Reading " + n / 2 + "into the end of array");
           if (f.read(b, n / 2, n / 2) != n / 2) {
               System.err.println("couldn't read " + n / 2 + "bytes.");
           }
           System.out.println(new String(b, 0, b.length));
           System.out.println("\nStill Available: " + f.available());
       } catch (IOException e) {
           System.out.println("I/O Error: " + e);
       }
       /**
        * 输出:
        * Total Available Bytes: 1777
        * First 44 bytes of the file one read() at a time
        * //Demonstrate FileInputStream.
        * //This progra
        * Still Available: 1733
        * Reading the next 44 with one read(b[])
        * m uses try-with-resource.It requires JDK 7 o
        *
        * Still Available: 1689
        * Skipping half of remaining bytes with skip()
        * Still Available: 845
        * Reading 22into the end of array
        * m uses try-with-resourm.err.println("couldn'
        *
        * Still Available: 823
        */
   }
}

   该例演示了从流中读取内容的3种方式、忽略输入以及检查六中能够读取的数据量的方式。

7.4 FileOutputStream类

   FileOutputStream类创建能够用于向文件中写入字节的OutputStream对象。该类实现了AutoCloseable、Closeable以及Flushable接口,它的4个构造函数如下所示:

FileOutoutStream(String filePath)
FileOutputStream(File fileObj)
FileOutputStream(String filePath,boolean append)
FileOutputStream(File fileObj,boolean append)

   它们都可能抛出FileNotFoundException异常。其中,filePath是文件的完整路径,fileObj是描述文件的File对象。如果append为true,就以追加的方式打开文件。
   FileOutputStream对象的创建不依赖于已经存在的文件。当创建对象时,FileOutputStream会在打开文件之前创建文件。当创建FileOutputStream对象时,如果试图打开只读文件,会抛出异常。
   下面的例子首先创建一个String对象,然后使用getBytes()方法提取与之等价的字节数组,创建一个样本字节缓冲区。然后该例还创建了3个文件。第1个文件是file1.txt,将包含样本中每隔一个字节的字节。第2个文件是file2.txt,将包含整个字节。第3个也是最后一个文件是file3.txt,将只包含最后四份之一的字节。

//Demonstrate FileOutputStream
//This program user the traditional approch to closing a file.
import java.io.FileOutputStream;
import java.io.IOException;
class FileOutputStreamDemo {
    public static void main(String[] args) {
        String source = "Now is the time for all good men\n"
                + " to come to the aid of their country\n"
                + " and pay their due taxes.";
        byte buf[] = source.getBytes();
        FileOutputStream f0 = null;
        FileOutputStream f1 = null;
        FileOutputStream f2 = null;
        try {
            f0 = new FileOutputStream("file1.txt");
            f1 = new FileOutputStream("file2.txt");
            f2 = new FileOutputStream("file3.txt");
            //write to first file
            for (int i=0;i<buf.length;i+=2){
                f0.write(buf[i]);
            }
            //write to second file
            f1.write(buf);
            //write to third file
            f2.write(buf,buf.length-buf.length/4,buf.length/4);;
        } catch (IOException e) {
            System.out.println("An I/O Error Occurred");
        }finally {
            try{
                if(f0!=null){
                    f0.close();
                }
            }catch(IOException e){
                System.out.println("Error Closing file1.txt");
            }
            try{
                if(f1!=null){
                    f1.close();
                }
            }catch (IOException e){
                System.out.println("Error Closing file2.txt");
            }
            try{
                if(f2!=null){
                    f2.close();
                }
            }catch (IOException e){
                System.out.println("Error Closing file3.txt");
            }
        }
    }
}

   输出:
file.txtfile2.txt
file2.txt
   上述程序当文件不再需要时,使用传统方式关闭文件。JDK7之前的所有版本都需要使用这种方式,并且在遗留代码中,该方式的使用仍十分广泛。可以看出,显示调用close()方法需要一些笨拙的代码,因为如果关闭操作失败,那么每次调用都会产生IOException异常。使用新的带资源的try语句,可以在本质上对这个程序进行改进。作为对比,下面是修改后的版本。这个版本更短并且更流线化:

//Demonstrate FileOutputStream
//This program uses try-with-resources.It requires JDK 7 or later.
import java.io.FileOutputStream;
import java.io.IOException;
class FileOutputStreamDemo {
  public static void main(String[] args) {
       String source = "Now is the time for all good men\n"
               + " to come to the aid of their country\n"
               + " and pay their due taxes.";
       byte buf[] = source.getBytes();
       try (FileOutputStream f0 = new FileOutputStream("file1.txt");
            FileOutputStream f1 = new FileOutputStream("file2.txt");
            FileOutputStream f2 = new FileOutputStream("file3.txt")){
           //write to first file
           for (int i=0;i<buf.length;i+=2){
               f0.write(buf[i]);
           }
           //write to second file
           f1.write(buf);
           //write to third file
           f2.write(buf,buf.length-buf.length/4,buf.length/4);;
       } catch (IOException e) {
           System.out.println("An I/O Error Occurred");
       }
   }
}
7.5 ByteArrayInputStream类

   ByteArrayInputStream是使用字节数组作为源的输入流的一个实现。这两个类有两个构造函数,每个构造函数都需要一个字节数组来提供数据源:

ByteArrayInputStream(byte array[])
ByteArrayInputStream(byte array[],int start,int numBytes)

   在此,array是输入源。第二个构造函数从字节数组的子集创建InputStream对象,这个数组子集从start指定的索引位置的字符开始,共numBytes个字符。
   close()方法对ByteArrayInputStream对象没有效果。所以不需要为ByteArrayInputStream对象调用close()方法。但是如果这么做的话也不会产生错误。
   下面的例子创建了两个ByteArrayInputStream对象,使用字母表的字节形式初始化它们:

//Demonstrate ByteArrayInputStream
import java.io.ByteArrayInputStream;
class ByteArrayInputStreamDemo {
    public static void main(String[] args) {
        String tmp = "abcdefghijklmnopqrstuvwxyz";
        byte b[] = tmp.getBytes();
        ByteArrayInputStream input1=new ByteArrayInputStream(b);
        ByteArrayInputStream input2=new ByteArrayInputStream(b,0,3);
    }
}

   input1对象包含整个小写字母表,而input2只包含前3个字母表。
   ByteArrayInputStream实现了mark()和reset()方法。然而,如果没有调用mark()方法,那么reset()方法会将流指针设置为流的开头——在这种情况下,也就是设置为传递给构造函数的字节数组的开头。下一个例子显示了如何使用reset()方法来读取相同的输入两次。在这个例子中,程序以小写形式读取并打印字母"abc"一次,然后再次使用大写形式读取并打印。

import java.io.ByteArrayInputStream;
public class ByteArrayInputStreamReset {
    public static void main(String[] args) {
        String tmp ="abc";
        byte b[] = tmp.getBytes();
        ByteArrayInputStream in=new ByteArrayInputStream(b);
        for (int i=0;i<2;i++){
            int c;
            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
         */
    }
}

   这个程序首先从流中读取每个字符,并以小写形式输出。然后重置流并再次读取,这一次在输出前会将每个字符转换成大写形式。

7.6 ByteArrayOutputStream类

   ByteArrayOutputStream是使用字节数组作为目标的输出流的一个实现。ByteArrayOutputStream有两个构造函数,如下所示:

ByteArrayOutputStream()
ByteArrayOutputStream(int numBytes)

   在第一种形式中,创建一个32字节的缓冲区。第二种形式中,创建一个由numBytes指定大小的缓冲区。缓冲区被保存在ByteArrayOutputStream中受保护的buf域变量中。如果需要的话,缓冲区的大小会自动增加。缓冲区能够保存的字节数量包含在ByteArrayOutputStream中受保护的count域变量中。
   close()方法对ByteArrayOutputStream对象没有效果。所以,不需要为ByteArrayOutputStream对象调用close()方法。但是如果调用的话,也不会产生错误。
   下面的程序演示了ByteArrayOutputStream类的使用:

//Demonstrate ByteArrayOutputStream.
//This program uses try-with-resources.It requires JDK 7 or later.
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
class ByteArrayOutputStreamDemo {
public static void main(String[] args) {
      ByteArrayOutputStream f= new ByteArrayOutputStream();
      String s = "This should end up in the array";
      byte buf[] = s.getBytes();
      try {
          f.write(buf);
      } catch (IOException e) {
          e.printStackTrace();
      }
      System.out.println("Buffer as string");
      System.out.println(f.toString());
      System.out.println("Into array");
      byte b[] = f.toByteArray();
      for (int i=0;i<b.length;i++){
          System.out.print((char)b[i]);
      }
      System.out.println("\nTo an OutputStream()");
      //Use try-with-resources to manage the file stream.
      try(FileOutputStream f2 = new FileOutputStream("test.txt")){
          f.writeTo(f2);
      }catch (IOException e){
          System.out.println("I/O Error: "+e);
          return;
      }
      System.out.println("Doing a reset");
      f.reset();
      for(int i=0;i<3;i++){
          f.write('X');
      }
      System.out.println(f.toString());
  }
    /**
     * 输出:
     * Buffer as string
     * This should end up in the array
     * Into array
     * This should end up in the array
     * To an OutputStream()
     * Doing a reset
     * XXX
     */
}

   这个例子使用writeTo()这个方便地方法来将f的内容写入test.txt文件中。
test.txt

7.7 过滤的字节流

   过滤的字节流是简单的封装器,用于封装底层的输入流和输出流,并且还透明地提供一些扩展级别的功能。这些流一般是通过接受通用流的方法访问的,通过流是过滤的超类,典型的扩展是缓冲、字符转换以及原始数据转换。过滤的字节流类是FilterInputStream和FilterOutputStream,它们的构造函数如下所示:

FilterOutputStream(OutputStream os)
FilterInputStream(InputStream is)

   这两个类提供的方法与InputStream和OutputStream类中的方法相同。

7.8 缓冲的字节流

   对于面向字节的流,缓冲流通过阿静内存缓冲区附加到I/O系统来扩展过滤流。这种流允许Java一次对多个字节执行多次I/O操作,从而提升性能。因为可以使用缓冲区,所以略过,标记或充值流都是可能发生的。缓冲的字节流是BufferedInputStream和BufferedOutputStream。pushbackInputStream也实现了缓冲流。
   1.BufferedInputStream类
   缓冲I/O是很常见的性能优化手段。Java的BufferedInputStream类允许将任何InputStream对象封装到缓冲流中以提高性能。
   BufferedInputStream类有两个构造函数:

BufferedInputStream(InputStream inputStream)
BufferedInputStream(InputStream inputStream,int bufSize)

   第1中形式使用默认缓冲区大小创建缓冲流。第2中形式中,缓冲区大小是由bufSize传递的。使缓冲区大小等于内存页面、磁盘块等大小的整数倍,可以明显提高性能。然而,这依赖于具体实现。最优的缓冲区大小通常依赖于宿主操作系统、可用的内存量以及机器的配置。为了充分利用缓冲,不需要这么复杂。比较好的缓冲大小大约是8192字节,并且对于I/O系统来说即使附加比较小的缓冲区,也总是一个好主意。这样的话,低级的系统就可以从磁盘或网络获取多块数据,并将结果存储在缓冲区找那个。因此,即使正在一次从InputStream对象读取一个字节,大部分时间也都是在操作访问速度很快的内存。
   缓冲输入流还为在可用缓冲流中支持向后移动提供了基础,除了任何InputStream都实现了的read()和skip()方法外,BufferedInputStream还支持mark()和reset()方法,BufferedInputStream.markSupported()方法返回true,这一事实反映了这一特性。
   下面的例子设计了一种情形,在这种情形中,可以使用mark()方法记住位于输入流的什么位置,在后面使用reset()方法返回到这个位置。这个例子解析了HTML试题引用流以获取版权符号。这个引用由“与”(&)符号开始,并以分号(;)结束,中间没有任何空白字符。样本输出使用两个&符合来显示发生reset()方法调用的位置以及不发生调用的位置。

//Use buffered input.
//This program uses try-with_resources.It requires JDK 7 or later
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
class BufferedInputStreamDemo {
    public static void main(String[] args) {
        String s = "This is a &copy; copyright symbol "+
                "but this is a &copy not.\n";
        byte buf[] = s.getBytes();
        ByteArrayInputStream in = new ByteArrayInputStream(buf);
        int c;
        boolean marked = false;
        //Use try-with-resources to manage the file.
        try(BufferedInputStream f = new BufferedInputStream(in)){
            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("&");
                        }
                        break;
                    default:
                        if(!marked){
                            System.out.print((char) c);
                        }
                        break;
                }
            }
        } catch (IOException e) {
            System.out.println("I/O Error: "+e);
        }
    }
}

   这个例子使用mark(32),来为后面32个字节的读取操作保存标记(对于所有实体引用来说,这足够了)。下面是这个程序产生的输出:
   This is a © copyright symbol but this is a &copy not.
   2.BufferedOutputStream类
   除了增加flush()方法之外,BufferedOutputStream与所有OutputStream类似,flush()方法用于确保将数据缓冲区写入到被缓冲的流中。BufferedOutputStream是通过减少系统实际写数据的次数来提高性能的,因此可能需要调用flush()方法,从而要求立即写入缓冲区的所有数据。
   与缓冲输入不同,缓冲输出没有提供附加功能。Java中用于输出的缓冲区只是为了提高性能。下面是两个构造函数:

BufferedOutputStream(OutputStream outputStream)
BufferedOutputStream(OutputStream outputStream,int bufSize)

   第1中形式使用默认缓冲区大小创建缓冲流。在第2中形式中,缓冲区大小是由bufSize传递的。
   3.PushbackInputStream类
   缓冲的新应用之一就是回推(pushback)的实现。回推用于输入流,以允许读取字节,然后再将它们返回(回推)到流中。PushbackInputStream类实现了这一思想,提供了一种机制,可以"偷窥"来自输入流的内容而不对它们进行破坏。
   PushbackInputStream类具有以下构造函数:

PushbackInputStream(InputStream inputStream)
PushbackInputStream(InputStream inputStream,int numBytes)

   第1种形式创建的流对象允许将一个字节返回到输入流;第2中形式创建的流对象具有一个长度为numBytes的回推缓冲区,从而允许将多个字节回推到输入流中。
   除了熟悉的来自InputStream的方法外,PushbackInputStream类还提供了unread()方法,如下所示:

void unread(int b)
void unread(byte buffer[])
void unread(byte buffer,int offset,int numBytes)

   第1种形式回推b的低字节,这会是后续的read()调用返回的下一个字节。第2种形式回推buffer中的字节。第3种形式回推buffer中从offset开始的numBytes个字节。当回推缓冲区已满时,如果试回推字节,就会抛出IOException异常。
   下面的例子显示了当编程语言解析器可能使用PushbackInputStream类和unread()方法来处理比较运算符"=="和赋值运算符“=”时,相互之间如何区别:

//Demonstrate unread().
//This program uses try-with-resources.It requires JDK 7 or later.
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.PushbackInputStream;
class PushbackInputStreamDemo {
    public static void main(String[] args) {
        String a="if (a == 4) a = 0;\n";
        byte buf[] = a.getBytes();
        ByteArrayInputStream in = new ByteArrayInputStream(buf);
        int c;
        try(PushbackInputStream f = new PushbackInputStream(in)){
            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;
                }
            }
        }catch (IOException e){
            System.out.println("I/O Error: "+e);
        }
        /**
         * 输出:
         * f (a .eq. 4) a <- 0;
         */
    }
}

   警告
   PushbackInputStream对象会使得InputStream对象(用于创建PushbackInputStream对象)的mark()或reset()方法无效,对于准备使用mark()或reset()方法的任何流来说,都应当使用markSupported()方法进行检查。

7.9 SequenceInputStream类

   SequenceInputStream类允许连接多个InputStream对象。SequenceInputStream对象的构造与其他所有InputStream对象都不同。SequenceInputStream构造函数使用一对InputStream对象或InputStream对象的一个Enumeration对象作为参数:

SequenceInputStream(InputStream first,InputStream second)
SequenceInputStream(Enumeration <? extends InputStream> streamEnum)

   在操作上,该类从第1个InputStream对象进行读取,直到读取完全部内容,然后切换到第2个InputStream对象。对于使用Enumeration对象的情况,该类持续读取所有InputStream对象中的内容,直到最后一个InputStream对象的末尾为止。当到达每个文件的末尾时,与之关联的流就会被关闭。关闭通过SequenceInputStream创建的流,会导致关闭所有未关闭的流。
   下面是一个简单的例子,该例使用SequenceInputStream对象输出两个文件中的内容。

//Demonstrate sequenced input.
//This program uses the traditional approch to closing a file.
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.Enumeration;
import java.util.Vector;
class InputStreamEnumerator implements Enumeration<FileInputStream> {
    private Enumeration<String> files;

    public InputStreamEnumerator(Vector<String> files) {
        this.files = files.elements();
    }

    @Override
    public boolean hasMoreElements() {
        return files.hasMoreElements();
    }

    @Override
    public FileInputStream nextElement() {
        try {
            return new FileInputStream(files.nextElement().toString());
        } catch (FileNotFoundException e) {
            return null;
        }
    }
}
import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;
import java.util.Vector;
class SequenceInputStreamDemo {
    public static void main(String[] args) {
        int c;
        Vector<String> files = new Vector<String>();
        files.addElement("file1.txt");
        files.add("file2.txt");
        files.add("file3.txt");
        InputStreamEnumerator ise = new InputStreamEnumerator(files);
        try (InputStream input = new SequenceInputStream(ise)) {
            while ((c = input.read()) != -1) {
                System.out.print((char) c);
            }
        } catch (NullPointerException e) {
            System.out.println("Error Opening File.");
        } catch (IOException e) {
            System.out.println("I/O Error: " + e);
        }
    }

}

   该例创建了一个Vector对象,然后为其添加了3个文件。将这个向量的名称传递给InputStreamEnumerator类,使用它们的名称打开FileInputStream对象,设计InputStreamEnumerator类的目的是为向量提供一个封装器,该封装器返回元素而不是返回文件名。SequenceInputStream对象依次打开每个文件,并且这个例子输出文件的内容。
   注意在nextElement()方法中,如果不能打开文件,就返回null。这会导致NullPointerException异常,可以在main方法中捕获异常。

7.10 SequenceInputStream类

   PrintStream类提供了来自System文件句柄System.out的所有输出功能,这使得PrintStream成为Java中最常用的类之一。PrintStream类实现了Appendable、AutoCloseable、Closeable以及Flushable接口。
   PrintStream类定义了几个构造函数,其中下面这几个从刚开始就已指出过:

PrintStream(OutputStream outputStream)
PrintStream(OutputStream outputStream,boolean autoFlushingObj)
PrintStream(OutputStream outputStream,boolean autoFlushingOn,String charSet)throws UnsupportedEncodingException

   在此,outputStream指定了用于接收输出的打开的OutputStream对象。autoFlushingOn参数控制每次写入一个换行符(\n)或字节数组,抑制或调用println()方法时,是否自动刷新输出缓冲区。如果autoFlusingOn为true,就自行刷新;如果为false,就不自动刷新。第一个构造函数不自动刷新缓冲区。可以通过charSet传递的名称来指定字符编码。
   下面的几个构造函数提供了能够输出写入文件中的PrintStream对象的简单方式:

PrintStream(File outputFile) throws FileNotFoundException
PrintStream(File outputFile,String charSet) throws FileNotFoundException,UnsupportedEncodingException
PrintStream(String outputFileName)throws FileNotFoundException
PrintStream(String outputFileName,String charSet)throws FileNotFoundException,UnsupportedEncodingException

   这些构造函数从File对象或根据指定的文件名创建PrintStream对象。两种情况中,都会自动创建文件。所有值钱存在的同名文件都会被销毁。一旦创建PrintStream对象,就可以将所有输出定向到指定的文件中。可以通过charSet传递的名称来指定字符编码。
   注意
   如果存在安全管理器,那么当发生安全性违规时,有些PrintStream构造函数会抛出SecurityException异常。
   PrintStream为所有类型(包括Object)都支持print()和println()方法。如果参数不是基本类型,那么PrintStream方法会调用对象的toString()方法并显示结果。
   JDK5发布以来,Java为PrintStream类添加了printf()方法。该方法允许指定将要写入的数据的精确格式。printf()使用Formatter类将数据写入调用流中。尽管可以手动进行格式化,但是通过直接使用Formatter,printf()方法简化了这一过程。该方法也与C/C++的printf()函数类似,这使得将已存在的C/C++代码转换成Java代码更加容易。将printf()方法添加到Java API中,简化了向控制台输出格式化数据。
   printf()方法具有以下一般形式:

PrintStream printf(String fmtString,Object... args)
PrintStream printf(Locale loc,String fmtString,Object ... args)

   第1个版本使用fmtString指定的格式将args写入标准输出,使用默认地区;第2个版本允许指定地区。这两个版本都返回调用PrintStream对象。
   一般而言,printf()方法的工作方式与Formatter指定的format()方法类似。fmtString包含两种类型的条目。第一种类型是由直接复制到输出缓冲区的字符构成。第二种类型包含格式说明符,格式说明符定义了由args指定的后续参数显示方式。
   因为System.out是PrintStream类型,所以可以在System.out上调用printf()方法,因此当向控制台写入内容时,只要需要格式化输出,就可以使用printf()替换println()。例如,下面的程序使用printf()以各种格式输出数值。

//Demonstrate printf().
class PrintfDemo {
 public static void main(String[] args) {
      System.out.println("Here are some numeric values "+
              "in different formats.\n");
      System.out.printf("Various integer formats: ");
      System.out.printf("%d %(d %+d %05d\n",3,-3,3,3);
      System.out.println();
      System.out.printf("Default floating-point format: %f\n",
              1234567.123);
      System.out.printf("Floating-point with commas: %,f\n",
              1234567.123);
      System.out.printf("Negative floating-point default: %,f\n",
              -1234567.123);
      System.out.printf("Negative floating-point option: %,(f\n",
              -1234567.123);
      System.out.println();
      System.out.printf("Line up positive and negative values:\n");
      System.out.printf("% ,.2f\n% ,.2f\n",1234567.123,-1234567.123);
      /**
       * 输出:
       * D:\worksoftware\jdk1.8\bin\java.exe "-javaagent:D:\worksoftware\idea\IntelliJ IDEA 2019.2.1\lib\idea_rt.jar=59859:D:\worksoftware\idea\IntelliJ IDEA 2019.2.1\bin" -Dfile.encoding=UTF-8 -classpath D:\worksoftware\jdk1.8\jre\lib\charsets.jar;D:\worksoftware\jdk1.8\jre\lib\deploy.jar;D:\worksoftware\jdk1.8\jre\lib\ext\access-bridge-64.jar;D:\worksoftware\jdk1.8\jre\lib\ext\cldrdata.jar;D:\worksoftware\jdk1.8\jre\lib\ext\dnsns.jar;D:\worksoftware\jdk1.8\jre\lib\ext\jaccess.jar;D:\worksoftware\jdk1.8\jre\lib\ext\jfxrt.jar;D:\worksoftware\jdk1.8\jre\lib\ext\localedata.jar;D:\worksoftware\jdk1.8\jre\lib\ext\nashorn.jar;D:\worksoftware\jdk1.8\jre\lib\ext\sunec.jar;D:\worksoftware\jdk1.8\jre\lib\ext\sunjce_provider.jar;D:\worksoftware\jdk1.8\jre\lib\ext\sunmscapi.jar;D:\worksoftware\jdk1.8\jre\lib\ext\sunpkcs11.jar;D:\worksoftware\jdk1.8\jre\lib\ext\zipfs.jar;D:\worksoftware\jdk1.8\jre\lib\javaws.jar;D:\worksoftware\jdk1.8\jre\lib\jce.jar;D:\worksoftware\jdk1.8\jre\lib\jfr.jar;D:\worksoftware\jdk1.8\jre\lib\jfxswt.jar;D:\worksoftware\jdk1.8\jre\lib\jsse.jar;D:\worksoftware\jdk1.8\jre\lib\management-agent.jar;D:\worksoftware\jdk1.8\jre\lib\plugin.jar;D:\worksoftware\jdk1.8\jre\lib\resources.jar;D:\worksoftware\jdk1.8\jre\lib\rt.jar;D:\inmoodsideaworkspace\Java8\out\production\Java8 PrintfDemo
       * Here are some numeric values in different formats.
       *
       * Various integer formats: 3 (3) +3 00003
       *
       * Default floating-point format: 1234567.123000
       * Floating-point with commas: 1,234,567.123000
       * Negative floating-point default: -1,234,567.123000
       * Negative floating-point option: (1,234,567.123000)
       *
       * Line up positive and negative values:
       *  1,234,567.12
       * -1,234,567.12
       *
       * Process finished with exit code 0
       */
  }
}

   PrintStream类还定义了format()方法,该方法具有以下一般形式:

PrintStream format(String fmtStrimg,Object ... args)
PrintStream format(Locale loc,String fmtString,Object ... args)

   它们的工作方式与printf()方法完全类似。

7.11 DataOutputStream 和 DataInputStream 类

   通过DataOutputStream和DataInputStream类,可以向流中写入基本类型数据或
从流中读取基本类型数据。它们分别实现了DataOutput和DataInput接口,这些接口定义了将基本类型值转换成字节序列或将字节序列转换成基本类型值的方法。这些流简化了在文件中存储二进制数据(例如整数或浮点数)的操作。下面将分别分析每个类。
   DataOutputStream扩展了FilterOutputStream,而FilterOutputStream扩展了OutputStream,除了实现DataOutput接口外,DataOutputStream还实现了AutoCloseable、Closeable以及Flushable接口。DataOutputStream定义了以下构造函数:

DataOutputStream(OutputStream outputStream)

   其中,outputStream指定了写入数据的输出流。当关闭DataOutputStream对象时(通过调用close()方法),outputStream指定的底层流也将被自动关闭。
   DataOutputStream支持其超类定义的所有方法。然而,是那些由DataOutput接口定义的方法使DataOutputStream变得有趣,DataOutputStream实现了这些方法。DataOutput定义了将基本类型值转换成字节序列以及将字节序列写入底层流中的方法。下面是这些方法的一些示例:

final void writeDouble(double value) throws IOException
final void writeBoolean(boolean value) throws IOException
final void writeInt(int value) throws IOException

   其中,value是将被写入流中的值。
   DataInputStream是DataOutputStream的互补。DataInputStream扩展了FilterInputStream,而FilterInputStream又扩展了InputStream.除了实现DataInput接口外,DataInputStream还实现了AutoColseable和Closeable接口。下面是DataInputStream类的唯一构造函数:

DataInputStream(InputStream inputStream)

   其中,inputStream指定了将从中读取数据的输入流。当关闭DataInputStream对象时(通过调用close()方法),也会自动关闭由inputStream指定的底层流。
   与DataOutputStream类似,DataInputStream类也支持其超类的所有方法。然而,是那些由DataInput接口定义的方法才使DataInputStream类变得独特。这些方法读取字节序列并将它们转换成基本类型值。下面是这些方法的一些示例:

final double readDouble() throws IOException
final double readBoolean() throws IOException
final double readInt() throws IOException

   下面的程序演示了DataOutputStream和DataInputStream类的使用:

//Demonstrate DataInputStream and DataOutputStream
//This program uses try-with-resources. It requires JDK 7 or later
import java.io.*;
class DataIODemo {
    public static void main(String[] args) throws IOException {
        //First,write the data.
        try (DataOutputStream dout = new DataOutputStream(new FileOutputStream("Test.dat"))) {
            dout.writeDouble(98.6);
            dout.writeInt(1000);
            dout.writeBoolean(true);
        } catch (FileNotFoundException e) {
            System.out.println("Cannot Open Output File");
            return;
        } catch (IOException e) {
            System.out.println("I/O Error: " + e);
        }
        //Now,read the data back;
        try (DataInputStream din = new DataInputStream(new FileInputStream("Test.dat"))) {
            double d = din.readDouble();
            int i = din.readInt();
            boolean b = din.readBoolean();
            System.out.println("Here ara the values: " +
                    d + " " + i + " " + b);
        } catch (FileNotFoundException e) {
            System.out.println("Cannot Open Input File");
            return;
        } catch (IOException e) {
            System.out.println("I/O Error: " + e);
        }
        /**
         * 输出:
         * Here ara the values: 98.6 1000 true
         */
    }
}
7.12 RandomAccessFile 类

   RandomAccessFile类封装了随机访问文件的功能,该类并非派生子InputStream或OutputStream,而是实现了DataInput和DataOutput接口,这两个接口定义了基本的I/O方法。此外,RandomAccessFile还实现了AutoCloseable和Closeable接口。RandomAccessFile很特殊,因为支持定为需求,即可以定义文件中的文件指针。RandomAccessFile具有以下两个构造函数:

RandomAccessFile(File fileObj,String access) throws FileNotFoundException
RandomAccessFile(String filename,String access) throws FileNotFoundException

   在第1种形式中,fileObj指定了作为File对象打开的文件。在第2种形式中,文件名称是通过fileName传递的。对于这两种形式,access决定了与允许的文件访问类型。如果access为"r",就只能读文件,不能写文件;如果为"rw",就说明文件是以读-写模式打开的;如果为"rws",就说明文件是针对读-写操作打开的,并且每次对文件中的数据或元数据的修改都会被立即写入物理设备;如果为"rwd",就说明文件是针对读-写操作打开的,并且对文件中的数据的修改都会立即写入物理设备。
   seek()方法用于设置文件指针在文件中的当前位置,如下所示:

void seek(long newPos) throws IOException

   其中,newPos指定了新位置,以字节为单位指定文件距离文件开头的位置,在调用seek()方法之后,下一次读或卸操作将在新的文件位置发生。
   RandomAccessFile类实现了标准的输入和输出方法,可以使用这些方法读取或写入随机访问文件。该类还提供了一些附加方法,其中一个方法是setLength(),该方法的签名如下所示:

void setLength(long len) throw IOException

   这个方法将调用文件的长度设置为由len指定的长度。这个方法可用于加长或缩短文件,如果文件被加长,那么添加的部分时未定义的。

8 字符流

   虽然字节流为处理各种类型的I/O操作提供了充足的功能,但是它们不能直接操作Unicode字符。因为Java的一个主要目的就是实现代码的"一次编写,到处运行",所以需要为字符提供直接的I/O支持。Reader和Writer抽象类位于字符流层次的顶部。

8.1 Reader 类

   Reader是抽象类,定义了Java的流字符输入模型。该类还实现了AutoCloseable、Closeable以及Readable接口。当发生错误时,该类中的所有方法(markSupported()方法除外)都会抛出IOException异常。表4简要描述了Reader类中的方法。

表4 Reader 类定义的方法
方 法 描 述
abstract void close() 关闭输入源。如果试图继续读取,将产生IOException异常
void mark(int numChars) 在输入流的当前放置标记,该标记在读入numChars个字符之前都一直有效
boolean markSupported() 如果这个流支持mark()或reset()方法,就返回true
int read() 返回一个表示调用输入流中下一个可用字符的整数,如果到到达文件末尾,就返回-1
int read(char buffer[]) 尝试读取buffer.length个字符到buffer中,并且返回成功读取的实际字符数。如果到达文件末尾,则返回-1
int read(CharBuffer buffer) 尝试读取字符到buffer中,并且返回成功读取的实际字符数,如果到达文件末尾,就返回-1
abstract int read(char buffer[],int offset,int numChars) 尝试读取numChars个字符到buffer中,从buffer[offset] 开始保存读取的字符,返回成功读取的字符数。如果到达文件末尾,就返回-1
boolean ready() 如果下一个输入请求不等的,就返回true;否则返回false
void reset() 将输入指针重新设置为前面设置的标记位置
long skip(long numChars) 略过numChars个输入字符,返回实际略过的字符数
8.2 Writer 类

   Writer是定义流字符输出模型的抽象类,实现了AutoCloseable、Closeable、Flushable以及Appendable接口。如果发生错误,Writer类中的所有方法都会抛出IOException异常,表5简要描述了Writer中的方法。

表5 Writer类定义的方法
方 法 描 述
Writer append(char ch) 将ch追加到调用输出流的末尾,返回对调用流的引用
Writer append(CharSequence chars) 将chars准假到调用输出流的末尾,返回对调用流的引用
Writer append(CharSequence chars,int begin,int end) 将chars中从begin到end-1之间的字符追加到调用输出流的末尾,返回对调用流的引用
abstract void close() 关闭输出流,如果试图继续向其中写入内容,将产生IOException异常
abstract void flush() 完成输出状态,从而清空所有缓冲区,即刷出输出缓冲区
void write(int ch) 向调用输出流写入单个字符。注意参数是int类型,从而可以直接使用表达式调用write()方法,而不必将之强制转换会char类型,但是只会写入低阶的16位
void write(char buffer[]) 将整个字符数组写入调用输出流中
abstract void write(char buffer[],int offset,int numChars) 将buffer数组中从buffer[offerset]开始的numChars个字符写入调用输出流中
void write(String str) 将str写入调用输出流中
void write(String str,int offset,int numChars) 将字符串str中从offset开始的numChars个字符写入调用输出流中
8.3 FileReader 类

   FileReader类可以创建用于读取文件内容的Rader对象,该类最常用的两个构造函数如下所示:

FileReader(String filePath)
FileReader(File fileObj)

   每个构造函数都会抛出FileNotFoundException异常。其中filePath是文件的完整路径名,fileObj是描述文件的File对象。
   下面的例子显示了如何从文件中读取文本行,以及如何在标准输出设备上进行显示。该例读取自己的源文件,源文件必须位于当前目录下。

//Demonstrate FileReader.
//This program uses try-with-resources. It requires JDK 7 or later.
import java.io.*;
class FileReaderDemo {
    public static void main(String[] args) {
        try(FileReader fr = new FileReader("FileReaderDemo.java")){
            int c;
            //Read and display the file
            while ((c=fr.read())!=-1){
                System.out.print((char)c);
            }
        }catch (IOException e){
            System.out.println("I/O Error: "+e);
        }
    }
}
8.4 FileWriter 类

   FileWriter类可以创建能够用于写入文件的Writer对象,该类最常用的4个构造函数如下所示:

FileWriter(String filePath)
FileWriter(String filePath,boolean append)
FileWriter(File fileObj)
FileWriter(File fileObj,boolean append)

   它们都会抛出IOException异常,其中,filePath是文件的完整路径名,fileObj是描述文件的File对象。如果append为true,输出将被追加到文件的末尾。
   FileWriter对象的创建不依赖于已经存在的文件。当创建对象时,FileWriter会打开文件之前为输出创建文件。对于这种情况,如果试图打开只读的文件,就会抛出IOException异常。
   下面的例子是前面讨论FileOutputStream时显示的例子的字符流版本。这个版本首先创建一个String对象,然后调用getChars()方法提取与这个String对象等价的字符数组,从而创建一个样本字符缓冲区。然后该例创建3个文件。第一个文件是file1.txt,将包含样本缓冲区中每隔一个字符的字符。第2个文件是file2.txt,将包含全部字符。最后一个,即第3个文件是file3.txt,将只包含最后四份之一的字符。

//Demonstrate FileWriter.
//This program uses try-with-resources. It requires JDK 7 or later.
import java.io.*;
class FileWriterDemo {
    public static void main(String[] args) {
        String source = "Now is the time for all good men\n"
                + " to come to the aid of their country\n"
                + " and pay their due taxes.";
        char buffer[] = new char[source.length()];
        source.getChars(0,source.length(),buffer,0);
        try(FileWriter f0=new FileWriter("file1.txt");
        FileWriter f1 = new FileWriter("file2.txt");
        FileWriter f2= new FileWriter("file3.txt")){
            //write to first file
            for (int i=0;i<buffer.length;i+=2){
                f0.write(buffer[i]);
            }
            //write to second file
            f1.write(buffer);
            //write to third file
            f2.write(buffer,buffer.length-buffer.length/4,buffer.length/4);
        }catch (IOException e){
            System.out.println("An I/O Error Occurred");
        }
    }
}
8.5 CharArrayReader 类

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

CharArrayReader(char array[])
CharArrayReader(char array[],int start,int numChars)

   其中,array是输入源。第2个构造函数根据字符数组的子集创建Reader对象,该子集从start指定的索引位置的字符开始,共numChars个字符。
   CharArrayReader实现的close()方法不会抛出任何异常,因为这不可能失败。
下面的例子使用一对CharArrayReader对象:

//Demonstrate CharArrayReader.
//This program uses try-with-resources. It requires JDK 7 or later
import java.io.CharArrayReader;
import java.io.IOException;
class CharArrayReaderDemo {
    public static void main(String[] args) {
        String tmp="abcdefghijklmnopqrstuvwxyz";
        int length = tmp.length();
        char c[] =new char[length];
        tmp.getChars(0,length,c,0);
        int i;
        try(CharArrayReader input1=new CharArrayReader(c)){
            System.out.println("input1 is:");
            while ((i=input1.read())!=-1){
                System.out.print((char)i);
            }
            System.out.println();
        }catch (IOException e){
            System.out.println("I/O Error: "+e);
        }
        try(CharArrayReader input2 = new CharArrayReader(c,0,5)){
            System.out.println("inputs is: ");
            while ((i=input2.read())!=-1){
                System.out.print((char)i);
            }
            System.out.println();
        }catch (IOException e){
            System.out.println("I/O Error: "+e);
        }
    }
    /**
     * 输出:
     * input1 is:
     * abcdefghijklmnopqrstuvwxyz
     * inputs is:
     * abcde
     */
}
8.6 CharArrayWriter 类

   CharArrayWriter类是使用数组作为目标的一个输出流实现。CharArrayWriter类具有两个构造函数,如下所示:

CharArrayWriter()
CharArrayWriter(int numChars)

   在第1种形式中,创建使用默认大小的缓冲区。在第2种形式中,创建由numChars指定大小的缓冲区。缓冲区保存在CharArrayWriter类的域变量中。如果需要,缓冲区的大小可以自动增加。缓冲区能够容纳的字符数量保存在CharArrayWriter类的count域变量中。buf和count都是受保护域变量。
   close()方法对CharArrayWriter没有影响。
   下面的例子通过重写前面演示ByteArrayOutputStream的例子,演示了CharArrayWriter类的使用。该例生成的输出与前面版本的相同。

//Demonstrate CharArrayWriter.
//This program uses try-with-resources. It requires JDK 7 or later.
import java.io.CharArrayWriter;
import java.io.FileWriter;
import java.io.IOException;
class CharArrayWriterDemo {
    public static void main(String[] args) {
        CharArrayWriter f = new CharArrayWriter();
        String s="This should end up in the array";
        char buf[] = new char[s.length()];
        s.getChars(0,s.length(),buf,0);
        try{
            f.write(buf);
        }catch (IOException e){
            System.out.println("Error Writing to Buffer");
            return;
        }
        System.out.println("Buffer as a string");
        System.out.println(f.toString());
        System.out.println("Into array");
        char c[] = f.toCharArray();
        for(int i=0;i<c.length;i++){
            System.out.print(c[i]);
        }
        System.out.println("\nTo a FileWriter()");
        //Use try-with_resources to manage the file stream.
        try(FileWriter f2=new FileWriter("test.txt")){
            f.writeTo(f2);
        }catch (IOException e){
            System.out.println("I/O Error: "+e);
        }
        System.out.println("Doing a reset");
        f.reset();
        for (int i=0;i<3;i++){
            f.write('X');
        }
        System.out.println(f.toString());
    }
    /**
     * 输出:
     * Buffer as a string
     * This should end up in the array
     * Into array
     * This should end up in the array
     * To a FileWriter()
     * Doing a reset
     * XXX
     */
}
8.7 BufferedReader 类

   BufferedReader类通过缓冲输入提高性能,该类具有两个构造函数:

BufferedReader(Reader inputStream)
BufferedReader(Reader inputStream,int bufSize)

   第1中方式使用默认缓冲区大小创建缓冲的字符流。在第2中形式中,缓冲区大小是由bufSize传递的。
   关闭BufferedReader对象也会导致inputStream指定的底层流被关闭。
   与面向字节的流一样,缓冲的输入字符流也提供了可在缓冲区中向后移动所需要的的基础。为了支持这一点,BufferedReader实现了mark()和reset()方法,并且BufferedReader.markSupported()会返回true。JDK8为BufferedReader添加了名为lines()的新方法。该方法返回对读取器读取的行序列的Stream引用。
   下面的例子重写了前面显示的BufferedInputStream示例,从而使用BufferedReader字符流而不是字节流。该版本与前面的版本一样,为了获取版权符号,使用mark()和reset()方法解析HTML实体引用流。这种引用以"与"符号(&)开头并以分号(;)结束,中间没有任何空白字符。样本输入使用两个&符号来显示在何处发生reset()方法调用,以及在何处不发生这种调用。该版本的输出与前面版本的相同。

//Use buffered input.
//This program uses try-with-resources. It requires JDK 7 or later.
import java.io.BufferedReader;
import java.io.CharArrayReader;
import java.io.IOException;
class BufferedReaderDemo {
    public static void main(String[] args) {
        String s = "This is a &copy; copyright symbol " +
                "but this is a &copy not.\n";
        char buf[] = new char[s.length()];
        s.getChars(0, s.length(), buf, 0);
        CharArrayReader in = new CharArrayReader(buf);
        int c;
        boolean marked = false;
        try (BufferedReader f = new BufferedReader(in)) {
            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;
                }
            }
        } catch (IOException e) {
            System.out.println("I/O Error: " + e);
        }
        /**
         * 输出:
         * This is a (c) copyright symbol but this is a &copy not.
         */
    }
}
8.8 BufferedWriter 类

   BufferWriter是缓冲输出的Writer。使用BufferedWriter可以通过减少实际想输出设备物理的写入数据的次数来提高性能。
   BufferedWriter具有以下两个构造函数:

BufferedWriter(Writer outputStream)
BufferedWriter(Writer outputStream,int bufSise)

   第1种形式创建的缓冲流使用具有默认大小的缓冲区。在第2中形式中,缓冲区的大小是由bufSize传递的。

8.9 PushbackReader 类

   PushbackReader类允许将一个或对个字符返回到输入流,从而可以向前查看输入流。下面是该类的两个构造函数:

PushbackReader(Reader inputStream)
PushbackReader(Reader inputStream,int bufSize)

   第1种形式创建的缓冲流允许回推一个字符。在第2种形式中,回推缓冲区的大小由bufSize传递。
   关闭PushbackReader也会关闭inputStream指定的底层流。
   PushbackReader类提供了unreader()方法,该方法调用输入对象返回一个或多个字符。unreader()方法有3种形式,如下所示:

void unreader(int ch) throws IOException
void unreader(char buffer[]) throws IOException
void unreader(char buffer[],int offset,int numChars)throws IOException

   第1种形式回推ch传递的字符,这是后续read()调用将返回的下一个字符。第2种形式返回buffer中的字符。第3种形式回推buffer中从offset位置开始的numChars歌字符。当回推缓冲区已满时,如果试图返回字符,就会抛出IOException异常。
   下面的程序通过使用PushbackReader替换PushbackInputStream,对前面的PushbackInpuStream示例进行了改写。与前面的版本相同,该版本显示了编程语言解析器可以使用回推流来处理比较运算符"==“与赋值运算符”="之间的区别。

//Demonstrate unread().
//This program uses try-with-resources. It requires JDK 7 or later.
import java.io.CharArrayReader;
import java.io.IOException;
import java.io.PushbackReader;
class PushbackReaderDemo {
    public static void main(String[] args) {
        String s="if (a == 4) a = 0;\n";
        char[] buf = new char[s.length()];
        s.getChars(0,s.length(),buf,0);
        CharArrayReader in = new CharArrayReader(buf);
        int c;
        try(PushbackReader f= new PushbackReader(in)){
            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;
                }
            }
        }catch (IOException e){
            System.out.println("I/O Error: "+e);
        }
        /**
         * 输出:
         * if (a .eq. 4) a <- 0;
         */
    }
}
8.10 PrintWriter 类

   PrintWriter本质上是PrintStream的面向字符版本,实现了Appendable、AutoCloseable、Closeable以及Flushable接口。PrintWriter类具有几个构造函数。下面这些构造函数是PrintWriter从一开始就支持的:

PrintWriter(OutputStream outputStream)
PrintWriter(OutputStream,boolean autoFlushingOn)
PrintWriter(Writer outputStream)
PrintWriter(Writer outputStream,boolean autoFlushingOn)

   其中,outputStream指定了将接受输出的打开的OutputStream对象。autoFlushingOn参数控制每次调用println()、printf()或format()方法时,是否刷新输出缓冲区,如果autoFlushingOn为true,就自动刷新:如果为false,就不自动刷新。没有指定autoFlushingOn参数的构造函数不自动刷新。
   下面这几个构造函数为构造向文件中写入输出的PrintWriter对象提供了简便方法:

PrintWriter(File outputFile) throws FileNotFoundException
PrintWriter(File outputFile,
String charset) throws FileNotFoundException,UnsupportedEncodingException
PrintWriter(String outputFileName) throws FileNotFoundException
PrintWriter(String outputFileName,String charSet) throws FileNotFoundException,UnsupportedEncodingException

   这些构造函数允许从File对象或根据指定的文件创建PrintWriter对象。对于每种形式都会自动创建文件。所有值钱存在的同名文件都会被销毁。一旦创建PrintWriter对象,就将所有输出定向到指定的文件,可以通过charSet传递的名称来指定字符编码。
   PrintWriter为所有类型(包括Object)都支持print()和println()方法。如果参数不是基本类型,那么PrintWriter方法会调用对象的toString()方法,然后输出结果。
   PrintWriter还支持printf()方法,它的工作方式与前面介绍的PrintStream类的printf()方法相同。该方法允许指定数据的精确格式。下面是在PrintWriter类中声明的printf()方法:

PrintWriter printf(String fmtString,Object ...args)
PrintWriter printf(Locale loc,String fmtString,Object ... args)

   第1个版本使用fmtString指定的格式将args写入标准输出,使用默认地区。第2个版本允许指定地区。这两个版本都返回调用PrintWriter对象。
   PrintWriter类也支持format()方法,该方法具有以下一般形式:

PrintWriter format(String fmtString,Object ... args)
PrintWriter format(Locale loc,String fmtString,Obkect ... args)

   format()方法的工作方式与printf()方法完全类似。

9 Console 类

   JDK 6将Console类添加到java.io包中,该类用于从控制台读取内容以及向控制台写入内容,并且实现了Flushable接口。Console类的主要目的是提供方便,因为该类的大部分功能可以通过System,in和System.out得到。然而,该类的使用可以简化某些类型的控制台交互,特别是当从控制台读取字符串时。
   Console类没有提供构造函数。相反,该类通过调用System.console()方法获取Console对象,该方法如下所示:

static Console console()

   如果控制台可用,就返回对控制台的引用;否则返回null。并不是所有情况下控制台都是可用的。因此,如果返回null,就不能进行控制台I/O。
   表6显示了Console类定义的方法。注意输入方法,比如readLine(),在发生输入错误时抛出IOError错误。IOError是Error的子类,用来指示某个I/O失败超出了程序的控制。因此,在正常情况下不会捕获IOError。如果访问控制台时抛出IOError,那么通常以为着灾难性的系统失败。

表6 Console类定义的方法
方 法 描 述
void flush() 将缓冲的输出物理地写到控制台
Console format(String fmtString,Object … args) 使用fmtString指定的格式将args写到控制台
Console printf(String fmtString,Object…args) 使用fmtString指定的格式将args写到控制台
Reader reader() 返回对连接到控制台的Reader对象的引用
String readLine() 读取并返回从键盘输入的字符串。当用户按下回车键时,输入结束、如果已经到达控制台输入流的末尾,就返回null;如果失败,就抛出IOError异常
String readLine(String fmtString,Object …args) 按照fmtString和args指定的格式显示提示性字符串,然后读取并返回从键盘输入的字符串。当用户按下回车键时,输入结束,如果已经达到控制台输入流的末尾,就返回null;如果失败,就抛出IOError异常
char readPassword() 读取从键盘输入的字符串。当用户按下回车键时,输入结束。输入的字符串并不显示。如果已经到达控制台输入流的末尾,就返回null;如果失败,就抛出IOError异常
char[] readPassword(String fmtString,Object…args) 按照fmtString和args指定的格式显示提示性媳妇穿,然后读取从键盘输入的字符串。当用户按下回车键时,输入结束。输入的字符串并不显示。如果已经到达控制台输入流的末尾,就返回null;如果失败,就抛出IOError异常
PrintWriter wirter() 返回对连接到控制台的Writer对象的引用

   还应当注意readPassword()防范,这些方法允许读取密码而不是键入的内容。当读取密码时,应当对两类数组进行"零输出",其中一类数组用于保存用户输入的字符串,另一类数组用于保存用来测试密码的字符串。这样可以减少恶意程序通过扫描内存来获取密码的机会。
   下面是一个演示Console类的例子:

//Demonstrate Console.
import java.io.Console;
class ConsoleDemo {
    public static void main(String[] args) {
        String str;
        Console con;
        //Obtain a reference to the console.
        con=System.console();
        //If no colsole available,exit.
        if(con==null) {
            return;
        }
        //Read a string and then display it.
        str = con.readLine("Enter a String: ");
        con.printf("Here is your string: %s\n",str);
    }
}

10 串行化

   串行化是将对象的状态写入字节流的过程。如果希望将程序的状态保存到永久性存储区域(例如文件)中,这是很有用的。以后,可以通过反串行化过程来恢复这些对象。
   实现远程方法调用(Remote Method Invocation,RMI)也需要串行化。远程方法调用允许在一台机器上的Java对象调用在另一台机器上的Java对象的方法。可以将对象作为参数提供给远程方法,发送机器串行化对象并进行传递,接受机器反串行化对象。
   假定将要串行化的对象具有指向其他对象的引用,而这些对象又具有更多对象的引用。这些对象以及它们之间的关系形成了有向图。在这个对象图形中,还可能存在环形引用。也就是说对象X可能包含指向对象Y的引用,二对象Y可能又包含对象X的引用。对象也可能包含指向它们自己的引用。在这些情形中,对象的串行化和饭串行化都能够正确地工作。如果试图串行化位于对象图顶部的对象,那么所有其他引用的对象都会被递归地定为和串行化。类似地,在反串行化过程中,能够争取地恢复所有这些对象以及对它们的引用。

10.1 Serializable 类

   只有实现了Serializable接口的类才能够通过串行化功能进行保存和恢复。Serializable接口没有定义成员,只是简单用于只是类可以被串行化。如果一个类是可串行化的,那么这个类的所有子类也都是可串行化的。
   声明为transient的变量不能通过串行化功能进行保存。此外,也不能保存static变量。

10.2 Externalizable 类

   Java对串行化和反串行化功能进行了精心设计,从而使得许多保存和恢复对象状态的工作都可以自动进行。然而在游行情况下,我们可能需要控制这些过程。例如,可能希望使用压缩和加密技术。Externalizable接口就是针对这些情况而设计的。
   Externalizable接口定义了下面两个方法:

void readExternal(ObjectInput inStream) throws IOException,ClassNotFoundException
void readExternal(ObjectOutput outStream) throws IOException

   在这两个方式中,inStream是从中读取对象的字节流,outStream是将对象写入其中的字节流。

10.3 ObjectOutput 接口

   ObjectOutput接口扩展了DataOutput和AutoCloseable接口,并且支持对象串行化。表7显示了ObjectOutput接口定义的方法,请特别注意writeObject()方法,该方法用于串行化对象。如果遇到错误,这些方法会抛出IOException异常。

表7 ObjectOutput接口定义的方法
方 法 描 述
void close() 关闭调用了,如果试图继续向流中写入内容,就产生IOException异常
void flush() 完成输出状态,以便情况缓冲区,即刷新输出缓冲区
void write(byte buffer[]) 将字节数组写入到输出流中
void write(byte buffer[],int offset,int numBytes) 将buffer数组中从buffer[offset] 位置开始的numBytes个字节写入调用流中
void write(int b) 将单个字节写入到流中,写入的字节是b的低阶字节
void writeObject(Object obj) 将obj对象写入调用流中
10.4 ObjectOutputStream 类

   ObjectOutputStream类扩展了OutputStream类并实现了ObjectOutput接口,负责将对象写入六中。该类的一个构造函数如下所示:

ObjectOutputStream(OutputStream outStream) throws IOException

   参数outStream是将其中写入串行化对象的输出流。关闭ObjectOutputStream对象会自动关闭outStream指定的底层流。
   表8列出了该类的一些常用方法。当遇到错误时,这些方法会抛出IOException异常,ObjectOutputStream还有一个内部类,名为PutField,可以帮助写入永久域变量。

表8 ObjectOutputStream 类定义的常用方法
方 法 描 述
void close() 关闭调用流,如果试图继续向流中写入内容,将产生IOException异常
void flush() 完成输出状态,以便清空所有缓冲区,即刷新输出缓冲区
void write(byte buffer[]) 将字节数组写入调用流中
void write(byte buffer[],int offset,int numBytes) 将buffer数组中从buffer[offset] 位置开始的numBytes个字节写入调入流中
void write(int b) 将单个字节写入调用流中,写入的字节是b的低阶字节
void writeBoolean(boolean b) 将一个布尔值写入调用流中
void writeByte(int b) 将一个byte值写入到流中,写入的字节是b的低阶字节
void writeBytes(String str) 将表示str的字节写入调用流中
void writeChar(int c) 将一个char值写入调用流中
void writeChars(String str) 将str中的字符写入调用流中
void writeDouble(double d) 将一个double值写入调用流中
void writeFloat(float f) 将一个float值写入调用流中
void writeInt(int i) 将一个int值写入调用流中
void writeLong(long l) 将一个long值写入调用流中
final void writeObject(Object obj) 将obj对象写入调用流中
void writeShort(int i) 将一个short值写入调用流中
10.5 ObjectInput 接口

   ObjectInput接口扩展了DataInput和AutoCloseable接口,并定义了在表9中显示的方法。ObjectInput接口支持对象串行化。请特别注意readObject()方法,该方法用于饭串行化对象。如果遇到错误,所有这些方法都会抛出IOException异常。readObject()方法还可能抛出ClassNotFoundException异常。

表9 ObjectInput接口定义的方法
方 法 描 述
int available() 返回输入缓冲区现在可用的字节数
void close() 关闭调用流,如果试图继续从中读取内容,将产生IOException异常
int read() 返回一个表述输入流下一个可用字节的整数。如果到达末尾,就返回-1
int read(byte buffer[]) 尝试读取buffer.length个字节到buffer中,返回成功读取的实际字节数,如果到达文件末尾,就返回-1
int read(byte buffer[],int offset,int numBytes) 尝试读取numBytes个字节到buffer中,从buffer[offset]位置开始存储,返回成功读取的字节数,如果到达文件末尾,就返回-1
Object readObject() 从调用流中读取对象
long skip(long numBytes) 忽略(即跳过) 调用字节流中numBytes个字节,返回实际忽略的字节数
10.6 ObjectInputStream 类

   ObjectInputStream类扩展了InputStream类并实现了ObjectInput接口。ObjectInputStream负责从流中读取对象,该类的一个构造函数如下所示:

ObjectInputStream(InputStream inStream) throws IOException

   参数inStream是从流中读取串行化对象的输入流。关闭ObjectInputStream对象会自动关闭inStream指定的底层流。
   表20显示了该类的一些常用方法。如果遇到错误,这些方法会抛出IOException异常。readObject()方法还能抛出ClassNotFoundException异常。ObjectInputStream还有一个内部类名为GetField,这个内部类可帮助读取永久域变量。

表10 ObjectInputStream 类定义的常用方法
方 法 描 述
int available() 返回输入缓冲区中现在可用的字节数
void close() 关闭调用流,如果试图继续从中读取内容,将产生IOException异常。底层流也会被关闭
int read() 返回一个表示输入流中下一个可用字节的整数,如果到达文件末尾,就返回-1
int read(byte buffer[],int offset,int numBytes) 尝试读取numBytes个字节到buffer中,从buffer[offset] 位置开始存储,返回成功读取的字节数。如果到达文件末尾,就返回-1
Boolean readBoolean() 从调用流中读取并返回一个布尔值
byte readBytes() 从调用流中读取并返回一个byte值
char readChar() 从调用流中读取并返回一个char值
double readDouble() 从调用流中读取并返回一个boolean值
float readFloat() 从调用流中读取并返回一个float值
void readFully(byte buffer[]) 读取buffer.length个字节到buffer中。只有当所有字节都读取完之后才返回
int readIn() 从调用流中读取并返回一个int值
long readLong() 从调用流中读取并返回一个long值
final Object readObject() 从调用流中读取并返回一个对象
short readShort() 从调用流中读取并返回一个short值
int readUnsignedByte() 从调用流中读取并返回一个无符号byte值
int readUnsignedShort() 从调用流中读取并返回一个无符号short值
10.7 串行化示例

   下面的程序演示了如何使用对象串行化和反串行化。该程序首先实例化MyClass类的一个对象。这个对象具有3个实例变量,分别是String、int和double类型。这3个变量中是希望保存和恢复的信息。
   创建一个指向"serial"文件的FileOutputStream对象,并为该文件创建一个ObjectOutputStream对象。然后使用ObjectOutputStream的writeObject()方法串行化对象。接下来刷新并关闭对象输出流。
   然后创建一个指向"serial"文件的FileInputStream对象,并为该文件流创建一个ObjectInputStream对象。然后使用ObjectInputStream的readObject()方法反串行化对象。接下来关闭对象输入流。
   注意MyClass类实现了Serializable接口。如果没这么做,就会抛出NoSerializableException异常。如果尝试将一些MyClass示例变量声明为transient以试验该程序,那么在串行化期间不会保存这些数据。

//A serialization demo.
//This program uses try-with-resources. It requires JDK 7 or later.
import java.io.*;
class SerializationDemo {
  public static void main(String[] args) {
       //Object serialization
       try(ObjectOutputStream objOStrm=new ObjectOutputStream(new FileOutputStream("serial"))){
           MyClass object1 = new MyClass("Hello",-7,2.7e10);
           System.out.println("object1: "+object1);
           objOStrm.writeObject(object1);
       }catch (IOException e){
           System.out.println("Exception during serialization: "+e);
       }
//Object deserialization
       try(ObjectInputStream objIStrm=new ObjectInputStream(new FileInputStream("serial"))){
           MyClass objest2 = (MyClass) objIStrm.readObject();
           System.out.println("object2: "+objest2);
       }catch (Exception e){
           System.out.println("Exception during deserialization: "+e);
       }
       /**
        * 输出:
        * object1: s=Hello; i=-7; d=2.7E10
        * object2: s=Hello; i=-7; d=2.7E10
        */
   }
}
发布了59 篇原创文章 · 获赞 20 · 访问量 3631

猜你喜欢

转载自blog.csdn.net/qq_34896730/article/details/104241640