I/O

1.1 I/O的基础知识
1.1.1 流

   Java程序通过流执行I/O。流是一种抽象,要么产生信息,要么使用信息。流通过Java的I/O系统链接到物理设备。所有流的行为方式都是相同的,尽管与它们链接的物理设备是不同的。因此,可以为任意类型的设备应用相同的I/O类和方法。这意味着可以将许多不同类型的输入——磁盘文件、键盘或网络socket,抽象为输入流。与之对应,输出流可以引用控制台、磁盘文件或网络连接。流是处理输入/输出的一种清晰方式,例如,代码中的所有部分都不需要理解键盘和网络之间的区别。流是Java在由java.io包定义的类层次中实现的。
   注意:
   除了在java.io包定义的基于流的I/O外,Java还提供了基于缓冲和基于通道的I/O它们是在java.nio及其子包中定义的。

1.1.2 字节流和字符流

   Java定义了两种类型的流:字节流和字符流。字节流为处理字节的输入和输出提供了方法。例如,当读取和写入二进制数据时,使用的就是字节流。字符流为处理字符的输入和输出提供了方便的方法。它们使用Unicode编码,所以可以被国际化。此外,在某些情况下,字符流比字节流高效。
   最初版本的Java(Java 1.0)没有提供字符流,因此,所有I/O都是面向字节的。字符流是由Java1.1添加的,并且某些面向字节的类和方法不再推荐使用。尽管不使用字符流的旧代码越来越少,但是有时仍然会遇到。作为一般原则,在合适的情况下应当更新旧代码以利用字符流的优点。
   另外一点:在最底层,所有I/O仍然是面向字节的。基于字符的流只是为处理字符提供了一种方便和高效的方法。
   下面分别概述面向字节的流和面向字符的流。

1.1.2.1 字节流类

   字节流是通过两个类层次定义的。在顶级是两个抽象类:InputStream和OutputStream。每个抽象类都有几个处理各种不同设备的具体子类,例如磁盘文件、网络连接甚至内存缓冲区。下表列出了java.io包中的字节流类。为了使用流类,必须导入java.io包。

java.io包中的字节流类
流 类含 义
BufferedInputStream缓冲的输入流
BufferedOutputStream缓冲的输出流
ByteArrayInputStream读取字节数组内容的输入流
ByteArrayOutputStream向字节数组写入内容的输出流
DataInputStream包含读取Java标准数据类型的方法的输入流
DataOutputStream包含写入Java标准数据类型的方法的输出流
FileInputStream读取文件内容的输入流
FileOutputStream向文件中写入内容的输出流
FilterInputStream实现InputStream
FilterOutputStream实现OutputStream
InputStream描述流输入的抽象类
ObjectInputStream用于对象的输入流
ObjectOutputStream用于对象的输出流
OutputStream描述流输出的抽象类
PipedInputStream输入管道
PipedOutputStream输出管道
PrintStream包含print()和println()的输出流
PushbackInputStream支持1字节"取消获取"输入流,这种流向输入流返回1个字节
SequenceInputStream由两个或多个按顺序依次读取的输入流组合而成的输入流

   抽象类InputStream()和OutputStream()定义了其他流类实现的一些关键方法。其中最重要的两个方法是read()和write(),这两个方法分别读取和写入字节数据。每个方法都有抽象形式,派生的流类必须重写这两个方法。

1.1.2.2 字符流类

   字符流类是通过两个类层次定义的。在顶层是两个抽象类:Reader和Writer。这两个抽象类处理Unicode字符流。下表列出了java.io包中的字符流类。

java.io包中的字符流类
流 类含 义
BufferedReader缓冲的输入字符流
BufferedWriter缓冲的输出字符流
CharArrayReader从字符数组读取内容的输入流
CharArrayWriter向字符数组写入内容的输出流
FileReader从文件中读取内容的输入流
FileWriter向文件中写入内容的输出流
FilterReader过滤的读取器
FilterWriter过滤的写入器
InputStreamReader将字节转换为字符的输入流
LineNumberReader计算行数的输入流
OutputStreamWriter将字符转换成字节的输出流
PipedReader输入管道
PipedWriter输出管道
PrintWriter包含print()和println()的输出流
PushbackReader允许字符返回到输入流的输入流
Reader描述字符流输入的抽象类
StringRader从字符串读取内容的输入流
StringWriter向字符串写入内容的输出流
Writer描述字符流输出的抽象类

   抽象类Reader和Writer定义了其他几个流类实现的重要方法。最重要的两个方法是read()和write(),这两个方法分别读取和写入字符数据。每个方法都有抽象形式,派生的流类必须实现这两个方法。

