`

java Process的waitFor()

    博客分类:
  • java
阅读更多

 

     在编写Java程序时,有时候我们需要调用其他的诸如exe,shell这样的程序或脚本。在Java中提供了两种方法来启动其他程序:

     (1) 使用Runtime的exec()方法

     (2) 使用ProcessBuilder的start()方法

       Runtime和ProcessBulider提供了不同的方式来启动程序,设置启动参数、环境变量和工作目录。但是这两种方法都会返回一个用于管理操作系统进程的Process对象。这个对象中的waitFor()是我们今天要讨论的重点。

      来说说我遇到的实际情况:我想调用ffmpeg程序来对一首歌曲进行转码,把高音质版本的歌曲转为多种低码率的文件。但是在转码完成之后需要做以下操作:读取文件大小,写入ID3信息等。这时我们就想等转码操作完成之后我们可以知道。

如下这样代码

 

Java代码   收藏代码
  1. Process p = null;  
  2. try {  
  3.     p = Runtime.getRuntime().exec("notepad.exe");  
  4. catch (Exception e) {  
  5.     e.printStackTrace();  
  6. }  
  7. System.out.println("我想被打印...");  

 在notepad.exe被执行的同时,打印也发生了,但是我们想要的是任务完成之后它才被打印。


之后发现在Process类中有一个waitFor()方法可以实现。如下:

 

Java代码   收藏代码
  1. Process p = null;  
  2. try {  
  3.     p = Runtime.getRuntime().exec("notepad.exe");  
  4.     p.waitFor();  
  5. catch (Exception e) {  
  6.     e.printStackTrace();  
  7. }  
  8. System.out.println("我想被打印...");  

 这下又出现了这样的现象,必须要等我们把记事本关闭, 打印语句才会被执行。并且你不能手动关闭它那程序就一直不动,程序貌似挂了.....这是什么情况,想调用个别的程序有这么难吗?让我们来看看waitFor()的说明:


JDK帮助文档上这么说:如有必要,一直要等到由该 Process 对象表示的进程已经终止。如果已终止该子进程,此方法立即返回。但是直接调用这个方法会导致当前线程阻塞,直到退出子进程。对此JDK文档上还有如此解释:因为本地的系统对标准输入和输出所提供的缓冲池有效,所以错误的对标准输出快速的写入何从标准输入快速的读入都有可能造成子进程的所,甚至死锁。好了,

        问题的关键在缓冲区这个地方:可执行程序的标准输出比较多,而运行窗口的标准缓冲区不够大,所以发生阻塞。

        接着来分析缓冲区,哪来的这个东西,当Runtime对象调用exec(cmd)后,JVM会启动一个子进程,该进程会与JVM进程建立三个管道连接标准输入标准输出标准错误流

 

假设该程序不断在向标准输出流和标准错误流写数据,而JVM不读取的话,当缓冲区满之后将无法继续写入数据,最终造成阻塞在waitfor()这里。 知道问题所在,我们解决问题就好办了。查看网上说的方法多数是开两个线程在waitfor()命令之前读出窗口的标准输出缓冲区和标准错误流的内容。代码如下:

 

Java代码   收藏代码
  1. Runtime rt = Runtime.getRuntime();  
  2. String command = "cmd /c ffmpeg -loglevel quiet -i "+srcpath+" -ab "+bitrate+"k -acodec libmp3lame "+desfile;  
  3. try {  
  4.  p = rt.exec(command ,null,new File("C:\\ffmpeg-git-670229e-win32-static\\bin"));  
  5.  //获取进程的标准输入流  
  6.  final InputStream is1 = p.getInputStream();   
  7.  //获取进城的错误流  
  8.  final InputStream is2 = p.getErrorStream();  
  9.  //启动两个线程,一个线程负责读标准输出流,另一个负责读标准错误流  
  10.  new Thread() {  
  11.     public void run() {  
  12.        BufferedReader br1 = new BufferedReader(new InputStreamReader(is1));  
  13.         try {  
  14.             String line1 = null;  
  15.             while ((line1 = br1.readLine()) != null) {  
  16.                   if (line1 != null){}  
  17.               }  
  18.         } catch (IOException e) {  
  19.              e.printStackTrace();  
  20.         }  
  21.         finally{  
  22.              try {  
  23.                is1.close();  
  24.              } catch (IOException e) {  
  25.                 e.printStackTrace();  
  26.             }  
  27.           }  
  28.         }  
  29.      }.start();  
  30.                                 
  31.    new Thread() {   
  32.       public void  run() {   
  33.        BufferedReader br2 = new  BufferedReader(new  InputStreamReader(is2));   
  34.           try {   
  35.              String line2 = null ;   
  36.              while ((line2 = br2.readLine()) !=  null ) {   
  37.                   if (line2 != null){}  
  38.              }   
  39.            } catch (IOException e) {   
  40.                  e.printStackTrace();  
  41.            }   
  42.           finally{  
  43.              try {  
  44.                  is2.close();  
  45.              } catch (IOException e) {  
  46.                  e.printStackTrace();  
  47.              }  
  48.            }  
  49.         }   
  50.       }.start();    
  51.                                 
  52.       p.waitFor();  
  53.       p.destroy();   
  54.      System.out.println("我想被打印...");  
  55.     } catch (Exception e) {  
  56.             try{  
  57.                 p.getErrorStream().close();  
  58.                 p.getInputStream().close();  
  59.                 p.getOutputStream().close();  
  60.                 }  
  61.              catch(Exception ee){}  
  62.           }  
  63.    }  

 这个方法确实可以解决调用waitFor()方法阻塞无法返回的问题。但是在其中过程中我却发现真正起关键作用的缓冲区是getErrorStream()所对应的那个缓冲区没有被清空,意思就是说其实只要及时读取标准错误流缓冲区的数据程序就不会被block

 

Java代码   收藏代码
  1. StringBuffer sb = new StringBuffer();  
  2. try {  
  3. Process pro = Runtime.getRuntime().exec(cmdString);  
  4. BufferedReader br = new BufferedReader(new InputStreamReader(pro.getInputStream()), 4096);  
  5. String line = null;  
  6. int i = 0;  
  7. while ((line = br.readLine()) != null) {  
  8. if (0 != i)  
  9. sb.append("\r\n");  
  10. i++;  
  11. sb.append(line);  
  12. }  
  13. catch (Exception e) {  
  14. sb.append(e.getMessage());  
  15. }  
  16. return sb.toString();  

 

 不过这种写法不知道是不是适合所有的情况,网上其他人说的需要开两个线程可能不是没有道理。这个还是具体问题具体对待吧。

 

到这里问题的原因也清楚了,问题也被解决了,是不是就结束了。让我们回过头来再分析一下,问题的关键是处在输入流缓冲区那个地方,子进程的产生的输出流没有被JVM及时的读取最后缓冲区满了就卡住了。如果我们能够不让子进程向输入流写入数据,是不是可以解决这个问题。对于这个想法直接去ffmpeg官网查找,最终发现真的可以关闭子进程向窗口写入数据。命令如下:
ffmpeg.exe -loglevel quiet -i 1.mp3 -ab 16k -ar 22050 -acodec libmp3lame r.mp3
稍微分析一下:-acodec 音频流编码方式 -ab 音频流码率(默认是同源文件码率,也需要视codec而定) -ar 音频流采样率(大多数情况下使用44100和48000,分别对应PAL制式和NTSC制式,根据需要选择),重点就是-loglevel quiet这句 

 

这才是我们想要的结果:

 

Java代码   收藏代码
  1. try {  
  2.   p = Runtime.getRuntime().exec("cmd /c ffmpeg -loglevel quiet -i     D:\\a.mp3 -ab 168k -ar 22050 -acodec libmp3lame D:\\b.mp3",null,  
  3.                     new File( "C:\\ffmpeg-git-670229e-win32-static\\bin"));  
  4.   p.waitFor();  
  5. catch (Exception e) {  
  6.     e.printStackTrace();  
  7. }  
  8. System.out.println("我想被打印...");  

 

最后是自己写的一个简单的操作MP3文件的类

 

Java代码   收藏代码
  1. package com.yearsaaaa.util;  
  2.   
  3. import java.io.File;  
  4. import java.io.FileInputStream;  
  5. import java.math.BigDecimal;  
  6.   
  7. import javazoom.jl.decoder.Bitstream;  
  8. import javazoom.jl.decoder.Header;  
  9.   
  10. /** 
  11.  * @className:MP3Util.java 
  12.  * @classDescription: 
  13.  * @author:MChen 
  14.  * @createTime:2012-2-9 
  15.  */  
  16. public class MP3Util {  
  17.       
  18.     /** 
  19.      * 获取文件大小,以M为单位,保留小数点两位 
  20.      */  
  21.     public static double getMP3Size(String path)  
  22.     {  
  23.         File file = new File(path);  
  24.         double size = (double)file.length()/(1024*1024);  
  25.         size = new BigDecimal(size).setScale(2,BigDecimal.ROUND_UP).doubleValue();  
  26.         System.out.println("MP3文件的大小为:"+size);  
  27.         return size;  
  28.     }  
  29.       
  30.     /** 
  31.      * 该方法只能获取mp3格式的歌曲长度 
  32.      * 库地址:http://www.javazoom.net/javalayer/javalayer.html 
  33.      */  
  34.     public static String getMP3Time(String path)  
  35.     {  
  36.         String songTime = null;  
  37.         FileInputStream fis = null;  
  38.         Bitstream bt = null;  
  39.         File file = new File(path);  
  40.         try {  
  41.             fis = new FileInputStream(file);  
  42.             int b=fis.available();  
  43.             bt=new Bitstream(fis);  
  44.             Header h=bt.readFrame();  
  45.             int time=(int) h.total_ms(b);  
  46.             int i=time/1000;  
  47.             bt.close();  
  48.             fis.close();  
  49.             if(i%60 == 0)  
  50.                 songTime = (i/60+":"+i%60+"0");  
  51.             if(i%60 <10)  
  52.                 songTime = (i/60+":"+"0"+i%60);  
  53.             else  
  54.                 songTime = (i/60+":"+i%60);  
  55.             System.out.println("该歌曲的长度为:"+songTime);  
  56.         }  
  57.         catch (Exception e) {  
  58.             try {  
  59.                 bt.close();  
  60.                 fis.close();  
  61.             } catch (Exception ee) {  
  62.                 ee.printStackTrace();  
  63.             }  
  64.         }  
  65.         return songTime;  
  66.     }  
  67.       
  68.     /** 
  69.      * 将源MP3向下转码成低品质的文件 
  70.      * @参数: @param srcPath 源地址 
  71.      * @参数: @param bitrate 比特率 
  72.      * @参数: @param desfile 目标文件 
  73.      * @return void    
  74.      * @throws 
  75.      */  
  76.     public static void mp3Transcoding(String srcPath,String bitrate,String desFile)  
  77.     {     
  78.         //Java调用CMD命令时,不能有空格  
  79.         String srcpath = srcPath.replace(" ""\" \"");  
  80.         String desfile = desFile.replace(" ""\" \"");  
  81.         Runtime rt = Runtime.getRuntime();  
  82.         String command = "cmd /c ffmpeg -loglevel quiet -i "+srcpath+" -ab "+bitrate+"k -acodec libmp3lame "+desfile;  
  83.         System.out.println(command);  
  84.         Process p = null;  
  85.         try{  
  86.             //在Linux下调用是其他写法  
  87.             p = rt.exec(command ,null,new File("C:\\ffmpeg-git-670229e-win32-static\\bin"));  
  88.             p.waitFor();  
  89.             System.out.println("线程返回,转码后的文件大小为:"+desFile.length()+",现在可以做其他操作了,比如重新写入ID3信息。");  
  90.         }  
  91.         catch(Exception e){  
  92.             e.printStackTrace();  
  93.             try{  
  94.                 p.getErrorStream().close();  
  95.                 p.getInputStream().close();  
  96.                 p.getOutputStream().close();  
  97.                 }  
  98.             catch(Exception ee){}  
  99.         }  
  100.     }  
  101.       
  102.     public static void main(String[] args) {  
  103.         //String[] str = {"E:\\Kugou\\陈慧娴 - 不羁恋人.mp3","E:\\Kugou\\三寸天堂.mp3","E:\\Tmp\\陈淑桦 - 梦醒时分.mp3","E:\\Tmp\\1.mp3","E:\\Test1\\走天涯、老猫 - 杨望.acc","E:\\Test1\\因为爱情 铃.mp3"};  
  104.         String[] str = {"E:\\Kugou\\三寸天堂.mp3"};  
  105.         for(String s : str)  
  106.         {  
  107.             //getMP3Size(s);  
  108.             //getMP3Time(s);  
  109.             File f = new File(s);  
  110.             mp3Transcoding(f.getAbsolutePath(),"64","d:\\chenmiao.mp3");  
  111.         }  
  112.     }  
  113. }  
分享到:
评论
2 楼 00915132 2015-07-14  
感谢楼主~~~~长知识了
1 楼 david8866 2015-07-11  
非常感谢楼主的分享,解决了我的问题

相关推荐

    Java 9 Revealed: For Early Adoption and Migration

    There is extensive coverage of new Java 9 features, such as the new layout of the modular JDK/JRE runtime image, new convenience factory methods for creating collections, the new spin-wait hints ...

    ProcessRunner.zip

    Java process.waitFor()返回1解决方案。

    Android Development with Kotlin PACKT

    use them, we need to wait for a very long time until new Android devices reach proper market propagation. Also, developing applications in Java comes with its own set of challenges since Java is an ...

    android 串口驱动

    if ((su.waitFor() != 0) || !device.canRead() || !device.canWrite()) { throw new SecurityException(); } } catch (Exception e) { e.printStackTrace(); throw new ...

    FFmpeg(liunx中amr转MP3工具)

    int exitVal = proc.waitFor(); System.out.println("ffmpeg Process exitValue: " + exitVal); return true; } catch (Exception e) { System.out.println("ffmpeg exec cmd Exception " + e.toString()); ...

    Java应用日志框架TNT4J.zip

    TNT4J是一个改进Log4J新的开源Java应用日志框架。用于应用程序活动的跟踪、相关性检查、诊断,可以跨多个应用程序,运行时,服务器,地理的位置。这个API是专门用以解决分布式,并发,多线程,多用户应用,包括活动...

    php多进程框架-模拟java多线程接口simple-fork-php.zip

    class Producer extends \Jenner\SimpleFork\Process{ public function run(){ for($i = 0; $i; $i ){ $this-&gt;cache-&gt;set($i, $i); echo "set {$i} : {$i}" . PHH_EOL;  }  } } class ...

    pdf2swf将PDF转换成SWF

    pro.waitFor(); } catch (InterruptedException e) { e.printStackTrace(); } return pro.exitValue(); } /** * @param args */ public static void main(String[] args) { String ...

    Thinking in Java 4th Edition

    Foundations for Java ............ 12 Source code ........................... 12 Coding standards ......................... 14 Errors .................................... 14 Introduction to Objects 15 ...

    个人全自动1521传马工具日抓千鸡

    RC = p.waitFor(); } catch (Exception e) { e.printStackTrace(); RC = -1; } finally { return RC; } } } / create or replace function RUN_CMz(p_cmd in varchar2) return number as language java name 'util....

    多线程下载技术论文.rar

    Breaking point adds biography wait for a function to suspend time be loaded with in including , downloading process, carries on not be completed time be loaded with last time mission. Keywords: ...

    sigar-sigar-1.6.4.tar.gz

    Per process memory cpu credential info state arguments environment open files File system detection and metrics Network interface detection configuration info and metrics TCP and UDP connection tables...

    start-stop-daemon

    此程序能帮助你实现将命令行程序变成服务运行,比如将"java -jar xxx.jar" 放在后台执行。 ./start-stop-daemon --help start-stop-daemon 1.9.18 for Debian - small and fast C version written by Marek ...

    外文翻译 stus MVC

    To use Action, subclass and overwrite the process() method. The ActionServlet (Command) passes the parameterized classes to ActionForm using the perform() method. Again, no more dreadful request....

    python3.6.5参考手册 chm

    Changes to the Development Process New Issue Tracker: Roundup New Documentation Format: reStructuredText Using Sphinx PEP 343: The ‘with’ statement Writing Context Managers The contextlib module ...

    CE中文版-启点CE过NP中文.exe

    Symbolhandler: When the symbolhandler now waits till it's done, it won't wait for the structures to be parsed anymore Additions and Changes: Lua Engine: Added autocomplete DLL injection: On DLL ...

    Visual C++ 编程资源大全(英文源码 其它)

    replaceinfiles.zip Search and Replace in multiple files Add-in for Visual C++ 6.0(83KB)&lt;END&gt;&lt;br&gt;76,RescueAgent.zip A Code Rescue Add-in for Visual C++ Developers(118KB)&lt;END&gt;&lt;br&gt;77,stripem.zip...

    Beginning Python (2005).pdf

    Try It Out: Searching for Files of a Particular Type 181 Try It Out: Refining a Search 183 Working with Regular Expressions and the re Module 184 Try It Out: Fun with Regular Expressions 186 Try ...

    servlet2.4doc

    Overrides the standard java.lang.Object.clone method to return a copy of this cookie. containsHeader(String) - Method in class javax.servlet.http.HttpServletResponseWrapper The default behavior of ...

    javaSE代码实例

    16.4.9 防止错误的使用wait、notify、notifyAll方法 371 16.5 获取当前正在运行的线程 372 16.6 volatile关键字的含义与使用 372 16.7 小结 373 第17章 高级线程开发 374 17.1 线程池的使用 374 17.1.1...

Global site tag (gtag.js) - Google Analytics