2017-07-13 2 views
1

Je souhaite lire la sortie standard d'un processus à mesure qu'il est généré. Le processus va envoyer des informations pour un indicateur de progression, donc cela n'a pas de sens que je reçois l'information tout à la fois, ce que je fais et quel est le problème. J'ai essayé d'utiliser la classe Scanner comme suggéré dans un article, mais je reçois toujours la sortie seulement après que le processus soit terminé. Je réalise que cette question a déjà été posée, mais elle n'a pas reçu de réponse.Sortie de processus de lecture Java non bufferisée/en temps réel

Vous voudrez probablement regarder d'abord la classe 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(); 
      } 
     } 
    } 

Mise à jour: J'ai essayé de rediriger la sortie vers un fichier et la lecture de celui-ci, mais il semble que cela fonctionne dans le même problème de mise en mémoire tampon que la mise en œuvre précédente: je reçois seulement deux points de données. Pour contourner ce problème, je vais devoir demander au créateur du fichier .exe d'inclure 4100 caractères supplémentaires dans chaque ligne indiquant la progression.

+0

Avez-vous accès au code du processus qui génère une sortie? Peut-être que vous pouvez faire des flushes là-bas sur un certain intervalle de temps/après n nombre d'écritures? –

+0

Je n'ai pas accès au code du processus que j'appelle, mais quand je l'appelle depuis la console, il semble sortir les données de l'indicateur de progression une par une, c'est-à-dire qu'il semble être vidé après chaque point de données. – Adder

+0

@Adder - est le programme externe que vous appelez C/C++? – zeppelin

Répondre

1

Si votre processus externe est C/C++ (stdio) sur la base, que cela est très probablement un problème de mise en mémoire tampon de bloc:

programmes stdio-en règle générale, sont en ligne tamponnées si elles sont en cours d'exécution de manière interactive dans un terminal et bloc mis en mémoire tampon lorsque leur sortie standard est redirigée vers un canal. Dans ce dernier cas, vous ne verrez pas de nouvelles lignes jusqu'à ce que le tampon déborde ou soit vidé.

Voir this answer pour plus de détails, et quelques solutions de contournement possibles.

S'il vous plaît noter également que, selon this, mise en mémoire tampon de ligne n'est pas une option sur Win32:

_IOLBF Pour certains systèmes, cette mise en mémoire tampon fournit la ligne. Cependant, pour Win32, le comportement est le même que _IOFBF - Full Buffering.

donc si vous choisissez de modifier le programme « exe » pour définir un mode de sortie approprié avec setvbuf, vous devez utiliser:

_IONBF No buffer 

à la place.

+0

Merci pour votre réponse. Comment le programme C doit-il être modifié? – Adder

+0

https://www.gnu.org/software/libc/manual/html_node/Controlling-Buffering.html De cette façon, je suppose. – Adder

+0

@Adder, oui, de voir ce lien s'il s'agit d'un binaire Windows: [setvbuf] (https://msdn.microsoft.com/fr-fr/library/86cebhfs.aspx), mais prenez note que la mise en mémoire tampon est pas pris en charge sous win32. – zeppelin

0

De l'Javadocs

En option, un PrintStream peut être créé de manière à vider automatiquement; cela signifie que la méthode flush est appelée automatiquement après l'écriture d'un tableau , l'appel de l'une des méthodes println ou l'écriture d'un caractère de nouvelle ligne ou d'un octet ('\ n').

Référence: http://docs.oracle.com/javase/8/docs/api/java/io/PrintStream.html

  1. Une façon de le faire est d'avoir flush flux de sortie() après chaque écriture. System.out.flush()

  2. Vous pouvez également définir votre propre PrintStream jetable et l'utiliser.

+0

Mon processus générant les points de données n'est pas écrit en Java et n'est accessible qu'indirectement à moi. – Adder