Java 8 pitfall – Beware of Files.lines()
There’s a really nice new feature in Java8 which allows you to get a stream of Strings from a file in a one liner.
List lines = Files.lines(path).collect(Collectors.toList());
You can manipulate the Stream as you would with any other Stream for example you might want to filter() or map() or limit() or skip() etc. I started using this all over my code until I was hit with this exception,
Caused by: java.nio.file.FileSystemException: /tmp/date.txt: Too many open files in system at sun.nio.fs.UnixException.translateToIOException(UnixException.java:91) at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:102) at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:107) at sun.nio.fs.UnixFileSystemProvider.newByteChannel(UnixFileSystemProvider.java:214) at java.nio.file.Files.newByteChannel(Files.java:361) at java.nio.file.Files.newByteChannel(Files.java:407) at java.nio.file.spi.FileSystemProvider.newInputStream(FileSystemProvider.java:384) at java.nio.file.Files.newInputStream(Files.java:152) at java.nio.file.Files.newBufferedReader(Files.java:2784) at java.nio.file.Files.lines(Files.java:3744) at java.nio.file.Files.lines(Files.java:3785)
For some reason I had too many open files! Odd, doesn’t Files.lines() close the file?
See code below ( run3()
) where I’ve created reproduced the issue:
package utility; import java.io.BufferedReader; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Date; import java.util.stream.Stream; public class Test2 { public static void main(String[] args) throws IOException{ int times = 100_000; Path path = Paths.get("/tmp", "date.txt"); Test2 t2 = new Test2(); t2.setDate(path); for (int i = 0; i < times; i++) { t2.run1(path); } for (int i = 0; i < times; i++) { t2.run2(path); } for (int i = 0; i < times; i++) { t2.run3(path); //throws exception too many files open } System.out.println("finished"); } public String run1(Path path){ try(BufferedReader br = new BufferedReader(new FileReader(path.toFile()))){ return br.readLine(); } catch (IOException e) { throw new AssertionError(e); } } public String run2(Path path){ try(Stream<String> stream = Files.lines(path)) { return stream.findFirst().get(); } catch (IOException e) { throw new AssertionError(e); } } public String run3(Path path) throws IOException{ return Files.lines(path).findFirst().get(); } public void setDate(Path path) { try (FileWriter writer = new FileWriter(path.toFile())){ writer.write(new Date().toString()); writer.flush(); } catch (IOException e) { throw new AssertionError(e); } } }
My code looked something like run3()
which produced the exception. I proved this by running the unix command lsof
(lists open files) and noticing many many instances of date.txt open. To check that the problem was indeed with Files.lines()
I made sure that the code ran with run1()
using a BufferedReader
, which it did. By reading through the source code forFiles
I realised that the Stream need to be created in an auto closable. When I implemented that in run2()
the code ran fine again.
In my opinion I don’t think that this is not particularly intuitive. It really spoils the one liner when you have to use the auto closable. I guess that the code does need a signal as to when to close the file but somehow it would be nice if that was hidden from us. At the very least it should be highlighted in the JavaDoc which it is not :-)
Reference: | Java 8 pitfall – Beware of Files.lines() from our JCG partner Daniel Shaya at the Rational Java blog. |
I’ve the same, and will add autoCloseable – for now I have service restart every 24h :)
I’ve found in Stream javaDoc:
“Streams have a BaseStream.close() method and implement AutoCloseable, but nearly all stream instances do not actually need to be closed after use. Generally, only streams whose source is an IO channel (such as those returned by Files.lines(Path, Charset)) will require closing.”
So its not highlighted but its present somewhere.
Can’t reproduce on windows 7 machine and Java 1.8.0_31-b13
Don’t see any other way to have a stream, besides an ugly (non-deterministic) finalizer. You could have a one-liner that reads all lines in memory, but that isn’t better.
The java of the other overloaded Files@lines(Path, CharSet) did mention the stream encapsulate a Reader which needs to be closed.
“The returned stream encapsulates a Reader. If timely disposal of file system resources is required, the try-with-resources construct should be used to ensure that the stream’s close method is invoked after the stream operations are completed”
http://docs.oracle.com/javase/8/docs/api/java/nio/file/Files.html#lines-java.nio.file.Path-