1.1.3 预定义流

   所有Java程序都自动导入java.lang包。这个包定义了System类,该类封装了运行时环境的某些方法。例如,使用该类的一些方法,可以获得当前时间以及与系统相关的各种属性设置。System还包含3个预定义的流变量:in、out以及error。这些变量在System类中被声明为public、static以及final。这意味着程序中的其他任何部分都可以使用它们,而不需要引用特定的System对象。
   System.out引用标准的输出流,默认情况下是控制台。System.in引用标准的输入流,默认情况下是键盘。System.err引用标准的错误流,默认情况下也是控制台。但是,这些流可以被重定向到任何兼容的I/O设备。
   System.in是InputStream类型的对象;System.out和System.err是PrintStream类型的对象。这些都是字节流,尽管它们通常用于从控制台读取字符以及向控制台写入字符。可以看出,如果愿意的话,可以在基于字符的流中封装这些流类。

1.2 读取控制台输入

   在Java1.0中,执行控制台输入的唯一方法是使用字节流。现在仍然可以使用字节流读取控制台输入。但是,对于商业应用程序,读取控制台输入的更好方法是使用面向字符的流。使用面向字符的流可以使程序更容易国际化和维护。
   在Java中,控制台输入是通过System.in读取完成的。为了获得与控制台关联的基于字符的流,可以在BufferedReader对象中封装System.in。BufferedReader支持缓冲的输入流。通常使用的构造函数如下所示:

BufferedReader(Reader inputReader)

   其中,inputReader是与即将创建的BufferedReader实例链接的流。Reader是抽象类,InputStreamReader是它的一个具体子类,该类将字节转化为字符。为了获得与System.in链接的InputStreamReader对象,使用下面的构造函数:

InputStreamReader(InputStream inputStream)

   因为System.in引用InputStream类型的对象,所以可以用作inputStream参数。将这些内容结合起来,下面的代码创建了一个与键盘连接的BufferedReader对象:

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

   执行这条语句之后,br就是通过System.in与控制台链接的基于字符的流。

1.2.1 读取字符

   为了从BufferedReader对象读取字符,需要使用read()方法。在此将使用如下所示的read()版本:

int read() throws IOException

   每次调用read()方法都会从输入流读取一个字符,并将之作为整数值返回。如果到达流的末尾,就返回-1。可以看出,该方法可能抛出IOExcepion异常。
   下面的程序演示了read()方法的使用,该程序从控制台读取输入,直到用户输入字符“q”。注意,可能产生的所有I/O异常都被简单地从main()方法中抛出。在复杂的应用程序中,可以显示地处理异常。

//Use a BufferedReader to read characters from the console
import java.io.*;
public class BRRead {
    public static void main(String[] args) throws IOException {
        char c;
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("Enter characters, 'q' to quit.");
        //read characters
        do {
            c = (char) br.read();
            System.out.println(c);
        } while (c != 'q');
    }
    /**
     * 输出结果
     * Enter characters, 'q' to quit.
     * 123abcq
     * 1
     * 2
     * 3
     * a
     * b
     * c
     * q
     */
}

   默认情况下System.in是按行缓冲的。这意味着在按下Enter键之前,实际上没有输入被传递到程序。这使得read()方法对于交互性的控制台输入不是很有价值。

1.2.2 读取字符串

   为了从键盘读取字符串,可以使用BufferedReader类的readLine()方法,该方法的一般形式如下所示:

String readLine()throws IOException

   可以看出,该方法返回一个String对象。
   下面的程序演示了BufferedReader类和readLine()方法;该程序读取并显示文本行,直到用户输入单词"stop":

//Read a string from console using a BufferedReader.
import java.io.*;
public class BRReadLines {
    public static void main(String[] args) throws IOException {
        //create a BufferedReader using System.in
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String str;
        System.out.println("Enter lines of text .");
        System.out.println("Enter 'stop' to quit.");
        do {
            str = br.readLine();
            System.out.println(str);
        } while (!str.equals("stop"));
    }
    /**
     * 执行结果
     * Enter lines of text .
     * Enter 'stop' to quit.
     * wsws
     * wsws
     * stop
     * stop
     */
}

   下面的例子创建了一个小型的文本编辑器。该程序创建了一个String对象数组,然后读取文本行,在数组中存储每一行。该程序最多读取100行或直到用户输入"stop"。该程序使用BufferedReader从控制台读取输入。

