构建 java.util.Scanner 的意外行为

Unexpected behavior constructing a java.util.Scanner

我有以下文件lines.txt

Line1
Line2
Line3

我正在使用扫描仪逐行解析此文件的内容。 我在 LinesReader.java

中有以下设置
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

class Line {
    Line(String content) {
        this.content = content;
    }
    public String content;

    public String toString() {
        return content;
    }
}

public class LinesReader {
    public static Line buildLine(InputStream is) {
        Scanner scanner = new Scanner(is);
        if (scanner.hasNextLine()) {
            return new Line(scanner.nextLine());
        }
        return null;
    }
    public static Line buildLine(Scanner scanner) {
        if (scanner.hasNextLine()) {
            return new Line(scanner.nextLine());
        }
        return null;
    }

    public static void main(String[] args) throws FileNotFoundException {
        List<Line> lines = new ArrayList<>();
        Line line = null;
        FileInputStream is = new FileInputStream("lines.txt");
        // buildLine(scanner) works as expected
        while ((line = buildLine(is)) != null) {
            lines.add(line);
        }

        System.err.println(lines);
    }
}

输出为

[Line1]

预期输出为

[Line1, Line2, Line3]

我了解 Scanner 实现了 AutoCloseable,但是根据 documentation 这只会申请一个 try-with-resources 构造而不是这里。另外,当我调试时,它说底层流​​是打开的。第二次调用 scanner.hasNextLine() 意外失败。

如果我在 main() 中构造一次扫描器,它会按预期工作。

我的java版本是1.8.0_275

作为对@Sweeper 的评论的回应,扫描仪似乎缓冲了比消耗的更多的东西,文档有点矛盾。

对于hasNextLine()

The scanner does not advance past any input.

对于nextLine()

Since this method continues to search through the input looking for a line separator, it may buffer all of the input searching for the line to skip if no line separators are present.

强调我的。

试试这个。

    ...
    FileInputStream is = new FileInputStream("lines.txt");
    while (true) {
        System.err.println(is.available());
        Scanner scanner = new Scanner(is);
        if (scanner.hasNextLine()) {
            lines.add(new Line(scanner.nextLine()));
        } else {
            break;
        }
    }

我得到了以下内容。

18
0    <---- FileInputStream is not avaliable for the 2nd Scanner
[Line1]

但是如果我移动这条线

    while (true) {
        FileInputStream is = new FileInputStream("lines.txt");
        System.err.println(is.available());
        ...
    }

循环不断打印出 18

hasNextLine()

的文档

The scanner does not advance past any input.

有点误导。它不会推进扫描器的内部缓冲区,这是显而易见的,但会读取几千字节的流。

在这种情况下,整个流被 hasNextLine() 使用。

个人认为这是Scanner实现的缺陷。 Scanner 的设计目的是为了方便和简单,而不是为了性能。将 InputStream 包装在 BufferedInputStream 中是明智的,并使使用更简单。

A Scanner 被缓冲,并且不能期望底层 (File)InputStream 不会比 nextLine 返回的内容读得更远。事实上,底层的 FileInputStream 可以推进到文件末尾。所以第一个 Scanner 实例可以让 FileInputStream 位于文件末尾。

从 java8 开始,使用 Path、Files、Stream 更容易。

Path path = Paths.get("lines.txt");
try (Stream<String> in = Files.lines(path, Charset.defaultCharset())) {
    List<Line> lines = in.map(Line::new)
            .collect(Collectors.toList());
    ...
}

上面也是自动关闭文件,try-with-resources语法。

新 类 代码更小。