ProcessBuilder的InputStream没有输入,直到外部进程关闭

5
在构建控制台应用程序的包装器时,我遇到了一个奇怪的问题:连接到外部进程输出(stdout)的输入流在外部进程退出之前是完全空白的 我的代码如下:
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;

public class Example{

    public static void main(String args[]) throws IOException{
        File executable = ...

        ProcessBuilder pb = new ProcessBuilder(executable.getCanonicalPath());

        pb.redirectErrorStream(true);

        Process p = pb.start();

        BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream(), Charset.forName("UTF-8")));

        String line;

        while((line = br.readLine()) != null){
            System.out.println(line);
        }
    }
}

我尝试了几种从输入流中读取的变体,但结果都一样。我尝试过:
````
CharBuffer charBuf = CharBuffer.allocate(1000);

InputStreamReader isr = new InputStreamReader(p.getInputStream(), Charset.forName("UTF-8"));

while(isr.read(charBuf) != -1){
    System.out.print(charBuf.flip().toString());
}

并且

byte[] buf = new byte[1000];
int r;

while((r = p.getInputStream().read(buf)) != -1){
    System.out.print(new String(buf, 0, r));
}

所有尝试都没有成功。

在某个地方,来自外部进程的输出被缓冲(无限期),我无法真正弄清楚在哪里。从命令行加载进程似乎很好,我可以看到输出立即出现。最奇怪的部分是外部进程的终止导致所有“缓冲”的输出一次性涌现出来(很多东西)。

不幸的是,我没有访问外部进程源代码的权限,但是考虑到当控制台中时它可以很好地写入stdout,因此在那里不应有任何区别(据我所知)。

欢迎任何想法。

编辑:

一个答案建议我重写输出和错误流的读取器以在单独的线程上运行。我的实际实现正在执行这项任务! 但问题仍然存在。上面发布的代码是我的实际代码的SSCCE,为了可读性而压缩了,实际代码涉及一个用于从InputStream读取的单独线程。

编辑2:

用户FoggyDay似乎提供了答案,该答案定义了在控制台和非控制台之间输出缓冲的行为如何更改。虽然检测到它们正在写入控制台的进程使用行缓冲(每个新行刷新一次缓冲区),但写入非控制台(它检测到不是控制台的所有内容)可能会完全缓冲(大小约为8K)。如果我让外部进程垃圾邮件(在for循环中使用8K的lorem ipsum),确实会出现输出。我现在的问题是如何使我的Java程序触发外部进程的行缓冲。


1
如果您不需要缓冲,为什么要使用 BufferedReader?我可能会改用 java.util.Scanner - Elliott Frisch
@ElliottFrisch 最初我想使用某种形式的缓冲,但是由于这并没有完全奏效,所以我开始使用 CharBuffer 等方法来尝试解决问题。我的最后一次尝试是直接读取原始的 InputStream,但仍然没有输出可见。鉴于 Scanner 是建立在原始 InputStream 之上的,我不知道它是否会奏效,但我还是会尝试一下。 - initramfs
我假设你已经尝试过将代码与其他可执行文件一起运行,并且在进程结束之前看到了输出结果?这个可执行文件有没有可能做了一些奇怪的事情,比如检查输出是否被重定向了? - Miserable Variable
为什么会出现这种情况,这个问题当然很难回答。例如,可能是因为应用程序不想被重定向 :) 如果您使用的是类Unix操作系统,则可以尝试另一种实现方式,通过 | while read line; do echo time: $line; done 进行管道传输,这可能是一个快速而简单的测试,可以显示它产生输出的频率。 - Miserable Variable
这篇文章是关于Perl的...但它提供了一些有用的洞察力,解释了你正在经历的原因和理由。请浏览此链接:http://perl.plover.com/FAQs/Buffering.html - FoggyDay
显示剩余5条评论
2个回答

1
对于您的问题“如何使我的Java程序触发外部进程的行缓冲”:
在Linux上,您可以使用“stdbuf”程序(coreutils软件包):stdbuf -oL your_program program_args 您只需要更改stdout,因为stderr默认情况下是未缓冲的。如果您有兴趣,setlinebuf的手册页面提供了其他背景信息:http://linux.die.net/man/3/setlinebuf

我的应用程序旨在跨平台使用,因此 Linux 特定的解决方案不够好。不过我会查看 stdbuf 的源代码,看看是否有什么可以做的。 - initramfs
尝试在Windows上运行此命令(Git自带stdbuf.exe),但似乎无效,我猜是JDK在缓冲? - teknopaul

-1
一些软件会检查它是否正在写入终端并切换到无缓冲输出的行为。这可能是在终端中运行程序的原因。将输出管道传输到cat并查看输出是否仍然立即出现。
另一个原因可能是程序在等待输入或其stdin关闭之前才执行某些操作,尽管这与迄今为止描述的症状不太匹配。

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