17

Java ファイル システム API をいじってみたところ、バイナリ ファイルのコピーに使用される次の関数が思い浮かびました。元のソースは Web から取得しましたが、try/catch/finally 句を追加して、何か問題が発生した場合に、関数を終了する前にバッファー ストリームが閉じられる (したがって、OS リソースが解放される) ようにしました。

パターンを表示するために関数を縮小しました。

public static void copyFile(FileOutputStream oDStream, FileInputStream oSStream) throw etc...
{
   BufferedInputStream oSBuffer = new BufferedInputStream(oSStream, 4096);
   BufferedOutputStream oDBuffer = new BufferedOutputStream(oDStream, 4096);

   try
   { 
      try
      { 
         int c;

         while((c = oSBuffer.read()) != -1)  // could throw a IOException
         {
            oDBuffer.write(c);  // could throw a IOException
         }
      }
      finally
      {
         oDBuffer.close(); // could throw a IOException
      }
   }
   finally
   {
      oSBuffer.close(); // could throw a IOException
   }
}

私が理解している限りでは、2 つclose()を finally 句に入れることはできません。1 つ目は問題close()なくスローでき、2 つ目は実行されないからです。

C# には、キーワードでこれを処理するDisposeパターンがあることを知っています。using

私は、C++ コードが (Java のような API を使用して) 次のようになることをよく知っています。

void copyFile(FileOutputStream & oDStream, FileInputStream & oSStream)
{
   BufferedInputStream oSBuffer(oSStream, 4096);
   BufferedOutputStream oDBuffer(oDStream, 4096);

   int c;

   while((c = oSBuffer.read()) != -1)  // could throw a IOException
   {
      oDBuffer.write(c);  // could throw a IOException
   }

   // I don't care about resources, as RAII handle them for me
}

何か不足していますか、それともclose()Buffered Stream のメソッドで例外を処理するためだけに、Java で見苦しく肥大化したコードを作成する必要があるのでしょうか?

(どこか間違っているので教えてください...)

編集:それは私ですか、それともこのページを更新したときに、質問とすべての回答の両方が数分で1ポイント減少したのを見ましたか? 誰かが匿名のままで楽しみすぎていませんか?

編集 2: McDowellは、私がここで言及しなければならないと感じた非常に興味深いリンクを提供しました: http://illegalargumentexception.blogspot.com/2008/10/java-how-not-to-make-mess-of-stream.html

編集 3: McDowell のリンクに続いて、パターンを使用する C# に似たパターンの Java 7 の提案を見つけました: http://tech.puredanger.com/java7/#resourceblock。私の問題は明示的に説明されています。どうやら、Java 7 でもdo、問題は残っています。

4

5 に答える 5

19

Java 6 以前では、ほとんどの場合、try/finally パターンがストリームを処理する正しい方法です。

黙ってストリームを閉じることを提唱する人もいます。これらの理由から、これを行う際には注意してください: Java: ストリーム処理を混乱させない方法


Java 7 ではtry-with-resources が導入されました。

/** transcodes text file from one encoding to another */
public static void transcode(File source, Charset srcEncoding,
                             File target, Charset tgtEncoding)
                                                             throws IOException {
    try (InputStream in = new FileInputStream(source);
         Reader reader = new InputStreamReader(in, srcEncoding);
         OutputStream out = new FileOutputStream(target);
         Writer writer = new OutputStreamWriter(out, tgtEncoding)) {
        char[] buffer = new char[1024];
        int r;
        while ((r = reader.read(buffer)) != -1) {
            writer.write(buffer, 0, r);
        }
    }
}

AutoCloseableタイプは自動的に閉じられます:

public class Foo {
  public static void main(String[] args) {
    class CloseTest implements AutoCloseable {
      public void close() {
        System.out.println("Close");
      }
    }
    try (CloseTest closeable = new CloseTest()) {}
  }
}
于 2008-10-11T16:33:45.340 に答える
4