//A tiny editor;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class TinyEdit {
    public static void main(String[] args) throws IOException {
        //create a BufferedReader using System.in
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String str[] = new String[100];
        System.out.println("Enter lines of text.");
        System.out.println("Enter 'stop' to quit.");
        for (int i=0;i<100;i++){
            str[i] = br.readLine();
            if(str[i].equals("stop")) break;
        }
        System.out.println("\nHere is you file:");
        for (int i=0;i<100;i++){
            if(str[i].equals("stop"))break;
            System.out.println(str[i]);
        }
    }
    /**
     * 输出结果
     * Enter lines of text.
     * Enter 'stop' to quit.
     * qwerrrr
     * xsdffff
     * xdfff.
     * stop
     * Here is you file:
     * qwerrrr
     * xsdffff
     * xdfff.
     */
}
1.3 向控制台写输出

   print()和println()是向控制台写输出的最容易方式,这两个方法是由PrintStream类(也就是System.out引用的对象的类型)定义的。System.out尽管是字节流,但它对于简单地程序仍然可以使用。
   因为PrintStream是派生自OutputStream的输出流,所以还实现了低级的write()方法。因此,可以使用wirte()向控制台输出。PrintStream定义的最简单形式的write()方法如下所示:

void write(int byteval)

   该方法输出由byteval指定的字节。尽管byteval被声明为整数,但是只有低8位被输出。下面的简短示例使用write()向屏幕输出字符"A",然后换行:

//Demostrate System.out.write().
public class WriteDemo {
    public static void main(String[] args) {
        int b;
        b = 'A';
        System.out.write(b);
        System.out.write('\n');
    }
}

   通常不会使用write()执行控制台输出(尽管在某些情况下这么做是有用的),因为print()和pringtln()确实更容易使用。

1.4 PrintWriter类

   尽管使用System.out向控制台输出是可以接受的,但是最好将其用于调试或用于示例程序。对于实际的程序,使用Java向控制台输出的推荐方法是通过PrintWriter流。PrintWriter是基于字符的类之一。为控制台输出使用基于字符的流,可以使国际化更容易。
   PrintWriter类定义了几个构造函数,在此将使用的构造函数如下所示:

PrintWriter(OutputStream outputStream,boolean flushingOn)

   其中outputStream是OutputStream类型的对象,flushingOn控制Java是否每次调用println()方法时刷新输出流。如果flushingOn为true,就自动刷新;如果为false,那么不会自动刷新。
   PrintWriter支持print()和println()方法。因此,可以使用与System.out相同的方式使用它们。如果参数不是简单类型,PrintWriter方法会调用对象的toString()方法,然后输出结果。
   为了使用PrintWriter向控制台输出,为输出流指定System.out,然后在每个新行之后刷新流。例如,下面这行代码创建了一个连接到输出的PrintWriter:

PrintWriter pw = new PrintWriter(System.out,true);

   下面的应用程序演示了如何使用PrintWriter处理控制台输出:

import java.io.PrintWriter;
//Demostrate PrintWriter
public class PrintWriterDemo {
    public static void main(String[] args) {
        PrintWriter pw = new PrintWriter(System.out, true);
        pw.println("This is String");
        int i = -7;
        pw.println(i);
        double d = 4.5e-7;
        pw.println(d);
    }
    /**
     * 输出结果
     * This is String
     * -7
     * 4.5E-7
     */
}

   在学习Java或调试时,使用System.out向控制台输出简单文本没有什么问题。但是,使用PrintWriter可以使实际的应用程序更容易国际化。

1.5 读/写文件

   Java提供了大量用于读写文件的类和方法。对于读/写文件,两个最常用的流类是FileInputStream和FileOutputStream,这两个类创建与文件链接的字节流。为了打开文件,只需要简单地创建这些类中某个类的对象,指定文件名作为构造函数的参数即可。尽管这两个类也支持其他构造函数,但在此将使用以下形式:

FileInputStream(String fileName)throws FileNotFoundException
FileOutputStream(String fileName)throws FileNotFoundException

   其中,fileName指定希望打开的文件的名称。当创建输入流时,如果文件不存在,就会抛出FileNotFoundException异常。对于输出流,如果不能打开文件或不能创建文件,也会抛出FileNotFoundException异常。FileNotFoundException是IOException的子类。当打开输出文件时,先前存在的同名文件被销毁。
   注意:
   对于存在安全管理器的情况,如果在试图打开文件时发生安全性违规,那么文件类FileInputStream和FileOutputStream会抛出SecurityException异常。默认情况下,通过java运行的应用程序不会使用安全管理器。因此,不需要查看是否会发生SecurityException异常。但是,其他类型的应用程序(例如applet)会使用安全管理器,通过这类应用程序执行的文件I/O可能产生SecurityException异常。对于这种情况,需要适当地处理该异常。
   文件使用完之后必须关闭。关闭文件是通过close()方法完成的,FileInputStream和FileOutputStream都实现了该方法。该方法的声明如下所示:

