Завершают ли терминальные операции поток?

dirPath содержит 200 тыс. файлов. Я хочу прочитать их один за другим и сделать некоторые обработки. Следующий фрагмент вызывает java.nio.file.FileSystemException: dirPath/file-N Too many open files. Разве терминальная операция forEach() должна закрывать открытый поток (т.е. Открытый файл) перед переходом к следующему? Другими словами, мне нужно добавить try-in-resources для потоковых файлов?

Files.list(dirPath)
     .forEach(filePath -> {
              Files.lines(filePath).forEach() { ... }
              });

Ответы

Ответ 1

Нет forEach не закрывает поток (созданный Files.list или Files.lines). Он задокументирован в javadoc, например, для Files.list:

Возвращаемый поток инкапсулирует Reader. Если требуется своевременное удаление ресурсов файловой системы, следует использовать конструкцию try-with-resources для обеспечения того, чтобы метод закрытия потока был вызван после завершения операций потока.

Ответ 2

Вложенные forEach в большинстве случаев являются неправильным инструментом.

Код

Files.list(dirPath).forEach(filePath -> Files.lines(filePath).forEach(line -> { ... });

может и должен быть заменен на

Files.list(dirPath).flatMap(filePath -> Files.lines(filePath)).forEach(line -> { ... });

или хорошо, поскольку в этом случае это не так просто:

Files.list(dirPath).flatMap(filePath -> {
    try { return Files.lines(filePath);}
    catch(IOException ex) { throw new UncheckedIOException(ex); }
}).forEach(line -> {  });

как побочный эффект, вы получаете следующее бесплатно:

Stream.flatMap(…):

Каждый отображаемый поток закрывается после того, как его содержимое было помещено в этот поток.

Итак, это предпочтительное решение. Или хорошо, чтобы сделать его полностью правильным:

try(Stream<Path> dirStream = Files.list(dirPath)) {
    dirStream.flatMap(filePath -> {
        try { return Files.lines(filePath);}
        catch(IOException ex) { throw new UncheckedIOException(ex); }
    }).forEach(line -> { });
}