Java:can';除非手动刷新,否则无法从进程获取标准数据

Java:can';除非手动刷新,否则无法从进程获取标准数据,java,process,io,buffer,Java,Process,Io,Buffer,我正在用Java为命令行程序编写一个终端包装器,并使用ProcessBuilder生成子流程。为了向子流程发送击键,我只需将e.getKeyChar()从GUI直接写入proc.getOutputStream()提供的OutputStream。为了从子流程接收输出,我基本上有一个while循环,它读取子流程的stdout: while ((b = br.read()) != -1) { System.out.println("Read "+b); bb[0] = (byte) b

我正在用Java为命令行程序编写一个终端包装器,并使用ProcessBuilder生成子流程。为了向子流程发送击键,我只需将
e.getKeyChar()
从GUI直接写入
proc.getOutputStream()
提供的
OutputStream
。为了从子流程接收输出,我基本上有一个while循环,它读取子流程的
stdout

while ((b = br.read()) != -1) {
    System.out.println("Read "+b);
    bb[0] = (byte) b;
    // call an event listener with the read byte
    listener.dataReceived(bb);
}
只有当我立即刷新两端的输出时,才有效。也就是说,我必须刷新每个用户输入,并且子流程必须刷新自己的
stdout
,以使事情发生。否则,
read()。如何使I/O正常运行

终端子流程示例:

#include <stdio.h>

int main() {
    char c;
    while((c = getchar()) != -1) {
        printf("Got: %d\n", c);
        // doesn't work in my Java program if the next line isn't present
        fflush(stdout);
    }
    return 0;
}
#包括
int main(){
字符c;
而((c=getchar())!=-1){
printf(“获取:%d\n”,c);
//如果下一行不存在,则在我的Java程序中不起作用
fflush(stdout);
}
返回0;
}

我正在使用Sun Java 6运行Ubuntu 10.10。

在数据写入磁盘之前,您无法从文件中读取数据。 在将数据放入管道/套接字的缓冲区之前,无法从套接字或管道读取数据

java程序无法控制外部进程刷新其输出并将数据写入磁盘/管道缓冲区/套接字缓冲区的时间(*)。您完全受外部程序缓冲行为的支配。这在每种操作系统和每种编程语言中都是如此

每个网络程序员都必须处理这个问题,所以只要处理好它就行了


(*)-有时,一些程序(例如,
cat
)有选项(
-u
)指示程序使用无缓冲输出。否则,您将受到

的摆布,您没有从事件调度线程运行I/O读取循环,是吗


您应该在单独的线程中运行从子进程读取的I/O(如果您还没有这样做)。按GUI键立即刷新到子进程可能是最好的;除非您想支持某种“一次读取一整行”的功能。

许多运行时库(例如,我知道libc可以做到这一点,如果其他库也这样做,也不会感到惊讶)默认情况下会缓冲它们的输出,除非输出是到终端。这极大地提高了处理多行数据(例如,在普通管道中)时的数据处理效率,但当只有少量信息时,会造成很大的伤害。如果您可以访问子流程的源代码,那么最好通过关闭缓冲或添加刷新来更新代码


但这并不总是可能的,尤其是在处理第三方代码时。在这种情况下,我知道的最好的其他修复方法是使用类似于欺骗子流程的工具。在内部,Expect知道如何伪装成终端(在Unix上使用PTY,在Windows上使用可怕的黑客),从而诱使其他程序关闭(或至少减少)它们的缓冲。Expect有一个脚本——unbuffer——专门用于这种用途。(一般来说,它可以做的不仅仅是处理不规则的缓冲,但无论如何它是最好的解决办法。)

对,但对于我的Java程序,I/O永远不会被发送,而如果我在终端中手动运行上述子进程,I/O几乎是即时的。是的,我们知道我们必须处理它。问题是怎么做。这就是我目前正在做的。我有一个从子进程读取的独立线程(因为
read()
块),它通知主线程中的事件处理程序。击键会立即发送到子流程并刷新。