void close()throws IOException

   关闭文件会释放为文件分配的系统资源,从而允许其他文件使用这些资源。关闭文件失败会导致“内存泄漏”,因为未使用的资源未被释放。
   注意:
   从JDK7开始,close()方法是由java.lang包中的AutoCloseable接口指定的。java.io包中的Closeable接口继承了AutoCloseable接口。所有流类都实现了这两个接口,包括FileInputStream和FileOutputStream。
   可以使用两种方式关闭文件。第一种是传统方法,当不在需要文件时显示调用close()方法。这是JDK7之前所有Java版本使用的方式。第二种方式是使用带资源的try语句,这种try语句是由JDK7新增的,当不再需要文件时能够自动关闭文件。在这种方式下,没有显示调用close()方法。
   为了读取文件,可以使用在FileInputStream中定义的read()版本:

int read()throws IOException

   每次调用read()方法时,都会从文件读取一个字节,并作为整数值返回。当到达文件末尾时,read()方法返回-1。该方法可能抛出IOException异常。
   下面的程序使用read()方法读取和显示文件的内容,该文件包含ASCII文本。文件名是作为命令行参数指定的。

/*
Display a text file
To use this program,specify the name of the file
that you want to see.
For example,to see a file called TEST.txt,
use the following command line.
 */
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class ShowFile {
    public static void main(String[] args) {
        int i;
        FileInputStream fin;
        //First,confirm that a filename has been specified.
        if (args.length != 1) {
            System.out.println("Usage: ShowFile filename");
            return;
        }
        //Attempt to open the file.
        try {
            fin = new FileInputStream(args[0]);
        } catch (FileNotFoundException e) {
            System.out.println("Cannot Open File");
            return;
        }
        //At this point,the file is open and can be read.
        //The following reads characters until EOF is encountered.
        try {
            do {
                i = fin.read();
                if (i != -1) System.out.println((char) i);
            } while (i != -1);

        } catch (IOException e) {
            System.out.println("Error Reading File");
        }
        //Close the file.
        try {
            fin.close();
        } catch (IOException e) {
            System.out.println("Error Closing File");
        }
    }
}

   在这个程序中,注意用来处理可能发生的I/O错误的try/catch代码块。对每个I/O操作都监视是否发生了异常,如果发生异常,就对异常进行处理。在简单程序或示例代码中将I/O异常简单地从main()中抛出是很常见的。在一些实际的代码中,将异常传播到调用例程,从而让调用者知道某个I/O操作失败,这可能是有用的。
   尽管上例在读取文件后关闭了文件流,但是另一种处理方式通常很有用,就是在finally代码块中调用close()方法。在这种方式下,访问文件的所有方法都被包含到try代码块中,并使用finally代码块关闭文件。对于这种方式,不管try代码块是如何终止的,文件都会被关闭。假定使用前面的例子,下面的代码显示了如何重新编码用于读取文件的try代码块:

 try {
      do {
          i = fin.read();
          if (i != -1) System.out.println((char) i);
      } while (i != -1);

  } catch (IOException e) {
      System.out.println("Error Reading File");
  }finally {
      //Close the file.
      try {
          fin.close();
      } catch (IOException e) {
          System.out.println("Error Closing File");
      }
  }

   尽管这么做对于这个例子没有什么问题,但一般来说,这种方式的一个优点是:即使访问文件的代码因为与I/O无关的异常而终止,finally代码块也仍然会关闭文件。
   有时在单个try代码块(而不是独立的两个代码块)中封装打开文件和访问文件的代码,然后使用finally代码块关闭文件更容易一些。例如,下面是编写ShowFile程序的另外一种方式:

import java.io.*;

/*
Display a text file
To use this program,specify the name of the file
that you want to see.
For example,to see a file called TEST.txt,
use the following command line.
 */
