`

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 Process 使用

    `Process`对象的`waitFor()`方法会阻塞直到进程结束,返回值是进程的退出码: ```java int exitCode = process.waitFor(); System.out.println("Process exited with code: " + exitCode); ``` ### 6. 销毁进程 ...

    Java Process详解及实例

    Process类提供了多种方法来执行外部命令,例如exec()方法可以执行指定的命令,waitFor()方法可以等待外部进程的结束。 在Java Process中,Runtime类是一个重要的类,它提供了对Java虚拟机的访问接口。通过Runtime类...

    利用app_process实现免root调用shell_Java_下载.zip

    process.waitFor(); } catch (IOException | InterruptedException e) { e.printStackTrace(); } } } ``` 在上面的代码中,我们创建了一个名为`ShellExecutor`的类,其中的`main`方法用于执行shell命令。通过`...

    java调用windows命令

    process.waitFor(); ``` 5. **删除文件** - 使用`Runtime.getRuntime().exec()`删除文件时,必须包含命令解释器,例如: ```java Process process = Runtime.getRuntime().exec("cmd /c del f:\\aaa.doc"); `...

    java执行可执行文件或批处理

    这可以通过调用`Process`对象的`waitFor()`方法来实现: ```java Process process = Runtime.getRuntime().exec("ping 192.168.1.1"); process.waitFor(); ``` 需要注意的是,如果命令的执行时间过长,`waitFor()`...

    深入研究java.lang.Process类.doc

    Process 类的API预览包括destroy()、exitValue()、getErrorStream()、getInputStream()、getOutputStream()、waitFor() 等方法。 五、进程的创建和管理 Process 对象可以通过 ProcessBuilder.start() 和 Runtime....

    OS.zip_OS process java_java 操作系统

    例如,可以使用`ProcessBuilder.start()`方法来启动一个新的进程,`Process.waitFor()`方法来等待进程完成,以及`Process.destroy()`方法来终止进程。此外,`Process`对象提供了访问进程输入、输出和错误流的方法,...

    java调用DOS获得系统进程列表

    - 为了确保所有输出都被正确读取,并且外部进程已经完全结束,需要调用`Process.waitFor()`方法。 ```java process.waitFor(); ``` 4. **完整示例代码**: ```java public class GetProcessList { public ...

    java 使用dos命令详解

    这可以通过调用`Process.waitFor()`方法来实现。 ##### 示例5:等待命令执行完成 ```java Process process = Runtime.getRuntime().exec("cmd /c dir"); BufferedReader bufferedReader = new BufferedReader( new...

    JAVA如何调用WINDOWS命令行.doc

    `process.waitFor()`则是等待子进程完成后再往下执行。 #### 2. 执行DOS内部命令 若要执行DOS内部命令,可以采用两种方法: - **方法一**:将命令解释器包含在`exec()`方法的参数中。 - 在Windows NT上,可以写...

    Java执行dos命令

    在上述示例中,`process.waitFor()`用于等待进程结束并返回退出码,`exitCode`表示命令执行的结果。 三、ProcessBuilder类 在某些情况下,Runtime类可能无法满足复杂的命令行参数需求,这时可以使用ProcessBuilder...

    JAVA调用DOS命令.pdf

    通过调用 Process 对象的 waitFor 方法,可以阻塞当前 JAVA 线程,直到命令执行完毕。 但是,需要注意的是,如果该命令是有标准输出或者是错误输出的话,必须在 waitFor 方法阻塞之前,通过读取 Process 对象的 ...

    Java cmd命令行模拟工具

    `process.waitFor()`方法会阻塞直到命令执行完毕。 对于更复杂的命令,如需要参数、环境变量或者管道操作,可以使用`ProcessBuilder`类。例如,下面的代码创建了一个`ProcessBuilder`实例,设置了命令和参数,并将...

    java 定时备份数据库

    process.waitFor(); ``` 为了实现定时任务,我们可以使用`java.util.concurrent.ScheduledExecutorService`。首先,定义一个方法来执行备份操作,然后将其添加到调度器中。以下是一个简单的例子: ```java import ...

    编程语言java批处理.pdf

    在执行之后,可以使用`Process`对象的方法来管理这个子进程,比如`waitFor()`方法可以阻塞当前线程,直到子进程结束。 另外,文档中提到了使用批处理(Batch)文件进行操作。批处理文件通常包含一系列命令,可以一...

    在Linux系统下用java执行系统命令实例讲解

    在执行命令后,需要通过`Process`对象的`waitFor()`方法等待命令执行完成,获取退出状态码。 ```java int exitCode = process.waitFor(); System.out.println("Command exited with code: " + exitCode); ``` 退出...

    JAVA如何调用DOS命令.doc

    process.waitFor(); ``` 4. **资源管理**: - 在执行涉及文件操作的任务,如ZIP文件的解压缩,需要特别注意资源的释放。如果不正确地管理这些资源,可能会导致文件被占用而无法删除。例如,使用Java的ZIP模块...

    JAVA如何调用DOS命令

    解决的办法是,利用 Java 提供的 Process 类提供的方法让 Java 虚拟机截获被调用程序的 DOS 运行窗口的标准输出,在 waitfor() 命令之前读出窗口的标准输出缓冲区中的内容。 删除文件 在删除文件时,需要注意 ...

    JAVA-DOS-command.rar_DOS java_dos command_java dos_shelf

    - **等待命令完成**:调用`process.waitFor()`来阻塞当前线程,直到命令执行完成。 - **清理资源**:确保关闭所有打开的流,并且在不再需要进程时调用`process.destroy()`。 3. **示例代码**: 以下是一个简单的...

Global site tag (gtag.js) - Google Analytics