2015-12-03 4 views
25

документация Javadoc для потока гласит:Почему Files.lines (и подобные потоки) не закрываются автоматически?

Streams есть метод с BaseStream.close() и реализовать AutoCloseable, но почти все экземпляры потока на самом деле не должны быть закрыты после использования. Как правило, только потоки, источник которых является каналом ввода-вывода (например, те, которые возвращаются файлами Files.lines (Path, Charset)), требуют закрытия. Большинство потоков поддерживаются коллекциями, массивами или генерирующими функциями, которые не требуют специального управления ресурсами. (Если поток требует закрытия, он может быть объявлен как ресурс в отчете try-with-resources.)

Таким образом, в большинстве случаев можно использовать потоки в одном слое, например collection.stream().forEach(System.out::println);, но для Files.lines и других потоков с ресурсами, необходимо использовать оператор try-with-resources или ресурсы утечки.

Это поражает меня как подверженного ошибкам и ненужного. Поскольку Streams можно только повторить один раз, мне кажется, что нет ситуации, когда вывод Files.lines не должен быть закрыт, как только он был повторен, и поэтому реализация должна просто вызвать неявно в конце любого терминала операция. Я ошибаюсь?

+1

По моему опыту, потоки, которые автоматически закрываются, когда вы не хотите их, почти невозможно работать. Вы не можете повторно открыть то, что уже было закрыто * для * вас. Отметить, сбросить, искать. Вы можете прочитать некоторые данные более одного раза с одним и тем же потоком в зависимости от реализации. – ebyrob

+2

@ebyrob не с этим потоком – assylias

+3

Не лучше простого try-with-resource, но если вам действительно нужно сделать это с помощью одного выражения: http://stackoverflow.com/a/31179709/2711488 – Holger

ответ

29

Да, это было преднамеренное решение. Мы рассмотрели обе альтернативы.

Принцип операционного проектирования здесь «объект, который приобретает ресурс, должен освободить ресурс». Файлы не закрываются автоматически при чтении в EOF; мы ожидаем, что файлы будут закрыты явно. Потоки, поддерживаемые ресурсами ввода-вывода, одинаковы.

К счастью, язык предоставляет механизм для автоматизации этого для вас: try-with-resources. Поскольку поток реализует AutoCloseable, вы можете сделать:

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

Аргумент, что «было бы очень удобно для автоматического близко, так что я мог бы написать это как один-лайнер» это хорошо, но будет в основном хвост виляет собака. Если вы открыли файл или другой ресурс, вы также должны быть готовы его закрыть. Эффективные и последовательные козыри управления ресурсами «Я хочу написать это в одной строке», и мы решили не искажать дизайн, чтобы сохранить однострочный характер.

+0

Я предполагаю, что логическое обоснование здесь заключается в том, что если есть необработанное исключение, Stream может быть не «прочитан полностью», а затем базовый дескриптор будет " никогда не закрывался ». Таким образом, это позволяет избежать этой проблемы. Слишком плохо, что он разбивает цепочку потоков и запутывается, потому что «большинству других потоков» не нужна эта парадигма. Итак, когда вы используете Try-with-Resources с объектами типа Stream? Иногда ... но потом еще не раз. Похоже, что метод #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

Кроме того, если вы хотите «написать одну строку». Вы можете просто сделать это:

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

 Смежные вопросы

  • Нет связанных вопросов^_^