代码之家  ›  专栏  ›  技术社区  ›  Adder

Java读取进程输出无缓冲/实时

  •  2
  • Adder  · 技术社区  · 8 年前

    我想在进程生成时正确读取其标准输出。 我知道以前有人问过这个问题,但没有人回答。

    您可能想先看看StreamGobblerOutput类。

        public List<String> executeCall(String fileName) 
        {
        StringBuilder sbOutput = new StringBuilder();
        StringBuilder sbError = new StringBuilder();
    
        File file = new File(fileName);
        try ( BufferedReader br = new BufferedReader(new FileReader(file)) ) {
            String line;
            while ((line = br.readLine()) != null) {
               String [] parts = line.split("\\s");
               if(parts.length<2) {
                   sbError.append("Command too short for call: " + parts[0]);
                   continue;
               }
    
                List<String> args = new ArrayList<String>();
                args.add ("sfb.exe");
                for(int i = 1; i <parts.length; ++i) {
                    args.add (parts[i]);
                }
                args.add (sfbPassword);
    
                ProcessBuilder pb = new ProcessBuilder (args);
                pb.directory(new File(Support.getJustThePathFromFile(file)));
                Map<String, String> envs = pb.environment();
                String path = envs.get("Path");
                envs.put("Path", Paths.get(".").toAbsolutePath().normalize().toString() + ";" +path);
    
    
                //pb.redirectOutput(new Redirect() {});
                Process p = pb.start();
    
                String outputPathPrefix = pb.directory().getCanonicalPath();
    
                // any output?
                StreamGobblerOutput outputGobbler = new StreamGobblerOutput(p.getInputStream(), outputPathPrefix);
                outputGobbler.start();
    
                // any errors?
                StreamGobblerError errorGobbler = new StreamGobblerError(p.getErrorStream());
                errorGobbler.start();
    
    
                try
                {
                    p.waitFor();
                }
                catch (InterruptedException e)
                {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
    
                sbOutput = outputGobbler.getOutput();
                sbError = errorGobbler.getErrors();
    
                String rootPath= Support.getJustThePathFromFile(new File(fileName));
                File rootFile = new File(rootPath + "/..");
                String rootFolder = rootFile.getCanonicalFile().getName();
                System.err.println("rootFolder: " + rootFolder);
                mainApp.addModifiedFiles(outputGobbler.getModifiedFileNames(), rootFolder);
    
            }
        } catch ( IOException ex) {
            sbError.append(ex.getMessage());
        }
    
        mainApp.addOutput(sbOutput.toString());
        mainApp.addError(sbError.toString());
    
        return;
    
    }
    
        private class StreamGobblerOutput extends Thread {
            private InputStream is;
            private String outputPathPrefix;
            private StringBuilder sbOutput;
            private List<String> modifiedFileNames;
            private Scanner scanner;
    
            private StreamGobblerOutput(InputStream is, String outputPathPrefix) {
                this.is = is;
                this.outputPathPrefix = outputPathPrefix;
                sbOutput = new StringBuilder();
                modifiedFileNames = new ArrayList<String>();
                scanner = new Scanner(is);
            }
    
            public StringBuilder getOutput() {
                return sbOutput;    
            }
    
            public List<String> getModifiedFileNames() {
                return modifiedFileNames;       
            }
    
            @Override
            public void run() {
                //create pattern
                Pattern patternProgress = Pattern.compile("\\((\\d+)%\\)");
    
                //InputStreamReader isr = new InputStreamReader(is);
                //BufferedReader br = new BufferedReader(isr);
                String ligne = null;
                while (scanner.hasNextLine()) {
                    ligne = scanner.nextLine();
    
                    sbOutput.append(ligne);
                    sbOutput.append("\r\n");
                    //bw.write("\r\n");
    
                    Matcher mProgress = patternProgress.matcher(ligne);
                    if (mProgress.find()) {
                        int percentage = Integer.parseInt(mProgress.group(1));
                        System.err.println("percentage=" + percentage);
                        mainApp.mainWindowController.setProgressExecute(percentage/100.0);
                    }
                }
                mainApp.mainWindowController.setProgressExecute(1.0);
                if (scanner != null) {
                    scanner.close();
                 }
            }
        }
    
        private class StreamGobblerError extends Thread {
            private InputStream is;
            private StringBuilder sbError;
            private Scanner scanner;
    
            private StreamGobblerError(InputStream is) {
                this.is = is;
                sbError = new StringBuilder();
                scanner = new Scanner(is);
            }
    
            public StringBuilder getErrors() {
                return sbError;     
            }
    
            @Override
            public void run() {
                //InputStreamReader isr = new InputStreamReader(is);
                //BufferedReader br = new BufferedReader(isr);
                String ligne = null;
                while (scanner.hasNextLine()) {
                    ligne = scanner.nextLine();
                    sbError.append(ligne);
                    sbError.append("\r\n");
                }
                if (scanner != null) {
                    scanner.close();
                 }
            }
        }
    

    作为一种解决方法,我必须要求.exe的创建者在显示进度的每行中额外包含4100个字符。

    2 回复  |  直到 8 年前
        1
  •  2
  •   zeppelin    8 年前

    如果您的外部进程基于C/C++(stdio),那么这很可能是块缓冲问题:

    如果基于stdio的程序在终端中交互运行,则通常使用行缓冲,当其stdout重定向到管道时,使用块缓冲。在后一种情况下,在缓冲区溢出或刷新之前,您不会看到新行。

    this answer 了解更多详细信息,以及一些可能的解决方法。

    另请注意,根据 this

    对于某些系统,这提供了线路缓冲。但是,对于Win32,行为与IOFBF-完全缓冲相同。

    因此,如果您选择修改“exe”程序以设置适当的输出模式 setvbuf ,您必须使用:

    _IONBF No buffer
    

    相反

        2
  •  0
  •   worker_bee    8 年前

    来自Javadocs

    这意味着在一个字节后自动调用flush方法

    http://docs.oracle.com/javase/8/docs/api/java/io/PrintStream.html

    1. 一种方法是在每次写入后让输出流flush()。 System.out.flush()