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 音频流采样率(大多数情况下使用44100和48000,分别对应PAL制式和NTSC制式,根据需要选择),重点就是-loglevel quiet这句

http://www.ffmpeg.com.cn/index.php/Ffmpeg%E9%80%89%E9%A1%B9%E8%AF%A6%E8%A7%A3

这才是我们想要的结果:

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");
		}
	}
}

总结

到此这篇关于Java Process中waitFor()问题的文章就介绍到这了,更多相关Java Process中waitFor()问题内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java超详细讲解多线程中的Process与Thread

    目录 进程和线程的关系 操作系统是如何管理进程的 并行和并发 创建线程的方法 串行执行和并发执行 Thread中的一次额重要方法 中断线程 线程等待 线程休眠(sleep) 进程和线程的关系 在操作系统中运行的程序就是进程,比如说QQ,播放器,游戏等等…程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念. 进程和线程都是为了处理并发编程这样的场景,但是进程有问题,频繁拆功创建和释放资源的时候效率低,相比之下,线程更轻量,创建和释放效率更高. 进程具有独立性,每个进程有各自独立

  • Java中Process类的使用与注意事项说明

    目录 Process类的使用与注意事项说明 1.在项目开发中 2.在这里就需要认识一下process类 3.来说说今天业务需求[waitfor()]: 4.前不久遇到一个奇怪的问题就是ajax调用没有返回值 java的Process深入讲解 Process类的使用与注意事项说明 1.在项目开发中 经常会遇到调用其它程序功能的业务需求,在java中通常有两种实现方法 Runtime runtime = Runtime.getRuntime(); Process p = runtime.exec(c

  • Java获取Process子进程进程ID方法详解

    目录 windows上获取pid linux上获取pid java可以通过Runtime.getRuntime().exec()执行一个操作系统的命令,在操作系统层面执行命令也就创建了一个进程,Java中用Process类表示进程,如何获取进程ID呢?Process是一个抽象类,然后它并没有直接为我们提供获取进程ID的属性或方法. 下面来介绍一下在两种最常用的操作系统(windows.linux)上用Java执行本地命令后,如何获取对应的进程ID. windows上获取pid 首先引入如下依赖.

  • Java Process详解及实例

    Runtime Java可以通过Runtime来调用其他进程,如cmd命令,shell文件的执行等.可以应该该类设置系统时间,执行shell文件.此处记录几个有用应用如下. 设置本地时间 可以调用cmd /c date命令,完成本地时间设置,不过这个命令在win7下可以使用,但是win10需要管理员权限,可能无法设置系统时间.win7下使用Java实现修改本地时间代码如下,需要注意的是waitFor是必须的,否则无法立即生效. /** * 设置本地日期 * @param date yyyy-MM

  • Java Process类的详解及实例代码

    Java Process类的详解 前言: 今天用了下Java.lang.Process类,只是初步的学习,并没有深入实践,因为感觉它的用途并不是很大,偶尔才可能用上,如果要经常使用它的人可以自行参考JDk文档. 对Process类的简要说明: Process类是一个抽象类,方法都是抽象的,它封装了一个进程,也就是一个可执行的程序  该类提供进程的输入.执行输出到进程.等待进程的完成和检查进程的退出状态及销毁进程的方法 ProcessBuilder.start()和Runtime.exec方法创建

  • Java Process中waitFor()的问题详解

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

  • java开发中嵌套类的详解及实例

     java开发中嵌套类的详解 在java语言规范里面,嵌套类(Nested Classes)定义是: A nested class is any class whose declaration occurs within the body of another class or interface. A top level class is a class that is not a nested class. 说的简单一点,就是定义在类里面的类.一般把定义内部类的外围类成为包装类(enclos

  • Java多线程中ReentrantLock与Condition详解

    一.ReentrantLock类 1.1什么是reentrantlock java.util.concurrent.lock中的Lock框架是锁定的一个抽象,它允许把锁定的实现作为Java类,而不是作为语言的特性来实现.这就为Lock的多种实现留下了空间,各种实现可能有不同的调度算法.性能特性或者锁定语义.ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,但是添加了类似锁投票.定时锁等候和可中断锁等候的一些特性.此外,它还提供了在激烈争用情况下更

  • Java jvm中Code Cache案例详解

    Code Cache JVM生成的native code存放的内存空间称之为Code Cache:JIT编译.JNI等都会编译代码到native code,其中JIT生成的native code占用了Code Cache的绝大部分空间 相关参数 Codecache Size Options -XX:InitialCodeCacheSize 用于设置初始CodeCache大小 -XX:ReservedCodeCacheSize 用于设置Reserved code cache的最大大小,通常默认是2

  • Java BigDecimal中divide方法案例详解

    1.首先说一下用法,BigDecimal中的divide主要就是用来做除法的运算.其中有这么一个方法. public BigDecimal divide(BigDecimal divisor,int scale, int roundingMode) 第一个参数是除数,第二个参数代表保留几位小数,第三个代表的是使用的模式. BigDecimal.ROUND_DOWN:直接省略多余的小数,比如1.28如果保留1位小数,得到的就是1.2 BigDecimal.ROUND_UP:直接进位,比如1.21如

  • Java开发过程中关于异常处理的详解

    1.运行java时,出现了异常: 我这里是因为:arr[3]不存在: java.lang.ArrayIndexOutOfBoundsException: 3 public class btyf { public static void main(String[] args){ int[] arr={1,2,3}; System.out.println(arr[0]); System.out.println(arr[3]); System.out.println(arr[1]); //1 异常 A

  • Java 多态中继承的转型详解与用法分析

    目录 一.前言 二.转型 向上转型 向下转型 三.instanceof运算符 instanceof的用处 instanceof的使用格式: 一.前言 前面我们学习了多态的概述和使用,现在我们来学习前面剩下的转型问题. 二.转型

  • Java多线程中的Balking模式详解

    目录 1.场景 2.详细说明 3.Balking模式的本质:停止并返回 源代码如下: 总结 1.场景 自动保存功能: 为防止电脑死机,而定期将数据内容保存到文件中的功能. 2.详细说明 当数据内容被修改时,内容才会被保存.即当写入的内容与上次写入的内容一致时,其实就没有必要执行写入操作.也就是说,以”数据内容是否一致”作为守护条件.若数据内容相同,则不执行写入操作,直接返回. 3.Balking模式的本质:停止并返回 如果现在不合适执行该操作,或者没有必要执行该操作,就停止处理,直接返回—-Ba

  • Java设计模式中的外观模式详解

    目录 模式介绍 UML类图 外观模式案例: 外观模式的注意事项和细节 模式介绍 外观模式(Facade) ,也叫“过程模式:外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用. 外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节. UML类图 类图解析: Facade:为调用端提供统一的调用接口,外观类知道哪些子系统负责处理请求,从而将调用端的请求代理给适当子系统对象

  • Java设计模式中的门面模式详解

    目录 门面模式 概述 应用场景 目的 优缺点 主要角色 门面模式的基本使用 创建子系统角色 创建外观角色 客户端调用 门面模式实现商城下单 库存系统 支付系统 物流系统 入口系统 客户端调用 门面模式 概述 门面模式(Facade Pattern)又叫外观模式,属于结构性模式. 它提供一个统一的接口去访问多个子系统的多个不同的接口,它为子系统中的一组接口提供一个统一的高层接口.使得子系统更容易使用. 客户端不需要知道系统内部的复杂联系,只需定义系统的入口.即在客户端和复杂系统之间再加一层,这一层

随机推荐