2015-12-03 8 views
25

ストリームのためのjavadocは述べている:Files.lines(およびそれに類するストリーム)が自動的に閉じられないのはなぜですか?

ストリームはBaseStream.close()メソッドを持っているとAutoCloseableを実装しますが、ほぼすべてのストリーム・インスタンスは、実際に使用した後に閉じする必要はありません。通常、ソースがIOチャネルであるストリーム(Files.lines(Path、Charset)によって返されるストリームなど)のみを閉じる必要があります。ほとんどのストリームは、特別なリソース管理を必要としないコレクション、配列、または生成関数によってサポートされています。ストリームが終了する必要がある場合、try-with-resourcesステートメントでリソースとして宣言できます。

したがって、ほとんどの場合、ストリームは1つのライナーで使用できますcollection.stream().forEach(System.out::println);ではなく、Files.linesおよびその他のリソースバックアップストリームについては、try-with-resourcesステートメントを使用するか、リソースをリークする必要があります。

これはエラーが発生しやすく、不要であると私に襲われます。ストリームは一度しか反復することができないので、反復されるとすぐにFiles.linesの出力を閉じるべきではないという状況はないと考えられます。したがって、実装は、単純に任意の端末の終わりで暗黙的に呼び出す必要があります操作。私は間違っていますか?

+1

私の経験では、ストリームを自動クローズしたくない場合は、ほとんど使用できません。 *あなたのためにすでに閉鎖されたものを再オープンすることはできません。マーク、リセット、シーク実装によっては、同じストリームで複数のデータを複数回読み込むことができます。 – ebyrob

+2

@ebyrobはそのストリームではありません – assylias

+3

単純なtry-with-resourceよりも優れているわけではありませんが、本当に単一の式でそれを行う必要がある場合は、http://stackoverflow.com/a/31179709/2711488 – Holger

答えて

29

はい、これは意図的な決定でした。我々は両方の選択肢を検討した。

ここでの動作設計の原則は、「リソースを取得するエンティティがリソースを解放する必要があります」です。 EOFに読み込むとファイルは自動的に閉じません。ファイルが明示的に閉じられることが期待されます。 IOリソースによってバックアップされるストリームは同じです。

幸いにも、この言語は、あなたのためにこれを自動化するメカニズムを提供します:try-with-resources。ストリームはAutoCloseableを実装しているので、あなたが行うことができます:

try (Stream<String> s = Files.lines(...)) { 
    s.forEach(...); 
} 

いいです「それは自動クローズので、私はワンライナーとしてそれを書くことができると本当に便利である」が、ほとんど振る尾だろうという議論犬。ファイルやその他のリソースを開いた場合は、そのリソースを閉じる準備もしておく必要があります。効果的で一貫したリソース管理は、「私はこれを1行に書いておきたい」という言葉よりもはるかに優先し、1行しか保持しないためにデザインを歪ませないことを選択しました。

+0

ここでの根拠は、未処理の例外がある場合、ストリームが "すべての方法で読み込まれない"ことがあり、決して閉じられませんでした。これにより、この問題は回避されます。あまりにもそれはストリームの連鎖を分割し、 "ほとんどの他のストリーム"がこのパラダイムを必要としないので混乱しています。だから、あなたはストリーム型のオブジェクトでTry-with-Resourcesをいつ使うのですか?時々...しかし、それ以外の時はもう一度ではありません。 #closeメソッドがパイプラインが「終了」されている場合でも、通常のパイプラインで呼び出されることはありません... – rogerdpack

+1

私の意見ではこれは気付きにくいです。 Files.lines()javadocにはありません。Eclipseは、同じ行に終了操作を置いて、ストリームとして変数を持たない場合には、閉じられていないリソースについて警告しません。 – aalku

12

@BrianGoetz答えに加えて、より具体的な例があります。 Streamにはiterator()のようなエスケープ・ハッチ方式があることを忘れないでください。あなたはこれをやっていると仮定します。その後

Iterator<String> iterator = Files.lines(path).iterator(); 

をあなたはhasNext()next()複数回呼び出すことができ、その後、ちょうどこのイテレータを放棄:Iteratorインタフェースは完全にそのような使用をサポートしています。 Iteratorを明示的に閉じることはできません。ここで閉じることができるオブジェクトはStreamです。このようにすれば、完全に正常に動作します。

try(Stream<String> stream = Files.lines(path)) { 
    Iterator<String> iterator = stream.iterator(); 
    // use iterator in any way you want and abandon it at any moment 
} // file is correctly closed here. 
2

さらに、「1行書き込み」が必要な場合。

Files.readAllLines(source).stream().forEach(...); 

ファイル全体が必要であり、ファイルが小さいことが確信できる場合に使用できます。それは怠惰な読書ではないからです。

+2

ここで '.stream()'は不要です。 –

+3

そして、ファイルが大きすぎてメモリに収まらないことを確認する必要があります。 – Oliv

0

私が気に入らず、「例外が発生した場合はファイルハンドルを開いたままにしておいてください」と気にしない場合は、ストリームを自動クローゼットストリームにラップすることができます。ウェイ):

static Stream<String> allLinesCloseAtEnd(String filename) throws IOException { 
    Stream<String> lines = Files.lines(Paths.get(filename)); 
    Iterator<String> linesIter = lines.iterator(); 

    Iterator it = new Iterator() { 
     @Override 
     public boolean hasNext() { 
     if (!linesIter.hasNext()) { 
      lines.close(); // auto-close when reach end 
      return false; 
     } 
     return true; 
     } 

     @Override 
     public Object next() { 
     return linesIter.next(); 
     } 
    }; 
    return StreamSupport.stream(Spliterators.spliteratorUnknownSize(it, Spliterator.DISTINCT), false); 
    } 
+1

これは動作しません。ストリームがすべての要素を消費するという保証はありません。 'find()'や '... Match(...)'や 'limit(...)'や 'takeWhile(...)'のような短絡操作があります。アプリケーションが 'iterator()'または 'spliterator()'でストリームを終了した場合、それが最後まで反復する保証もありません。したがって、ソリューションは効率性を大幅に低下させるだけで、ユースケースの数が少なくなります。 – Holger

+0

また、良い点、ありがとう! (すべての行を読んでも動作しますが、そうでない場合はこれを使用しないことをお勧めします)。または、ストリームを渡すことができる機能、たとえばストリームを開くメソッドからストリームを渡すことができる機能を考慮する人もいれば、最終的に使用された場合には正常に自己クローズすることもできます。 – rogerdpack

関連する問題