public class ShowFile {
    public static void main(String[] args) {
        int i;
        FileInputStream fin = null;
        //First,confirm that a filename has been specified.
        if (args.length != 1) {
            System.out.println("Usage: ShowFile filename");
            return;
        }
        //The following code opens a file,reads characters until EOF
        //is encountered,and then closes the file via a finally block.
        try {
            fin = new FileInputStream(args[0]);
            do {
                i = fin.read();
                if (i != -1) System.out.println((char) i);
            } while (i != -1);
        } catch (FileNotFoundException e) {
            System.out.println("File Not Found.");
            return;
        } catch (IOException e) {
            System.out.println("An I/O Error Occurred");
        } finally {
            //Close file in all cases.
            if (fin != null) {
                try {
                    fin.close();
                } catch (IOException e) {
                    System.out.println("Error Closing File");
                }
            }
        }
    }
}

   在这种方式下,注意fin被初始化为null。然后,在finally代码块中,只有当fin不是null时才关闭文件。这种方式可以工作,因为如果成功地打开文件,fin将不再是null。因此,如果在打开文件时发生了异常,就不会调用close()方法。
   对于前面的例子,还可以更紧凑地使用try/catch语句。因为FileNotFoundException是IOException的子类,所以这种异常不需要单独捕获。例如,下面的语句进行了重新编写以消除捕获FileNotFoundException异常。在这种情况下,会显示描述错误的标准异常消息。

 try {
       fin = new FileInputStream(args[0]);
       do {
           i = fin.read();
           if (i != -1) System.out.println((char) i);
       } while (i != -1);
   } catch (IOException e) {
       System.out.println("I/O Error: "+e);
   } finally {
       //Close file in all cases.
       try {
       if (fin != null) fin.close();
       } catch (IOException e) {
           System.out.println("Error Closing File");
       }
   }

   在这种方式下,所有错误,包括文件打开错误,都由一条catch语句简单地进行处理。但是,对于希望单独处理文件打开错误的情况,例如可能因为用户键入了错误的文件名而引起的错误,这种方式并不合适。对于这种情况,可能希望在进入访问文件的try代码块之前,提示用户改正文件名。
   为了向文件中写入内容,可以使用FileOutputStream定义的write()方法,该方法最简单地形式如下所示 :

void write(int byteval) throws IOException

   该方法向文件中写入由byteval指定的字节。尽管byteval被声明为整型,但只有低8位会被写入到文件中。如果写文件期间发生了错误,会抛出IOException异常。下一个例子使用write()方法复制文件:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/*
Copy a file.
To use this program,specify the name
of the source file and the destination file.
For example,to copy a file called FIRST.TEXt
to a file called SECOND.TEXT,use the following
command line.
java CopyFile FIRST>TXT SECOND.TXT
 */
public class CopyFile {
    public static void main(String[] args) {
        int i;
        FileInputStream fin = null;
        FileOutputStream fout = null;
        //First,confirm that both files have been specified.
        if (args.length != 2) {
            System.out.println("Usage: CopyFile from to");
            return;
        }
        //Copy a File.
        try {
            //Attempt to open the files.
            fin = new FileInputStream(args[0]);
            fout = new FileOutputStream(args[1]);
            do {
                i = fin.read();
                if (i != -1) fout.write(i);
            } while (i != -1);
        } catch (IOException e) {
            System.out.println("I/O Error: " + e);
        } finally {
            try {
                if (fin != null) fin.close();
            } catch (IOException e) {
                System.out.println("Error Closing Input File");
            }
            try {
                if (fout != null) fout.close();
            } catch (IOException e) {
                System.out.println("Error Closing Output File");
            }
        }
    }
}

   在这个程序中,在关闭文件时使用了两个独立的try代码块。这样可以确保两个文件都被关闭,即使对fin.close()方法的调用抛出异常。
   一般来说,就像前面的两个程序所做的那样,Java程序通过异常机制对所有潜在的I/O错误进行处理。这与使用错误代码报告文件错误的一些计算机语言不通。使用异常处理I/O错误,不但使文件处理更清晰,而且在执行输入时,还使Java能够更容易区分文件错误和文件结尾(End Of File,EOF)条件。

