BufferedReader 不会读取最后一行输入

BufferedReader will not read final line of input

我现在遇到这个问题正在解决一个问题(练习 java)。问题是要确保所提供输入的括号顺序正确(更多信息在 link: http://www.codeabbey.com/index/task_view/matching-brackets 中)。我面临的问题是我的 bufferedReader 不会读取我的最后一行输入。它进入了最后一个循环,但在阅读之前似乎 "pause"。我能让它工作的唯一方法是如果我按回车键,然后程序继续最后一次通过 input.readLine() 并打印出我的字符串。这是我的代码:

public static void main(String[] args)
{

    try
    {
    BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
    System.out.println("input data:");
    //First line is read to take in the number of lines for input will follow
    int data = Integer.parseInt(input.readLine());

    int i = 0;

    while(i < data)
    {
    //temp string builder to hold the wanted characters
    StringBuilder stringy = new StringBuilder();
    String line = input.readLine();
    //temp string builder holding the entire line
    StringBuilder sb = new StringBuilder(line);
        for(int j = 0; j < sb.length(); j++)
        {
            //loops through string builder & adds the wanted characters to stringy
            switch(sb.charAt(j)){
            case '(' : stringy.append(sb.charAt(j));
            break;

            case ')' : stringy.append(sb.charAt(j));
            break;

            case ']' : stringy.append(sb.charAt(j));
            break;

            case '[' : stringy.append(sb.charAt(j));
            break;

            case '{' : stringy.append(sb.charAt(j));
            break;

            case '}' : stringy.append(sb.charAt(j));
            break;
            }
        } 
        System.out.println(stringy);

        i++;
    }
    }catch(IOException x)
    {
        x.printStackTrace();
    }

}

对不起,如果我不够清楚。我试图在网上阅读此内容,但人们似乎没有这个特定问题。我不确定如何,但似乎在我最后一行输入之前添加了额外的一行或其他内容。感谢您的帮助,我真的很感激。

编辑: 抱歉,我知道我没有为程序提供任何输入数据。这是:

4
(a+[b*c]-{d/3}) 
(a + [b * c) - 17]
((a * x) + [b] * y) + c
auf(zlo)men [gy<psy>] four{s}

只需将上面的内容复制并粘贴到您的程序中,您就会看到问题

总结

BufferedReader#readLine 方法会阻止 I/O 读取输入,直到它在输入中找到行终止符。输入的最后一行与所有其他行不同,因为它末尾没有行终止符。在终端中按 enter 会添加所需的行终止符,但作为副作用,它也会导致终端将其光标向下移动一行,从而导致您注意到 "space in between" 行。这并不是真正的意外行为,也不是您代码中的错误,但您可以 "fix" 通过确保在输入的最后一行末尾有一个行终止符来实现它。

详情

我可以重现您描述的行为。我编译代码,运行 它,然后粘贴到示例输入中。就像你说的,它挂在最后一行。然后我按回车键,这使它继续进行,但为什么这是必要的?在最后的结果之前还有意想不到的差距。

> java Test
input data:
4
(a+[b*c]-{d/3}) 
(a + [b * c) - 17]
((a * x) + [b] * y) + c
auf(zlo)men [gy<psy>] four{s}([]{})
([)]
(()[])

()[]{}

我还注意到另一个你没有提到的问题。在上面示例的第七行(以 "auf" 开头的行)中,结果立即打印出来,没有移动到新行。

嘿,这是怎么回事?好吧,让我们尝试应用一些调试技术。 jstack is a tool that ships with the JDK that lets you attach to a running JVM and dump the state of its threads of execution. It's a great way to get a glimpse at what your code is really doing when it runs. Let's try running jstack right when the process appears to hang. First though, I need to identify the process ID of the JVM. Let's use jps 做到这一点。

> jps
83518 Test

> jstack 83518
2015-12-24 21:25:17
Full thread dump Java HotSpot(TM) 64-Bit Server VM (24.65-b04 mixed mode):

...

"main" prio=5 tid=0x00007fbba2001000 nid=0x1903 runnable [0x000000010a560000]
   java.lang.Thread.State: RUNNABLE
    at java.io.FileInputStream.readBytes(Native Method)
    at java.io.FileInputStream.read(FileInputStream.java:272)
    at java.io.BufferedInputStream.read1(BufferedInputStream.java:273)
    at java.io.BufferedInputStream.read(BufferedInputStream.java:334)
    - locked <0x00000007aaa9a5f0> (a java.io.BufferedInputStream)
    at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:283)
    at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:325)
    at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:177)
    - locked <0x00000007aab2ad88> (a java.io.InputStreamReader)
    at java.io.InputStreamReader.read(InputStreamReader.java:184)
    at java.io.BufferedReader.fill(BufferedReader.java:154)
    at java.io.BufferedReader.readLine(BufferedReader.java:317)
    - locked <0x00000007aab2ad88> (a java.io.InputStreamReader)
    at java.io.BufferedReader.readLine(BufferedReader.java:382)
    at Test.main(Test.java:22)

