Java BufferedReader.readLine() 不等待 EOL 吗?

4

如果我漏掉了一些显而易见的东西...请看一下这段代码片段:

String readString;
String writeString = "O hai world.";
BufferedReader br = new BufferedReader(
    new InputStreamReader( 
        new ByteArrayInputStream(writeString.getBytes()),
        "UTF-8"),
    1024);
readString = br.readLine();
System.out.println("readString: " + readString);

我期望这将打印"readString: null",因为我认为 BufferedReader 将在检测到有效的 EOL 之前遇到 EOF,但实际上它打印出 "readString: O hai world"。这似乎与 BufferedReader 的 Javadocs 所说的 readLine() 做什么相反:
“读取文本行。行被认为是以换行符 ('\n')、回车符 ('\r') 或回车符后面紧跟着换行符中的任何一个字符终止的。
返回:包含行内容(不包括任何行终止符)的字符串;如果已经到达流的末尾,则为null。”
我看不出来为什么我的字符串会被重新解释为以 '\n' 和/或 '\r' 结束...有人能给我点提示吗?谢谢!
编辑: 为了提供一些背景,我正在尝试编写 JUnit 测试来验证我编写的 Reader 类,该类旨在在 System.in 上进行读取。使用 ByteArrayInputStreams 似乎是模拟 System.in 的合理方法(请参见 this relevant SO post)。
当我的Reader捕获一行时,它目前依赖于BufferedReader.readLine()。对于我的目的,我的Reader的所有行都必须以'\n'或'\r'结尾;在没有EOL的情况下遇到EOF不应该解析为有效行。所以我现在的问题是这样的(当我有时间时,我会试着更详细地测试这些问题,但希望你们聪明的人能帮助我):
  • BufferedReader.readLine()是否存在问题/文档错误?或者ByteArrayInputStream在其字节数组耗尽时返回了错误内容?
  • 这种测试Reader的方法是否存在问题?当使用System.in进行readLine()时,我应该期望它能正常工作吗?我倾向于认为答案是肯定的。
  • 有更好的方法模拟System.in进行单元测试吗?
  • 如果我需要严格区分InputStream中的'\n'和'\r',那么自己编写readLine()方法是否更好?我会非常惊讶如果是这种情况。
谢谢!

你给了它一个字符串,它给你返回了一个字符串。有什么不喜欢的呢?;) - paulsm4
readLine() 不应该阻塞等待 EOL 字符吗?我是不是在某个地方神奇地插入了一个? - jasterm007
+1 因为阅读文档后,我同意你的观点。readline() 方法还应该提到行终止符可能是 EOF。 - goat
这是一个有趣的问题。我想知道如果从具有该内容的文件中读取,它是否会有相同的行为。 - Austin Heerwagen
谢谢@rambocoder和austin-heerwagen。我猜我会尝试深入挖掘,看看当它的字节数组耗尽时ByteArrayInputStream是否返回EOL,或者readLine()实际上是损坏/文档错误的。我会让问题保持开放状态,直到我找到确凿的东西,或者如果有人想出了什么。 - jasterm007
@njhwang:你在原帖里真的应该提到这一点:“我试图测试一下,如果我只给它一部分行内容,然后延迟一下再把整个行(包括结束符)给它,我的 Reader 是否能够成功读取一整行。”请看下面我的回复。 - paulsm4
5个回答

3
ByteArrayInputStream在耗尽时不会返回EOL,它只会返回-1,这可能被视为EOF。
问题在于BufferedReader会缓冲从输入流中读取的所有内容,如果在任何EOL字符出现之前遇到EOF(-1),则返回缓冲到该点的字符串。
因此,如果你想非常严格地说,就可以说readLine()根据当前文档要求是错误的,或者如果这是预期的行为,则应以不同的方式记录它。
在我看来,考虑到流中的最后一行不必以EOL字符结尾(EOF足够),因此readLine的当前行为是正确的,即读取了一行,因为遇到了EOF。所以,文档应该改变。

