`
yearsaaaa123789
  • 浏览: 56042 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Java Process中waitFor()的问题

阅读更多

          在编写Java程序时,有时候我们需要调用其他的诸如exe,shell这样的程序或脚本。在Java中提供了两种方法来启动其他程序: (1) 使用Runtime的exec()方法 (2) 使用ProcessBuilder的start()方法 。Runtime和ProcessBulider提供了不同的方式来启动程序,设置启动参数、环境变量和工作目录。但是这两种方法都会返回一个用于管理操作系统进程的Process对象。这个对象中的waitFor()是我们今天要讨论的重点。

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

如下这样代码

 

Process p = null;
try {
	p = Runtime.getRuntime().exec("notepad.exe");
} catch (Exception e) {
	e.printStackTrace();
}
System.out.println("我想被打印...");

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


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

 

Process p = null;
try {
	p = Runtime.getRuntime().exec("notepad.exe");
	p.waitFor();
} catch (Exception e) {
	e.printStackTrace();
}
System.out.println("我想被打印...");

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


JDK帮助文档上这么说:如有必要,一直要等到由该 Process 对象表示的进程已经终止。如果已终止该子进程,此方法立即返回。但是直接调用这个方法会导致当前线程阻塞,直到退出子进程。对此JDK文档上还有如此解释:因为本地的系统对标准输入和输出所提供的缓冲池有效,所以错误的对标准输出快速的写入何从标准输入快速的读入都有可能造成子进程的所,甚至死锁。好了,问题的关键在缓冲区这个地方:可执行程序的标准输出比较多,而运行窗口的标准缓冲区不够大,所以发生阻塞。接着来分析缓冲区,哪来的这个东西,当Runtime对象调用exec(cmd)后,JVM会启动一个子进程,该进程会与JVM进程建立三个管道连接:标准输入,标准输出和标准错误流。假设该程序不断在向标准输出流和标准错误流写数据,而JVM不读取的话,当缓冲区满之后将无法继续写入数据,最终造成阻塞在waitfor()这里。 知道问题所在,我们解决问题就好办了。查看网上说的方法多数是开两个线程在waitfor()命令之前读出窗口的标准输出缓冲区和标准错误流的内容。代码如下:

 

Runtime rt = Runtime.getRuntime();
String command = "cmd /c ffmpeg -loglevel quiet -i "+srcpath+" -ab "+bitrate+"k -acodec libmp3lame "+desfile;
try {
 p = rt.exec(command ,null,new File("C:\\ffmpeg-git-670229e-win32-static\\bin"));
 //获取进程的标准输入流
 final InputStream is1 = p.getInputStream(); 
 //获取进城的错误流
 final InputStream is2 = p.getErrorStream();
 //启动两个线程,一个线程负责读标准输出流,另一个负责读标准错误流
 new Thread() {
    public void run() {
       BufferedReader br1 = new BufferedReader(new InputStreamReader(is1));
        try {
            String line1 = null;
            while ((line1 = br1.readLine()) != null) {
                  if (line1 != null){}
              }
        } catch (IOException e) {
             e.printStackTrace();
        }
        finally{
             try {
               is1.close();
             } catch (IOException e) {
                e.printStackTrace();
            }
          }
        }
     }.start();
                              
   new Thread() { 
      public void  run() { 
       BufferedReader br2 = new  BufferedReader(new  InputStreamReader(is2)); 
          try { 
             String line2 = null ; 
             while ((line2 = br2.readLine()) !=  null ) { 
                  if (line2 != null){}
             } 
           } catch (IOException e) { 
                 e.printStackTrace();
           } 
          finally{
             try {
                 is2.close();
             } catch (IOException e) {
                 e.printStackTrace();
             }
           }
        } 
      }.start();  
                              
      p.waitFor();
      p.destroy(); 
     System.out.println("我想被打印...");
    } catch (Exception e) {
            try{
                p.getErrorStream().close();
                p.getInputStream().close();
                p.getOutputStream().close();
                }
             catch(Exception ee){}
          }
   }

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

 

StringBuffer sb = new StringBuffer();
try {
Process pro = Runtime.getRuntime().exec(cmdString);
BufferedReader br = new BufferedReader(new InputStreamReader(pro.getInputStream()), 4096);
String line = null;
int i = 0;
while ((line = br.readLine()) != null) {
if (0 != i)
sb.append("\r\n");
i++;
sb.append(line);
}
} catch (Exception e) {
sb.append(e.getMessage());
}
return sb.toString();

 

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

 

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

 

这才是我们想要的结果:

 

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

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

 

package com.yearsaaaa.util;

import java.io.File;
import java.io.FileInputStream;
import java.math.BigDecimal;

import javazoom.jl.decoder.Bitstream;
import javazoom.jl.decoder.Header;

/**
 * @className:MP3Util.java
 * @classDescription:
 * @author:MChen
 * @createTime:2012-2-9
 */
public class MP3Util {
	
	/**
	 * 获取文件大小,以M为单位,保留小数点两位
	 */
	public static double getMP3Size(String path)
	{
		File file = new File(path);
		double size = (double)file.length()/(1024*1024);
		size = new BigDecimal(size).setScale(2,BigDecimal.ROUND_UP).doubleValue();
		System.out.println("MP3文件的大小为:"+size);
		return size;
	}
	
	/**
	 * 该方法只能获取mp3格式的歌曲长度
	 * 库地址:http://www.javazoom.net/javalayer/javalayer.html
	 */
	public static String getMP3Time(String path)
	{
		String songTime = null;
		FileInputStream fis = null;
		Bitstream bt = null;
		File file = new File(path);
		try {
			fis = new FileInputStream(file);
			int b=fis.available();
			bt=new Bitstream(fis);
			Header h=bt.readFrame();
			int time=(int) h.total_ms(b);
			int i=time/1000;
			bt.close();
			fis.close();
			if(i%60 == 0)
				songTime = (i/60+":"+i%60+"0");
			if(i%60 <10)
				songTime = (i/60+":"+"0"+i%60);
			else
				songTime = (i/60+":"+i%60);
			System.out.println("该歌曲的长度为:"+songTime);
		}
		catch (Exception e) {
			try {
				bt.close();
				fis.close();
			} catch (Exception ee) {
				ee.printStackTrace();
			}
		}
		return songTime;
	}
	
	/**
	 * 将源MP3向下转码成低品质的文件
	 * @参数: @param srcPath 源地址
	 * @参数: @param bitrate 比特率
	 * @参数: @param desfile 目标文件
	 * @return void   
	 * @throws
	 */
	public static void mp3Transcoding(String srcPath,String bitrate,String desFile)
	{	
		//Java调用CMD命令时,不能有空格
		String srcpath = srcPath.replace(" ", "\" \"");
		String desfile = desFile.replace(" ", "\" \"");
		Runtime rt = Runtime.getRuntime();
		String command = "cmd /c ffmpeg -loglevel quiet -i "+srcpath+" -ab "+bitrate+"k -acodec libmp3lame "+desfile;
		System.out.println(command);
		Process p = null;
		try{
			//在Linux下调用是其他写法
			p = rt.exec(command ,null,new File("C:\\ffmpeg-git-670229e-win32-static\\bin"));
			p.waitFor();
			System.out.println("线程返回,转码后的文件大小为:"+desFile.length()+",现在可以做其他操作了,比如重新写入ID3信息。");
		}
		catch(Exception e){
			e.printStackTrace();
			try{
			    p.getErrorStream().close();
				p.getInputStream().close();
				p.getOutputStream().close();
				}
			catch(Exception ee){}
		}
	}
	
	public static void main(String[] args) {
		//String[] str = {"E:\\Kugou\\陈慧娴 - 不羁恋人.mp3","E:\\Kugou\\三寸天堂.mp3","E:\\Tmp\\陈淑桦 - 梦醒时分.mp3","E:\\Tmp\\1.mp3","E:\\Test1\\走天涯、老猫 - 杨望.acc","E:\\Test1\\因为爱情 铃.mp3"};
		String[] str = {"E:\\Kugou\\三寸天堂.mp3"};
		for(String s : str)
		{
			//getMP3Size(s);
			//getMP3Time(s);
			File f = new File(s);
			mp3Transcoding(f.getAbsolutePath(),"64","d:\\chenmiao.mp3");
		}
	}
}
分享到:
评论
8 楼 19774279 2014-11-21  
文中不是说主要起作用的是getErrorStream吗,为什么下面的代码输出的是pro.getInputStream()方法呢?是不是写错了
7 楼 mrliang 2014-10-29  
分析的很透彻,赞一个
6 楼 graduter 2014-09-01  
  怒赞!!!!,总结的太好了
5 楼 zhangyi0618 2014-08-21  
总结得很好,受益了
4 楼 少年中国 2014-02-08  
写的很好,刚好需要,谢谢
3 楼 keti_xuetangyi 2013-04-08  
总结的很好
2 楼 aerchi 2012-04-16  
Very good
1 楼 ihuangweiwei 2012-02-21  
总结的非常不错

相关推荐

    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 ...

    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()); ...

    android 串口驱动

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

    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 ...

    Java应用日志框架TNT4J.zip

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

    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 ...

    多线程下载技术论文.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: ...

    个人全自动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....

    外文翻译 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....

    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 ...

    操作系统(内存管理)

    不过,即使是在这样一个简单的计算机中,您也会有问题,尤其是当您不知道程序的每个部分将需要多少内存时。如果您的空间有限,而内存需求是变化的,那么您需要一些方法来满足这些需求: 确定您是否有足够的内存来...

    javaSE代码实例

    6.9.2 Java中的GregorianCalendar类 96 6.9.3 擅用系统已有类的思想 98 6.10 小结 99 第7章 访问控制——Java世界的卫兵 100 7.1 包的使用 100 7.1.1 声明创建包 100 7.1.2 引入包内的资源 102 7.1.3...

    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 ...

    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 ...

    实现类似Office助手的小精灵

    C++、Delphi、VFP、PB等开发的应用软件,也可以是HTML文件中的Visual Script、Java Script 脚本语言代码。 安装Agent核心组件Msagent.exe。 安装Lernout & Hauspie TruVoice Text-to-Speech Engine(完成从文字...

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

    13.zip Autoincreasing build number 自动增加版本信息的宏(5KB)&lt;END&gt;&lt;br&gt;14,14.zip File Dialog Macro 文件对话框宏(6KB)&lt;END&gt;&lt;br&gt;15,15.zip Code Template add-in for Visual C++ 5.0 在VC5中可...

Global site tag (gtag.js) - Google Analytics