残念ながら、このタイプのコードは、Java では少し肥大化する傾向があります。

ところで、oSBuffer.read または oDBuffer.write への呼び出しの 1 つが例外をスローした場合、おそらくその例外を呼び出し階層に浸透させたいと思うでしょう。

finally 節内で無防備な close() 呼び出しを行うと、元の例外が close() 呼び出しによって生成された例外に置き換えられます。つまり、失敗した close() メソッドは、read() または write() によって生成された元の例外を隠す可能性があります。したがって、他のメソッドがスローしなかった場合に限り、 close() によってスローされた例外を無視する必要があると思います。

私は通常、内側の try 内に明示的な close-call を含めることでこれを解決します。

  試す {
    その間 (...) {
      読んだ...
      書きます...
    }
    oSBuffer.close(); // ここでは例外は無視されません
    oDBuffer.close(); // ここでは例外は無視されません
  } 最後に {
    サイレントクローズ(oSBuffer); // ここでは例外を無視
    サイレントクローズ(oDBuffer); // ここでは例外を無視
  }
  static void silentClose(Closeable c) {
    試す {
      c.close();
    } キャッチ (IOException ie) {
      // 無視されます。呼び出し元はこの意図を持っている必要があります
    }
  }

最後に、パフォーマンスのために、コードはおそらくバッファー (読み取り/書き込みごとに複数バイト) で動作するはずです。数字でそれを裏付けることはできませんが、バッファリングされたストリームを上に追加するよりも呼び出しが少ない方が効率的です。

于 2008-10-11T16:29:02.997 に答える
4

問題はありますが、ウェブ上で嘘をついているのを見つけたコードは本当に貧弱です。

バッファ ストリームを閉じると、その下のストリームが閉じます。あなたは本当にそれをしたくありません。必要なのは、出力ストリームをフラッシュすることだけです。また、基になるストリームがファイル用であると指定しても意味がありません。一度に 1 バイトずつコピーしているため、パフォーマンスが低下します (実際に java.io を使用する場合は、少し高速な transferTo/transferFrom を使用できます)。私たちがそれについて話している間、変数名はうんざりです。そう:

public static void copy(
    InputStream in, OutputStream out
) throw IOException {
    byte[] buff = new byte[8192];
    for (;;) {
        int len = in.read(buff);
        if (len == -1) {
            break;
        }
        out.write(buff, 0, len);
    }
}

try-finally を頻繁に使用していることに気付いた場合は、"execute around" イディオムを使用して除外できます。

私の意見では、Java は何らかの方法でスコープの最後でリソースを閉じる必要があります。private囲んでいるブロックの最後で閉じる単項後置演算子として追加することをお勧めします。

于 2008-10-11T16:36:19.940 に答える
3

はい、それがJavaの仕組みです。制御の反転があります。オブジェクトのユーザーは、オブジェクト自体が後でクリーンアップするのではなく、オブジェクトをクリーンアップする方法を知っている必要があります。残念ながら、これは Java コード全体に散在する多くのクリーンアップ コードにつながります。

C# には、オブジェクトがスコープ外になったときに Dispose を自動的に呼び出す "using" キーワードがあります。Javaにはそのようなものはありません。

于 2008-10-11T16:50:09.363 に答える
2

ファイルのコピーなどの一般的なIOタスクの場合、上記のようなコードは車輪の再発明です。残念ながら、JDKはより高いレベルのユーティリティを提供していませんが、apachecommons-ioは提供しています。

たとえば、FileUtilsには、ファイルやディレクトリを操作するためのさまざまなユーティリティメソッドが含まれています(コピーを含む)。一方、JDKでIOサポートを実際に使用する必要がある場合、IOUtilsには、例外をスローせずにリーダー、ライター、ストリームなどを閉じる一連のcloseQuietly()メソッドが含まれています。

于 2008-10-11T17:14:52.090 に答える