谢谢。你有没有建议如何对依赖于System.in输入的内容执行单元测试?我试图测试我的Reader是否能够成功读取一行,如果我将其部分行提供给它,延迟一段时间,然后再给它剩余的行,包括EOL。否则,我会发布一个单独的问题。 - jasterm007
如果我理解正确,您想模拟一个在System.in上写东西时暂停然后继续写的用户。 我认为,您唯一能够模拟这种行为的方法是扩展ByteArrayInputStream并重写read方法,以便偶尔(或至少一次)在返回下一个字节之前休眠。 - Razvan

1
我想象这个阻塞会在你从一个真正的流(例如网络套接字)中读取时发生。但由于底层输入是一个数组,读取器知道数据的真正结束已经到达,因此阻塞是不必要的,因为没有新数据即将到来。因此,阻塞将是错误的行动。在实际读取数据的地方返回空值也是错误的做法。

我同意,当ByteArrayInputStream返回EOF时阻塞是错误的行为。但我不同意null是错误的返回值。 BufferedReader 的意义在于缓冲信息,直到可以返回它。 在这种情况下,readLine()应该缓冲“O hai world。”然后在没有遇到EOL时返回null,因为实际上没有读取一行。 - jasterm007
想象一个文本编辑器。即使文本文件没有尾随的EOL,当你打开文件时,仍然会看到最后一行文本。如果按照你所建议的方式实现readLine(),你将无法使用它来实现文本编辑器。在文档中添加EOF作为行终止条件,是我认为需要做的事情。 - Dmitry B.
嗯,这似乎是一个使用 BufferedReader.readLine() 读取文件中除最后一行之外的所有行的用例,然后它将返回 null,因为存在不完整的行,然后您可以使用您喜欢的 BufferedReader.read() 变体来完成剩下的部分。无论如何,感谢您确认您认为文档不反映现实。 - jasterm007

1

我相信您想要一个“机器人”来模拟按键进行测试:

这个类用于生成本地系统输入事件,以便进行测试自动化、自运行演示和其他需要控制鼠标和键盘的应用程序。Robot 的主要目的是促进 Java 平台实现的自动化测试。

这里有一篇进一步讨论它的文章:


0
你期望在这个版本的代码中发生什么?
String readString;
String writeString = "O\nhai\nworld.";
BufferedReader br = new BufferedReader(
    new InputStreamReader( 
        new ByteArrayInputStream(writeString.getBytes()),
        "UTF-8"),
    1024);
while (true) {
    readString = br.readLine();
    if (readString == null) break;
    System.out.println("readString: " + readString);
}

  1. 打印“O”
  2. 打印“hai”
  3. 中断。但实际上,它打印了“O”,然后是“hai”,最后是“world”。
- jasterm007
如果使用"3) break",那么你将永远得不到"world"。这些数据将永久消失。readLine()的作用只是将数据分解成好的块或行。但是在获取null之前,您仍应该获得所有数据。有道理吗?是的,文档可能需要更清晰明了。 - user1541656
我仍然不同意,但似乎我在这里是少数派。如果readLine()按照文档所说的那样工作,那么它将返回null,我希望BufferedReader仍然有数据缓冲。正如我对dmitry-beransky所说的,我觉得此时你可以使用常规的read()方法获取其余的数据,直到遇到EOF。对于许多用例来说可能不方便,但对于我正在尝试构建模拟延迟System.in输入的单元测试来说非常麻烦。 - jasterm007
@njhwang - 你有没有考虑过文档之所以没有“说出来”...是因为他们假设你会简单地“知道”它?假设我正在为你的汽车编写手册。步骤1:将钥匙插入点火开关中。我的手册是否“有问题”,因为我没有先说“打开门并进入”?我想我们可以辩论一下... - paulsm4

0

现在唯一的替代方案就是丢弃最后不完整的行。这并不理想。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接