webentwicklung-frage-antwort-db.com.de

Warum werden Files.lines (und ähnliche Streams) nicht automatisch geschlossen?

Der Javadoc für Stream besagt:

Streams verfügen über eine BaseStream.close () - Methode und implementieren AutoCloseable, aber nahezu alle Stream-Instanzen müssen nach der Verwendung nicht wirklich geschlossen werden. Im Allgemeinen müssen nur Streams, deren Quelle ein IO - Kanal ist (wie die von Files.lines (Path, Charset) zurückgegebenen), geschlossen werden. Die meisten Streams werden durch Sammlungen, Arrays oder Generierungsfunktionen unterstützt, für die keine besondere Ressourcenverwaltung erforderlich ist. (Wenn ein Stream geschlossen werden muss, kann er in einer try-with-resources-Anweisung als Ressource deklariert werden.) 

Daher kann man Streams meistens in einem Einzeiler wie collection.stream().forEach(System.out::println); verwenden, aber für Files.lines und andere ressourcengestützte Streams muss man eine try-with-resources-Anweisung verwenden oder Ressourcen verlieren.

Dies erscheint mir als fehleranfällig und unnötig. Da Streams nur einmal iteriert werden können, scheint es mir nicht so zu sein, dass die Ausgabe von Files.lines nicht geschlossen wird, sobald sie iteriert wurde. Daher sollte die Implementierung am Ende eines Terminals einfach implizit close aufrufen Operation. Irre ich mich

43
MikeFHay

Ja, das war eine bewusste Entscheidung. Wir haben beide Alternativen in Betracht gezogen. 

Das Betriebsentwurfsprinzip lautet hier: "Wer die Ressource erwirbt, sollte die Ressource freigeben". Dateien werden nicht automatisch geschlossen, wenn Sie EOF lesen. wir erwarten, dass Dateien von jedem, der sie geöffnet hat, explizit geschlossen werden. Streams, die von IO -Ressourcen unterstützt werden, sind die gleichen.

Glücklicherweise bietet die Sprache einen Mechanismus, um dies für Sie zu automatisieren: Try-with-Ressourcen. Da Stream AutoCloseable implementiert, können Sie Folgendes tun:

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

Das Argument, "es wäre wirklich praktisch, automatisch zu schließen, damit ich es als One-Liner schreiben könnte", ist Nizza, wäre aber meistens der Schwanz, der den Hund wedelt. Wenn Sie eine Datei oder eine andere Ressource geöffnet haben, sollten Sie auch bereit sein, sie zu schließen. Effektives und konsistentes Ressourcenmanagement ist ein Trumpf "Ich möchte dies in einer Zeile schreiben", und wir haben uns entschieden, das Design nicht zu verzerren, nur um die Einzeiligkeit zu erhalten. 

55
Brian Goetz

Ich habe neben der @BrianGoetz-Antwort ein spezifischeres Beispiel. Vergessen Sie nicht, dass die Stream Escape-Schraffur-Methoden wie iterator() hat. Angenommen, Sie machen Folgendes:

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

Danach können Sie hasNext() und next() mehrmals aufrufen und dann diesen Iterator aufgeben: Die Iterator-Schnittstelle unterstützt eine solche Verwendung perfekt. Es gibt keine Möglichkeit, die Iterator explizit zu schließen. Das einzige Objekt, das Sie hier schließen können, ist die Stream. Auf diese Weise würde es perfekt funktionieren:

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.
15
Tagir Valeev

Zusätzlich wenn Sie "eine Zeile schreiben" wollen. Sie können dies einfach tun:

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

Sie können es verwenden, wenn Sie sicher sind, dass Sie die gesamte Datei benötigen und die Datei klein ist. Weil es keine faule Lektüre ist.

4

Wenn Sie wie ich faul sind und nichts dagegen haben, dass "wenn eine Ausnahme ausgelöst wird, wird der Dateizugriff geöffnet", können Sie den Stream in einen automatisch schließenden Stream einschließen.

  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
rogerdpack