23

ストリーム パイプライン中に多くのファイルを開くこのコードを実行すると:

public static void main(String[] args) throws IOException {
    Files.find(Paths.get("JAVA_DOCS_DIR/docs/api/"),
            100, (path, attr) -> path.toString().endsWith(".html"))
        .map(file -> runtimizeException(() -> Files.lines(file, StandardCharsets.ISO_8859_1)))
        .map(Stream::count)
        .forEachOrdered(System.out::println);
}

例外があります:

java.nio.file.FileSystemException: /long/file/name: Too many open files

問題はStream.count、ストリームのトラバースが完了したときにストリームを閉じないことです。しかし、それが端末操作であることを考えると、なぜそうすべきではないのかわかりません。reduceやなどの他の端末操作についても同様forEachです。flatMap一方、それが構成するストリームを閉じます。

ドキュメントには、必要に応じて try-with-resouces-statement を使用してストリームを閉じるように指示されています。私の場合、count行を次のように置き換えることができます。

.map(s -> { long c = s.count(); s.close(); return c; } )

しかし、これはうるさくて醜く、大規模で複雑なパイプラインを使用する場合には、非常に不便な場合があります。

だから私の質問は次のとおりです。

  1. ターミナル操作が作業中のストリームを閉じるようにストリームが設計されなかったのはなぜですか? これにより、IO ストリームでより適切に機能するようになります。
  2. パイプラインで IO ストリームを閉じるための最適なソリューションは何ですか?

runtimizeExceptionチェックされた例外をRuntimeExceptions にラップするメソッドです。

4

5 に答える 5

22

ここには 2 つの問題がありますIOException

事前定義された関数インターフェイスのいずれも、チェック済みの例外を宣言していません。つまり、ラムダ内で処理するか、未チェックの例外にラップして再スローする必要があります。あなたのruntimizeException機能はそれを行うようです。おそらく、独自の関数インターフェイスを宣言する必要もありました。お気づきかもしれませんが、これは苦痛です。

ファイルなどのリソースのクローズでは、ストリームの終わりに達したときにストリームを自動的にクローズするという調査が行われました。これは便利ですが、例外がスローされたときのクローズには対応していません。ストリームでは、これを正しく行うための魔法のメカニズムはありません。

リソース クロージャを処理する Java の標準的な手法、つまり Java 7 で導入されたtry-with-resourcesコンストラクトが残っています。TWR は、実際には、コール スタック内のリソースが開かれたときと同じレベルでリソースを閉じたいと考えています。「開く者は閉じなければならない」という原則が適用されます。TWR は例外処理も処理します。通常、例外処理とリソースのクローズを同じ場所で処理すると便利です。

この例では、ストリームが aStream<Path>を aにマップするという点で、ストリームは少し変わっていますStream<Stream<String>>。これらのネストされたストリームは閉じられていないストリームであり、システムが開いているファイル記述子を使い果たすと、最終的に例外が発生します。これを困難にしているのは、ファイルが 1 つのストリーム操作で開かれ、その後下流に渡されることです。これにより、TWR を使用できなくなります。

このパイプラインを構築する別の方法は次のとおりです。

呼び出しはFiles.linesファイルを開くものであるため、これは TWR ステートメントのリソースでなければなりません。このファイルの処理は、(一部の)IOExceptionsスローされる場所であるため、同じ TWR ステートメントで例外のラッピングを行うことができます。これは、リソースのクローズと例外のラッピングを処理しながら、パスを行数にマップする単純な関数を持つことを示唆しています。

long lineCount(Path path) {
    try (Stream<String> s = Files.lines(path, StandardCharsets.ISO_8859_1)) {
        return s.count();
    } catch (IOException ioe) {
        throw new UncheckedIOException(ioe);
    }
}

このヘルパー関数を作成すると、メイン パイプラインは次のようになります。

Files.find(Paths.get("JAVA_DOCS_DIR/docs/api/"),
           100, (path, attr) -> path.toString().endsWith(".html"))
     .mapToLong(this::lineCount)
     .forEachOrdered(System.out::println);
于 2014-04-08T07:10:20.320 に答える
4

このストリーム操作を呼び出す必要がありますclose()。これにより、基になるすべてのクローズ ハンドラーが呼び出されます。

さらに良いのは、ステートメント全体をtry-with-resourcesブロックでラップすることです。そうすれば、クローズ ハンドラーが自動的に呼び出されます。

これは、あなたの状況では可能ではないかもしれません。これは、何らかの操作で自分で処理する必要があることを意味します。現在の方法は、ストリームにはまったく適していない可能性があります。

map()2回目の操作で実際にそれを行う必要があるようです。

于 2014-04-07T19:49:55.133 に答える
0

Filesからの別の方法を使用し、ファイル記述子のリークを回避する代替手段を次に示します。

Files.find(Paths.get("JAVA_DOCS_DIR/docs/api/"),
    100, (path, attr) -> path.toString().endsWith(".html"))
    .map(file -> runtimizeException(() -> Files.readAllLines(file, StandardCharsets.ISO_8859_1).size())
    .forEachOrdered(System.out::println);

あなたのバージョンとは異なり、行数のint代わりに aを返します。longしかし、そんなに多くの行を含むファイルはありませんよね?

于 2014-04-07T20:01:48.090 に答える