从进程打印Java InputStream

7

更新:我找到了为什么这可能不起作用的关键部分!我使用了System.setOut(out);其中out是一个特殊的PrintStream,用于将信息输出到JTextArea中

这是代码,但我遇到的问题是只有当我结束进程时才会打印出信息。

public Constructor() {
    main();
}

private void main() {
    btnStart.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            try {
                ProcessBuilder builder = new ProcessBuilder("java", textFieldMemory.getText(), "-jar", myJar);
                Process process = builder.start();
                InputStream inputStream = process.getInputStream();
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream), 1);
                String line;
                while ((line = bufferedReader.readLine()) != null) {
                    System.out.println(line);
                }
                inputStream.close();
                bufferedReader.close();
            } catch (IOException ioe) {
                ioe.printStackTrace();
            }
        }
    });
} 

当前输出:

Line 1
Line 2
Line 3
Line 4
Line 5

这是正确的输出结果,但在我结束进程时,它只被打印成一个大块。

有人知道问题出在哪里吗?如果知道,能否帮助解释一下为什么会出现这种情况,提前感谢您。


2
输出已经被缓存在“BufferedReader”中,请尝试直接从“InputStream”中读取内容,看看是否有所不同。 - MadProgrammer
1
我同意@MadProgrammer的观点,问题可能出在缓冲区上。然而,我建议只需使用其两个参数构造函数BufferedReader缓冲区大小设置为1:new BufferedReader(new InputStreamReader(inputStream), 1) - DaoWen
使用 ByteArrayOutputStream 会更好吗?我完全不懂流,这让我很困扰 XD - Ciphor
@Perception 不,我没有使用 process.waitFor,也没有创建任何额外的线程。 - Ciphor
@Perception 添加了更多的内容。 - Ciphor
显示剩余8条评论
2个回答

11

在单独的线程中处理进程的输出流可能会有所帮助。在继续逻辑之前,您还想显式等待进程结束:

ProcessBuilder builder = new ProcessBuilder("java",
        textFieldMemory.getText(), "-jar", myJar);
final Process process = builder.start();
final Thread ioThread = new Thread() {
    @Override
    public void run() {
        try {
            final BufferedReader reader = new BufferedReader(
                    new InputStreamReader(process.getInputStream()));
            String line = null;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
            reader.close();
        } catch (final Exception e) {
            e.printStackTrace();
        }
    }
};
ioThread.start();

process.waitFor();

1
对我没用,就像我说的那样,我认为这与我将控制台输出重定向到JTextArea有关。 - Ciphor
是的,这是一个非常重要的细节。很可能你需要在每次写入时显式地刷新流,然后触发JTextArea的重绘,以便实时显示输出。 - Perception
如果您查看我的问题中的原始代码并将 inputStream.close(); 移动到 while 循环中,我发现了一些有趣的东西:我成功地在 JTextArea 中打印出了第一行,然后流被显然关闭了。 - Ciphor
好的,close 做了一个 flush,所以尝试使用 flush 代替 close,看看它如何影响程序。 - Perception
很遗憾,InputStream没有flush方法:/ - Ciphor
抱歉,我误解了你的写法。我的意思是在你的自定义输出流上进行刷新,而不是在输入上。 - Perception

4
基本上,从目前所了解到的有限信息来看,听起来您正在事件分派线程中执行进程并读取InputStream
任何阻塞EDT的事情都会阻止其处理重绘请求,相反,您应该使用像SwingWorker这样的东西,它具有从EDT内部更新UI的功能。
请参考Swing中的并发以获取更多详细信息。

enter image description here

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class PBDemo {

    public static void main(String[] args) throws Exception {
        new PBDemo();
    }

    public PBDemo() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
            setLayout(new BorderLayout());
            JTextArea ta = new JTextArea();
            add(new JScrollPane(ta));

            new ProcessWorker(ta).execute();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }
    }

    public interface Consumer {
        public void consume(String value);            
    }

    public class ProcessWorker extends SwingWorker<Integer, String> implements Consumer {

        private JTextArea textArea;

        public ProcessWorker(JTextArea textArea) {
            this.textArea = textArea;
        }

        @Override
        protected void process(List<String> chunks) {
            for (String value : chunks) {
                textArea.append(value);
            }
        }

        @Override
        protected Integer doInBackground() throws Exception {
            // Forced delay to allow the screen to update
            Thread.sleep(5000);
            publish("Starting...\n");
            int exitCode = 0;
            ProcessBuilder pb = new ProcessBuilder("java.exe", "-jar", "HelloWorld.jar");
            pb.directory(new File("C:\\DevWork\\personal\\java\\projects\\wip\\StackOverflow\\HelloWorld\\dist"));
            pb.redirectError();
            try {
                Process pro = pb.start();
                InputConsumer ic = new InputConsumer(pro.getInputStream(), this);
                System.out.println("...Waiting");
                exitCode = pro.waitFor();

                ic.join();

                System.out.println("Process exited with " + exitCode + "\n");

            } catch (Exception e) {
                System.out.println("sorry" + e);
            }
            publish("Process exited with " + exitCode);
            return exitCode;
        }

        @Override
        public void consume(String value) {
            publish(value);
        }
    }

    public static class InputConsumer extends Thread {

        private InputStream is;
        private Consumer consumer;

        public InputConsumer(InputStream is, Consumer consumer) {
            this.is = is;
            this.consumer = consumer;
            start();
        }

        @Override
        public void run() {
            try {
                int in = -1;
                while ((in = is.read()) != -1) {
//                    System.out.print((char) in);
                    consumer.consume(Character.toString((char)in));
                }
            } catch (IOException exp) {
                exp.printStackTrace();
            }
        }
    }
}

对我没用,就像我说的那样,我认为这与我将控制台输出重定向到 JTextArea 有关。 - Ciphor
没有提到 JTextArea?你是在 EDT 中执行该进程吗? - MadProgrammer
抱歉,我意识到这是导致问题的关键部分,就像我之前评论的那样。而且很抱歉,我们在我的课程中还没有涵盖线程。><" - Ciphor
代码借用自此答案,针对类似问题。非常感谢! - Hovercraft Full Of Eels
@HovercraftFullOfEels 我什么都不知道,我只是“借鉴”一切 ;) - MadProgrammer

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