...

我已经修剪了 jstack 的输出以仅显示相关的主线程。那很有意思。我可以看到主入口点:Test.main。我可以看到对 BufferedReader#readLine 的调用。在一系列其他方法调用之后,它到达 FileInputStream#read。如果我 运行 jstack 多次,我会一直看到同样的东西。这意味着执行停留在试图从输入中读取字节的方法中。这很奇怪。那怎么解释呢?也许 BufferedReader#readLine 的 JavaDocs 包含对行为的一些解释。

Reads a line of text. A line is considered to be terminated by any one of a line feed ('\n'), a carriage return ('\r'), or a carriage return followed immediately by a linefeed.

至此,让我们尝试形成一个假设。什么可能导致进程在尝试读取数据时卡住? JavaDocs 说一行被认为是由特定字符终止的。也许我们的最后一行输入不包含行终止符。

为了证实这一理论,让我们尝试查看一个 hexadecimal dump of our input. I usually use the xxd 命令行工具来执行此操作。这是结果。

0000000: 340a 2861 2b5b 622a 635d 2d7b 642f 337d  4.(a+[b*c]-{d/3}
0000010: 2920 0a28 6120 2b20 5b62 202a 2063 2920  ) .(a + [b * c) 
0000020: 2d20 3137 5d0a 2828 6120 2a20 7829 202b  - 17].((a * x) +
0000030: 205b 625d 202a 2079 2920 2b20 630a 6175   [b] * y) + c.au
0000040: 6628 7a6c 6f29 6d65 6e20 5b67 793c 7073  f(zlo)men [gy<ps
0000050: 793e 5d20 666f 7572 7b73 7d              y>] four{s}

我正在测试 Mac,它使用单个控制字符 LF(换行符)来指示新行。这在其他平台上可能有所不同。最值得注意的是,Windows 使用 2 个控制字符的序列:CR/LF(回车 return/line 进给)。根据Unicode standard, the ASCII code for LF is 0a in hex representation. This is shown in the Basic Latin(ASCII)码表。回到我们的十六进制转储,我们可以看到 0a 字符出现了 4 次,请注意最后一行的末尾没有 0a 字符。

这开始看起来像是一个很有前途的理论。我们还能做些什么来验证它?感谢 OpenJDK, we can view the source code implementation of many of the common JDK classes, including BufferedReader. Let's try looking at the implementation of BufferedReader#readLine。这是一个非常棘手的循环,但底线是它在名为 eol 的变量中跟踪 "end of line",这就是导致它停止填充 fill 中的内部缓冲区的条件方法,而不是 return 一个字符串给调用者。

charLoop:
    for (i = nextChar; i < nChars; i++) {
        c = cb[i];
        if ((c == '\n') || (c == '\r')) {
            eol = true;
            break charLoop;
        }
    }

    startChar = nextChar;
    nextChar = i;

    if (eol) {
        String str;
        if (s == null) {
            str = new String(cb, startChar, i - startChar);
        } else {
            s.append(cb, startChar, i - startChar);
            str = s.toString();
        }
        nextChar++;
        if (c == '\r') {
            skipLF = true;
        }
        return str;
    }

OK,我信服了!让我们通过重复我们的原始测试来检验假设,但这次让我们确保我们在最后一行的末尾有一个行终止符。复制粘贴输入的那个版本,我现在看到了这些结果。

> java Test
input data:
4
(a+[b*c]-{d/3}) 
(a + [b * c) - 17]
((a * x) + [b] * y) + c
auf(zlo)men [gy<psy>] four{s}
([]{})
([)]
(()[])
()[]{}

更像了!