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

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

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

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

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

如下这样代码

  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()方法可以实现。如下:

  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()命令之前读出窗口的标准输出缓冲区和标准错误流的内容。代码如下:

  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. new Thread() {
  31. public void  run() {
  32. BufferedReader br2 = new  BufferedReader(new  InputStreamReader(is2));
  33. try {
  34. String line2 = null ;
  35. while ((line2 = br2.readLine()) !=  null ) {
  36. if (line2 != null){}
  37. }
  38. } catch (IOException e) {
  39. e.printStackTrace();
  40. }
  41. finally{
  42. try {
  43. is2.close();
  44. } catch (IOException e) {
  45. e.printStackTrace();
  46. }
  47. }
  48. }
  49. }.start();
  50. p.waitFor();
  51. p.destroy();
  52. System.out.println("我想被打印...");
  53. } catch (Exception e) {
  54. try{
  55. p.getErrorStream().close();
  56. p.getInputStream().close();
  57. p.getOutputStream().close();
  58. }
  59. catch(Exception ee){}
  60. }
  61. }

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

  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这句 

这才是我们想要的结果:

  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文件的类

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

java Process的waitFor()的更多相关文章

  1. Memory usage of a Java process java Xms Xmx Xmn

    http://www.oracle.com/technetwork/java/javase/memleaks-137499.html 3.1 Meaning of OutOfMemoryError O ...

  2. Process的Waitfor() 引起代码死锁

    Java用process调用c#的exe后,process.waitfor(). exe执行会停在某处.据说是waitfor引起的exe子线程死锁. 先存一个链接 http://yearsaaaa12 ...

  3. java Process在windows的使用汇总(转)

    最常用的是ant(java工程中流行),maven,及通用的exec(只要有shell脚本如.sh,.bat,.exe,.cmd等).而其实前两者不容易出错,后者却遇到了以下问题:Caused by: ...

  4. Java JPS找不到正在执行的java进程 jps cannot see running java process

    最近磁盘进展,把临时目录/tmp给全删了,结果发现jps的输出为空,找不到正在运行的jvm进程. 但是新建的进程没有问题,能够正常查看: [root@node-master ~]# ps -e|gre ...

  5. 使用Runtime.getRuntime().exec()在java中调用python脚本

    举例有一个Python脚本叫test.py,现在想要在Java里调用这个脚本.假定这个test.py里面使用了拓展的包,使得pythoninterpreter之类内嵌的编译器无法使用,那么只能采用ja ...

  6. Java程序执行Linux命令

    Java程序中要执行linux命令主要依赖2个类:Process和Runtime 首先看一下Process类: ProcessBuilder.start() 和 Runtime.exec 方法创建一个 ...

  7. JAVA 调用exe程序执行对应的文件 (个人用于编译Java文件)

    需求: 需要利用Java程序,来调用计算机本身的黑窗口,来将特定的Java文件编译成对应的字节码文件. 实现思路: 通过调用Java的Runtime类,每个 Java 应用程序都有一个 Runtime ...

  8. Java 调用 shell 脚本详解

    这一年的项目中,有大量的场景需要Java 进程调用 Linux的bash shell 脚本实现相关功能. 从之前的项目中拷贝的相关模块和网上的例子来看,有个别的“陷阱”造成调用shell 脚本在某些特 ...

  9. Java执行shell遇到的各种问题

    1.判断子进程是否执行结束 有的时候我们用java调用shell之后,之后的操作要在Process子进程正常执行结束的情况下才可以继续,所以我们需要判断Process进程什么时候终止. Process ...

随机推荐

  1. 微信小程序购物商城系统开发系列

    微信小程序购物商城系统开发系列 微信小程序开放公测以来,一夜之间在各种技术社区中就火起来啦.对于它 估计大家都不陌生了,对于它未来的价值就不再赘述,简单一句话:可以把小程序简单理解为一个新的操作系统. ...

  2. cacti手册选译(1)

    第一章 系统需求 Cacti需要你的系统安装一下软件: RRDTool版本1.0.49及以上,推荐1.4+ MYSQL5.x及以上版本 PHP5.1及以上 支持PHP的web Server如Apach ...

  3. Careercup - Microsoft面试题 - 5428361417457664

    2014-05-11 03:37 题目链接 原题: You have three jars filled with candies. One jar is filled with banana can ...

  4. 【转】eclipse下使用hibernate tools实现hibernate逆向工程

    一.基本环境 Eclipse 3.6 AppFuse Struts2 2.1.0 JBoss Hibernate Tools 3.4.0 二.JBoss Hibernate Tools 3.4.0安装 ...

  5. Nagios全方位告警接入-电话/微信/短信都支持

    百度告警平台地址: http://gaojing.baidu.com 联系我们: 邮箱:gaojing@baidu.com 电话:13924600771 QQ群:183806029 Nagios接入 ...

  6. 深入理解jQuery中live与bind方法的区别

    本篇文章主要是对jQuery中live与bind方法的区别进行了详细的分析介绍,需要的朋友可以过来参考下,希望对大家有所帮助 注意如果是通过jq添加的层和对象一定要用live(),用其他的都不起作用 ...

  7. 【POJ】【2891】Strange Way to Express Integers

    中国剩余定理/扩展欧几里得 题目大意:求一般模线性方程组的解(不满足模数两两互质) solution:对于两个方程 \[ \begin{cases} m \equiv r_1 \pmod {a_1} ...

  8. VSFTPD全攻略(/etc/vsftpd/vsftpd.conf文件详解)

    /etc/vsftpd/vsftpd.conf文件详解,分好类,方便大家查找与学习 #################匿名权限控制############### anonymous_enable=YE ...

  9. HackPorts – Mac OS X 渗透测试框架与工具

    HackPorts是一个OS X 下的一个渗透框架. HackPorts是一个“超级工程”,充分利用现有的代码移植工作,安全专业人员现在可以使用数以百计的渗透工具在Mac系统中,而不需要虚拟机. 工具 ...

  10. sublime text3 插件安装

    安装Package control 先打开安装代码的命令行 按 ctrl+~或者 view  -> show console 将下面的代码粘贴到输入框里 按回车 import urllib.re ...