« Java:例外が起こったときに実行されるコードとされないコード | トップページ | Java:MacからWindowsへのファイル転送で作成される余分なファイルを削除する »

Java:Reader/Writerにおけるclose()メソッド呼び出しの流儀

【課題】
今回の記事では,次の2点について確認します。

●確認1
テキストファイルを読み込むためのクラスインスタンスを以下の手順で作成してファイル読み込みを行ったとします

  File infile = new File("入力ファイルのパス");
 
  FileInputStream is = new FileInputStream(infile);
  InputStreamReader sr = new InputStreamReader(is,"utf-8");
  BufferedReader r = new BufferedReader(sr);
上記で作成したBufferedReaderでテキストを読み込み終わったあと,作成した3つのクラスインスタンス
・BufferedReader
・InputStreamReader
・FileInputStream
これ全部に対してcloseを実行する必要があるか,それとも最後に作成したBufferedReaderだけをcloseすれば大丈夫か。

ちなみにBufferedReaderのAPIリファレンスに使用例として以下のコードが掲載されています。

BufferedReader in = new BufferedReader(new FileReader("foo.in"));
この例の通りに記述すると,FileReaderへの参照がないためFileReaderをcloseできません。
それで問題ないのか確認します。
https://docs.oracle.com/javase/jp/6/api/java/io/BufferedReader.html

●確認2
Writerの派生クラスのインスタンスからデータを書き出した後,すべてのデータを書き出すためにflushが実行される必要がありますが,そのためにflushメソッドを明示的に呼び出すべきかどうか確認します。

●検証用テストコード
確認1と2の両方とも以下のコードで確認します。

public class FileIOTest
{
  public static void main(String[] args)
  {
    try {
      File infile = new File("入力ファイルのパス");
      File outfile = new File("出力ファイルのパス");
 
      FileInputStream is = new FileInputStream(infile);
      InputStreamReader sr = new InputStreamReader(is);
      BufferedReader r = new BufferedReader(sr);
 
      FileOutputStream os = new FileOutputStream(outfile);
      OutputStreamWriter sw = new OutputStreamWriter(os);
      BufferedWriter w = new BufferedWriter(sw);
 
      try {
        String line;
        while ((line = r.readLine()) != null) {
          w.write(line);
          w.newLine();
        }
      }
      finally {
        //try { r.read(); } catch(IOException ex) { ex.printStackTrace(); }
        try { r.close(); } catch(IOException ex) { ex.printStackTrace(); }
        //try { sr.read(); } catch(IOException ex) { ex.printStackTrace(); }
        try { sr.close(); } catch(IOException ex) { ex.printStackTrace(); }
        //try { is.read(); } catch(IOException ex) { ex.printStackTrace(); }
        try { is.close(); } catch(IOException ex) { ex.printStackTrace(); }
 
        //try { w.flush(); } catch(IOException ex) { ex.printStackTrace(); }
        try { w.close(); } catch(IOException ex) { ex.printStackTrace(); }
        //try { sw.flush(); } catch(IOException ex) { ex.printStackTrace(); }
        try { sw.close(); } catch(IOException ex) { ex.printStackTrace(); }
        //try { os.flush(); } catch(IOException ex) { ex.printStackTrace(); }
        try { os.close(); } catch(IOException ex) { ex.printStackTrace(); }
      }
    }
    catch (IOException ex) {
      ex.printStackTrace();
    }
  }
}
※上記コードでは,整形のため空白部分は全角スペースを使用しています。
テストコードのうち,finallyブロックの前段6行がReaderクラスメソッドの検証用,後段6行がWriterクラスメソッドの検証用コードになります。
finallyブロックの「read」メソッドは,これを呼んでIOExceptionが起きるかどうかで各インスタンスがまだ読み込み可能か確認するために呼んでいます。

●確認1:検証
テストコードのfinallyブロックのうち,前段6行のコメントをはずして実行してみます。
コメントをはずしたfinallyブロック前段:

  try { r.read(); } catch(IOException ex) { ex.printStackTrace(); }
  try { r.close(); } catch(IOException ex) { ex.printStackTrace(); }
  try { sr.read(); } catch(IOException ex) { ex.printStackTrace(); }
  try { sr.close(); } catch(IOException ex) { ex.printStackTrace(); }
  try { is.read(); } catch(IOException ex) { ex.printStackTrace(); }
  try { is.close(); } catch(IOException ex) { ex.printStackTrace(); }

上記のように修正して実行すると,以下の2つの例外が起こります。
java.io.IOException: Stream closed
  at sun.nio.cs.StreamDecoder.ensureOpen(StreamDecoder.java:27)
  at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:128)
  at sun.nio.cs.StreamDecoder.read0(StreamDecoder.java:107)
  at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:93)
  at java.io.InputStreamReader.read(InputStreamReader.java:151)
  at ymnk.FileIOTest.main(FileIOTest.java:38)
