在编写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. js selector libray

    http://sizzlejs.com/ http://selectivizr.com/

  2. 标准非STL之bitset

    template <size_t N> class bitset; BitsetA bitset stores bits (elements with only two possible ...

  3. Log4Net学习【二】

    Log4Net结构详解 当我们在描述为系统做日志这个动作的时候,实际上描述了3个点:做日志,其实就是在规定,在什么地方 用什么日志记录器 以什么样的格式做日志.把三个最重要的点抽取出来,即什么地方,日 ...

  4. Travis-CI的进一步使用

    今天主要对.travis.yml文件和makefile进行进一步的了解: 1.在.travis.yml文件中添加了给linux系统中安装了cppunit库的语句,使能够持续集成写过的单元测试的代码.主 ...

  5. Shell常用操作

    1.读取配置文件中的jdbc_url参数的值($InputParamFile为待读取的目标文件绝对路径) jdbc_url=`grep "jdbc_url" $InputParam ...

  6. openstack与VMware workStation的区别

    免责声明:     本文中使用的部分图片来自于网络,如有侵权,请联系博主进行删除 最近一直在研究云计算,恰好有个同事问了我一个问题:你们研究的openstack到底是什么东西?跟VMware Work ...

  7. JS 学习笔记--4---运算符

    1.JS 中包含的运算符有:一元运算符.二元运算符.三元运算符.算术运算符.关系运算符.逻辑运算符.位运算符.赋值运算符.其他的运算符等. 2.表达式:简单来讲就是一句代码(分号隔开),解释器会把它翻 ...

  8. Matlab实现单变量线性回归

    一.理论 二.数据集 6.1101,17.592 5.5277,9.1302 8.5186,13.662 7.0032,11.854 5.8598,6.8233 8.3829,11.886 7.476 ...

  9. Leetcode#145 Binary Tree Postorder Traversal

    原题地址 递归写法谁都会,看看非递归写法. 对于二叉树的前序和中序遍历的非递归写法都很简单,只需要一个最普通的栈即可实现,唯独后续遍历有点麻烦,如果不借助额外变量没法记住究竟遍历了几个儿子.所以,最直 ...

  10. JS控制图片拖动 放大 缩小 旋转 支持滚轮放大缩小 IE有效

    <html> <head>     <title>图片拖动,放大,缩小,转向</title> <script type="text/ja ...