BufferedReader.readLine阻塞了我的程序,但是BufferedReader.read()正常读取。

13

我有如下代码片段:

Process proc = Runtime.getRuntime().exec(command);
BufferedReader br = new BufferedReader(new InputStreamReader(proc.getErrorStream()));
String line = br.readLine();

现在在上面的代码中,我确定进程总是会有一行输入,所以我没有使用任何while循环或任何空值检查。问题在于readLine阻塞了。我知道的一个原因是,流没有数据可读,因此readLine一直在等待。为了验证这一点,我删除了readLine并使用了read()函数,如下所示:

Process proc = Runtime.getRuntime().exec( command );
BufferedReader br = new BufferedReader(new InputStreamReader(proc.getErrorStream()));
int a;
while((a=br.read())!=-1){
    char ch = (char) a;
    if(ch == '\n')
        System.out.print("New line "+ch);
    if(ch == '\r')
        System.out.print("Carriage return "+ch);
    System.out.print(ch);
}

令我惊讶的是,这段代码运行成功并打印出了“New line”和“Carriage return”的消息。现在我正在想为什么readLine会阻塞呢?数据是可用的,并以换行符结尾。还有什么其他原因吗?

注意:以上内容有时只能工作一次!可能是15次中的一次。
注意:我也尝试使用ProcessBuilder,但结果相同。

更新: 所以我切换到ProcessBuilder,然后重定向了errorStream,现在当我执行process.getInputStream时可以同时获得输入流和错误流,这个方法很好用。以下是摘录:

ProcessBuilder pb = new ProcessBuilder(command.split(" "));
pb..redirectErrorStream(true);
Process proc = pb.start();
BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream()));
String line = br.readLine();
//Now I get both input and error stream.

我想将错误流与输入流区分开来,但是使用这种方法它们都混在一起了!有什么建议吗?


我正在检查错误流,所以文件大小是否重要? - Destructor
1
可能可以。如果能够以这种方式更频繁地刷新缓冲区来更改被调用的进程,那么它可能会有所帮助。 或者,你需要在进程 - 也许 - 仍在运行时读取错误输出吗?如果不需要,等待进程结束后再开始读取。 - Gren
1
等待进程完成是毫无意义的,甚至可能会适得其反。直接阻塞即可。如果您想要输出和错误流分开,请不要合并它们,并在单独的线程中分别读取它们。 - user207421
你试过这个代码吗?while (line != null) { line = br.readLine(); } 只是出于好奇。也许在你想读取所需行之前,会有一个简单的换行符? - Martin Frank
你的意思是:while ((line=br.readLine())!=null) { do something},但即使这样也不起作用,因为我们读取一行并检查它是否为空,如果是,则执行某些操作。问题在于readLine永远不会返回。 - Destructor
显示剩余17条评论
4个回答

2

您可以使用线程来避免它。

例如,一个从属线程将负责读取。这不会阻止程序的进展。


我希望程序在读取完所有数据之前等待。但问题在于readLine永远不会返回!基本上,如果在一定时间后readLine返回,那么我就没问题了。 - Destructor
从我启动的进程的错误流中,该进程调用带参数的可执行文件。 - Destructor
请问您想要实现什么目标?我只是在问一下,因为可能有其他更简单的方法来完成。 - Arvind Bishnoi
这是一个相当老的问题,我现在不太记得上下文了。就我所知,我想使用readLine读取可执行文件的错误流,但它一直处于阻塞状态,也就是说,除非可执行文件被杀死,否则我无法从readLine中获取控制权。 使用read()函数而不是readLine()函数可以解决这个问题。最终,我使用了ProcessBuilder.redirectErrorStream(true),虽然我必须对我的代码进行更改,因为错误和输入流现在作为一个流返回。 可执行文件的错误流数据很少,并且没有刷新,因此readLine会一直处于阻塞状态。 - Destructor
那是个好消息,你能够解决它。感谢回复。XD - Arvind Bishnoi

1
我认为问题不在于标准误差正在阻止,而在于标准输出正在阻塞调用应用程序导致其阻塞。标准输出通常被缓冲。如果您调用的进程向标准输出写入的内容少于缓冲区大小,则一切正常,并且可以达到编写标准错误的代码点。如果进程填充了缓冲区,它尝试写入标准输出将会被阻塞,并且永远无法达到编写标准错误的地方。这可能是您偶尔看到它工作的原因-有时标准输出不会填满缓冲区。这也可能是为什么经过很长时间后它能够工作的原因:最终对标准输出的写入超时。作为演示,在我的Windows 8机器上,这个简单的进程总是像您描述的那样被阻塞:
public class Proc {

    public static void main(String[] args) {
        for(int i=0;i<1000;i++) {
            System.out.print("More data ");
        }
        System.out.println();
        System.err.println("An error line");
    }
}

0
为了避免获取错误流和输入流合并,只需删除该行:pb.redirectErrorStream(true); 因为在Java文档中已经说过:

如果此属性为true,则由此对象的start()方法启动的任何子进程生成的任何错误输出将与标准输出合并,以便可以使用Process.getInputStream()方法读取两者。这使得更容易将错误消息与相应的输出相关联。初始值为false。

通过调用pb.redirectErrorStream(true);,您正在合并两个输出。

0

getErrorStream方法的说明如下: 返回与子进程的错误输出连接的输入流。该流从由此Process对象表示的进程的错误输出中获取数据。 如果使用ProcessBuilder.redirectError或ProcessBuilder.redirectErrorStream重定向了子进程的标准错误,则此方法将返回空输入流。

而ReadLine方法的说明如下: 读取一行文本。一行被认为是以换行符('\n')、回车符('\r')或回车符后紧接着一个换行符终止的。

根据API提供的解释,readline方法无限期地等待换行符或回车符,而processbuilder返回NULL,这就是为什么readLine会因此终止的原因。


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