1.6 自动关闭文件

   上例中,一旦不再需要文件,示例程序就显式调用close()方法以关闭文件。这是JDK7以前的Java版本使用的文件关闭方式。尽管这种方式仍然有效并且有用,但是JDK7增加了一个新特性,该特性提供了另外一种管理资源(Automatic Resource Management ARM),该特性以try语句的扩展版为基础。自动资源管理的主要优点是:当不再需要文件(或其他资源)时,可以防止无意中忘记释放它们。忘记关闭文件可能导致内存泄漏,并且可能导致其他问题。
   自动资源管理基于try语句的扩展形式,它的一般形式如下所示:

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

   其中,resource-specification是用来声明和初始化资源(例如文件流)的语句。该语句包含一个变量声明,在该变量声明中使用将被管理的对象引用初始化变量。当try代码块结束时,自动释放资源。对于文件,这意味着会自动关闭文件(因此,不需要显式地调用close()方法)。当然,这种形式的try语句也可以包含catch和finally子句。新形式的try语句被称为带资源的try语句。
   只有对于那些实现了AutoCloseable接口的资源,才能使用带资源的try语句。AutoCloseable接口由java.lang包定义,该接口定义了close()方法。java.io包中的Closeable接口继承自AutoCloseable接口。所有流类都实现了这两个接口。因此,当使用流时——包括文件流,可以使用带资源的try语句。
   作为自动关闭文件的第一个例子,下面是ShowFile程序的改写版,该版本使用自动文件关闭功能:

import java.io.*;
/*
This version of the ShowFile program uses a
try-with-resources statement to automatically
close a file after it is no longer needed.
Note:This code required JDK7 or later
 */
public class ShowFile {
    public static void main(String[] args) {
        int i;
        //First,confirm that a filename has been specified.
        if (args.length != 1) {
            System.out.println("Usage: ShowFile filename");
            return;
        }
        //The following code uses a try-with-resources statement to open
        //a file and then automatically close it when the try block id left.
        try (FileInputStream fin = new FileInputStream(args[0])) {
            do {
                i = fin.read();
                if (i != -1) System.out.println((char) i);
            } while (i != -1);
        } catch (FileNotFoundException e) {
            System.out.println("File Not Found.");
        } catch (IOException e) {
            System.out.println("An I/O Error Occurred");
        }
    }
}

   在这个程序中,请特别注意在try语句中打开文件的方式:

try(FileInputStream fin = new FileInputStream(args[0])){

   注意try语句的资源约定部分声明了FileInputStream类型的变量fin,然后将由FileInputStream类构造函数打开的文件的引用赋给该变量。因此,在该程序的这个版本中,变量fin局限于try代码块,当进入try代码块时创建。当离开try代码块时,会隐式地调用close()方法以关闭与fin关联的流。不需要显示地调用close()方法,这意味着不可能忘记关闭文件,这是使用带资源的try语句的关键好处。
   try语句声明的资源被隐式声明为final,理解这一点很重要。这意味着在创建资源变量后,不能将其他资源赋给该变量,此外,资源的作用域限于带资源的try语句。
   可以在一条try语句中管理多个资源。为此,只需要简单地使用分号分隔每个资源约定即可。下面的程序显示了一个例子。该程序对前面显示的CopyFile程序进行了改写,使用带资源的try语句管理fin和fout。

import java.io.*;
/*A version of CopyFile that uses try-with-resources.
It demostrates two resources (in this case files) being
managed by a single try statement.
 */
public class CopyFile {
    public static void main(String[] args) {
        int i;
        //First,confirm that both files have been specified.
        if (args.length != 2) {
            System.out.println("Usage: CopyFile from to");
            return;
        }
        //Open and manage two files via the try statement.
        try (FileInputStream fin = new FileInputStream(args[0]);
             FileOutputStream fout = new FileOutputStream(args[1])) {
            do {
                i = fin.read();
                if (i != -1) fout.write(i);
            } while (i != -1);
        } catch (IOException e) {
            System.out.println("I/O Error: " + e);
        }
    }
}

   在这个程序中,请注意在try代码块总从文件读取内容以及将内容写入文件的方式:

 try (FileInputStream fin = new FileInputStream(args[0]);
             FileOutputStream fout = new FileOutputStream(args[1]))
 {
 //...            

   在try代码块结束之后,fin和fout都将被关闭。如果将程序的这个版本和前面的版本进行比较,可以发现该版本更短一些。流线化源代码的能力是自动资源管理优点的一个方面。
   对于带资源的try语句来说,还有另外一个方面需要提及:一般来说,当执行try代码块时,当在finally子句中关闭资源时,try代码块中的异常有可能导致发生另外一个异常。对于“常规的”try语句,原始异常丢失,被第二个异常取代。但是使用带资源的try语句时,第二个异常会被抑制,但是它没丢失,而是被添加到与第一个异常相关联的抑制异常列表中。通过使用Throwable定义的getSuppressed()方法可以获取抑制异常列表中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值