Java8 Files.lines()使用問題

Java8的標準庫中增加了一個新的方法:

1

2

3

4

public final class Files

{

    public static Stream<String> lines(Path path) throws IOException

}

方法描述文檔

這個方法很簡單,從path對應的文件中讀取所有內容,並按行分割,返回一個 Stream<String>。

使用這個方法可以很容易的從一個文件中讀取連續的某幾行內容。如:

1

2

3

4

5

try{

    return Files.lines(Paths.get(file)).skip(start).limit(limit).collect(Collectors.toList());

}catch(IOExceptione){

    logger.error("get content from {} error,{}", file, e.getMessage());

}

我在系統中用上面這段代碼從/proc/stat中定時讀取系統狀態。一切看着都很完美。
但是!
當系統運行了一天之後,系統突然不可用了。。。打開日誌,滿屏幕的這個錯誤:

1

/proc/stat: Too many open files

毫無疑問, Files.lines()這個方法有問題,沒有關閉打開的文件。

仔細看了一下官方文檔,裏面有這麼一句:

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.

啥意思呢?就是說如果需要週期性的讀取文件,需要使用 try-with-resources語句來保證stream的close方法被調用,從而關閉打開的文件。
就像下面這樣:

1

2

3

4

5

try(Stream<String> stream = Files.lines(Paths.get(file))){

    return stream.skip(start).limit(limit).collect(Collectors.toList());

} catch (IOException e){

    logger.error("get content from{} error,{}",file, e.getMessage());

}

改用上面這個方式之後,一切安好。。。

爲啥非要這樣寫呢?
首先需要了解Java的 try-with-resources語句,參考文檔
try-with-resources語句可以自動調用資源的close方法。因此,上面那個正確的調用方式等價於下面這種:

1

2

3

4

5

6

7

8

Stream<String> stream = Files.lines(Paths.get(file));

try {

    return stream.skip(start).limit(limit).collect(Collectors.toList());

} catch (IOException e){

    logger.error("get content from{} error,{}",file, e.getMessage());

} finally {

    stream.close();

}

重點在finally塊裏,調用了Stream的close方法。Stream的close方法的作用在其註釋裏面有說明:

Closes this stream, causing all close handlers for this stream pipeline to be called.

Stream的close方法會調用所有 close handlers。
Stream還有一個onClose方法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

/**

* Returns an equivalent stream with an additional close handler.  Close

* handlers are run when the {@link #close()} method

* is called on the stream, and are executed in the order they were

* added.  All close handlers are run, even if earlier close handlers throw

* exceptions.  If any close handler throws an exception, the first

* exception thrown will be relayed to the caller of {@code close()}, with

* any remaining exceptions added to that exception as suppressed exceptions

* (unless one of the remaining exceptions is the same exception as the

* first exception, since an exception cannot suppress itself.)  May

* return itself.

*

* <p>This is an <a href="package-summary.html#StreamOps">intermediate

* operation</a>.

*

* @param closeHandler A task to execute when the stream is closed

* @return a stream with a handler that is run if the stream is closed

*/

S onClose(Runnable closeHandler);

註釋比較長,總結一下就是給Stream添加一個 close handler。Stream的close方法調用的 close handlers正是通過這個方法添加的。

那麼問題來了, Files.lines()返回的Stream有添加 close handler麼?
打開 Files.lines()的代碼:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

public static Stream<String> lines(Path path, Charset cs) throws IOException {

    BufferedReader br = Files.newBufferedReader(path, cs);

    try {

        return br.lines().onClose(asUncheckedRunnable(br));

    } catch (Error|RuntimeException e) {

        try {

            br.close();

        } catch (IOException ex) {

            try {

                e.addSuppressed(ex);

            } catch (Throwable ignore) {}

        }

        throw e;

    }

}

重點是這行 return br.lines().onClose(asUncheckedRunnable(br));。
BufferedReader.lines()方法返回的也是一個 Stream<String>。然後,調用了這個Stream的 onClose方法設置了一個close handler
打開 asUncheckedRunnable的代碼:

1

2

3

4

5

6

7

8

9

private static Runnable asUncheckedRunnable(Closeable c) {

    return () -> {

        try {

            c.close();

        } catch (IOException e) {

            throw new UncheckedIOException(e);

        }

    };

}

破案了~

Files.lines()方法在其返回的Stream裏面添加了一個close handler,在close handler裏面關閉其打開的文件。所以,必須調用其返回的Stream的close方法來保證關閉文件。

這裏面還有一個問題:

collect(Collectors.toList())方法是一個termianl操作,把Stream轉成了List,難道 Stream.collect()裏面沒有調用close方法麼?

答案是確實沒有。。。

Stream.collect()的代碼比較複雜,各位可以自行查看,這裏就不分析了。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章