java.io.IOException: Bad file descriptor
  at java.io.FileInputStream.read(Native Method)
  at ymnk.FileIOTest.main(FileIOTest.java:40)

1つ目の例外は,finallyブロック3行目の「sr.read();」を実行して「すでにcloseされている」という例外が起きています。
「r.close();」を実行することでsrもcloseされることがわかります。
2つ目の例外は,finallyブロック5行目の「is.read();」を実行して「間違ったファイル記述子」という例外が起きています。
エラーの意味がイマイチよくわかりませんが,「r.close();」「sr.close();」をコメントにすると例外が起きなくなるので,原因は先に実行された「r.close();」メソッドの実行かと思われます。
「sr.read();」と「sr.close();」をコメントにしても例外が起きるので,「r.close();」を実行すると,以降のreadメソッド呼び出しはすべて例外になるようです。
ちなみにcloseメソッドでは例外は起きません。これについて各APIリファレンスによると「InputStreamReader」と「BufferedReader」のcloseメソッドの説明では「すでに閉じられているストリームを閉じても、何の影響もありません」となっています。
※「FileInputStream」のcloseメソッドの説明には多重closeについての記述はありません。
●確認1:結論
ファイル入力について,
BufferedReader r = new BufferedReader(
            new InputStreamReader(
            new FileInputStream(new File("入力ファイルパス"))));
※上記コードでは,整形のため空白部分は全角スペースを使用しています。
このコードでBufferedReaderインスタンスを生成して読み込みを行い「r.close()」で閉じた場合,BufferedReaderだけでなくInputStreamReaderもFileInputStreamも閉じられます。BufferedReaderだけをcloseすれば大丈夫です。

●確認2:検証
テストコードのfinallyブロックのうち,前段6行を元に戻して後段6行のコメントをはずして実行してみます。
コメントをはずしたfinallyブロック後段:

  try { w.flush(); } catch(IOException ex) { ex.printStackTrace(); }
  try { w.close(); } catch(IOException ex) { ex.printStackTrace(); }
  try { sw.flush(); } catch(IOException ex) { ex.printStackTrace(); }
  try { sw.close(); } catch(IOException ex) { ex.printStackTrace(); }
  try { os.flush(); } catch(IOException ex) { ex.printStackTrace(); }
  try { os.close(); } catch(IOException ex) { ex.printStackTrace(); }

これで実行すると,以下の例外が起こります。
java.io.IOException: Stream closed
  at sun.nio.cs.StreamEncoder.ensureOpen(StreamEncoder.java:26)
  at sun.nio.cs.StreamEncoder.flush(StreamEncoder.java:121)
  at java.io.OutputStreamWriter.flush(OutputStreamWriter.java:212)
  at ymnk.FileIOTest.main(FileIOTest.java:45)

finallyブロック後段3行目の「sw.flush();」を実行して「すでにcloseされている」という例外が起きています。closeは2行目の「w.close();」で行われており,closeしたストリームをflushすると例外になることがわかります。
これについてOutputStreamWriterのAPIリファレンスを調べてみると,closeメソッドを実行した後は,write及びflushメソッドの呼び出しでIOException例外が発生する旨の記述があります。
同時に,closeが呼ばれたら,まずflushが行われること,closeしたストリームをcloseしても問題ない旨の記述があります。
https://docs.oracle.com/javase/jp/6/api/java/io/Writer.html
●確認2:結論
Writerの派生クラスは出力が完了したあとで明示的にflushを呼ばなくてもcloseでflushが行われます。
また,closeを実行してからflushを呼ぶと例外が起きてしまいます。
flushを呼んで例外が起きた場合,その例外を確実にcatchしないと以降のclose処理が行われないため,closeを呼ぶべき場所でflushも呼ぶのは逆に危険です。

【まとめ】
どちらの確認でも結論は同じでした。
「入出力の終了時はcloseを1回呼べばOK」

【著作権表記】上記コードを含む本ブログのプログラムコードは,私的利用可,商用利用可,改変しての利用可です。利用の際に作者に許諾を得る必要はありません。

■関連書籍をAmazonで検索:[Java]
スッキリわかるJava入門 第2版(kindle版)



にほんブログ村 IT技術ブログへ にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキングへ ←この記事が役に立ったという方はクリックお願いします。


|

« Java:例外が起こったときに実行されるコードとされないコード | トップページ | Java:MacからWindowsへのファイル転送で作成される余分なファイルを削除する »

Java」カテゴリの記事

トラックバック


この記事へのトラックバック一覧です: Java:Reader/Writerにおけるclose()メソッド呼び出しの流儀:

« Java:例外が起こったときに実行されるコードとされないコード | トップページ | Java:MacからWindowsへのファイル転送で作成される余分なファイルを削除する »