Java?Process中waitFor()的問題詳解
在編寫Java程序時(shí),有時(shí)候我們需要調(diào)用其他的諸如exe,shell這樣的程序或腳本。在Java中提供了兩種方法來(lái)啟動(dòng)其他程序: (1) 使用Runtime的exec()方法 (2) 使用ProcessBuilder的start()方法 。Runtime和ProcessBulider提供了不同的方式來(lái)啟動(dòng)程序,設(shè)置啟動(dòng)參數(shù)、環(huán)境變量和工作目錄。但是這兩種方法都會(huì)返回一個(gè)用于管理操作系統(tǒng)進(jìn)程的Process對(duì)象。這個(gè)對(duì)象中的waitFor()是我們今天要討論的重點(diǎn)。
來(lái)說說我遇到的實(shí)際情況:我想調(diào)用ffmpeg程序來(lái)對(duì)一首歌曲進(jìn)行轉(zhuǎn)碼,把高音質(zhì)版本的歌曲轉(zhuǎn)為多種低碼率的文件。但是在轉(zhuǎn)碼完成之后需要做以下操作:讀取文件大小,寫入ID3信息等。這時(shí)我們就想等轉(zhuǎn)碼操作完成之后我們可以知道。
如下這樣代碼
Process p = null; try { p = Runtime.getRuntime().exec("notepad.exe"); } catch (Exception e) { e.printStackTrace(); } System.out.println("我想被打印...");
在notepad.exe被執(zhí)行的同時(shí),打印也發(fā)生了,但是我們想要的是任務(wù)完成之后它才被打印。
之后發(fā)現(xiàn)在Process類中有一個(gè)waitFor()方法可以實(shí)現(xiàn)。如下:
Process p = null; try { p = Runtime.getRuntime().exec("notepad.exe"); p.waitFor(); } catch (Exception e) { e.printStackTrace(); } System.out.println("我想被打印...");
這下又出現(xiàn)了這樣的現(xiàn)象,必須要等我們把記事本關(guān)閉打印語(yǔ)句才會(huì)被執(zhí)行。并且你不碰手動(dòng)關(guān)閉它那程序就一直不動(dòng),程序貌似掛了.....這是什么情況,想調(diào)用個(gè)別的程序有這么難嗎?讓我們來(lái)看看waitFor()的說明:
JDK幫助文檔上這么說:如有必要,一直要等到由該 Process 對(duì)象表示的進(jìn)程已經(jīng)終止。如果已終止該子進(jìn)程,此方法立即返回。但是直接調(diào)用這個(gè)方法會(huì)導(dǎo)致當(dāng)前線程阻塞,直到退出子進(jìn)程。對(duì)此JDK文檔上還有如此解釋:因?yàn)楸镜氐南到y(tǒng)對(duì)標(biāo)準(zhǔn)輸入和輸出所提供的緩沖池有效,所以錯(cuò)誤的對(duì)標(biāo)準(zhǔn)輸出快速的寫入何從標(biāo)準(zhǔn)輸入快速的讀入都有可能造成子進(jìn)程的所,甚至死鎖。好了,問題的關(guān)鍵在緩沖區(qū)這個(gè)地方:可執(zhí)行程序的標(biāo)準(zhǔn)輸出比較多,而運(yùn)行窗口的標(biāo)準(zhǔn)緩沖區(qū)不夠大,所以發(fā)生阻塞。接著來(lái)分析緩沖區(qū),哪來(lái)的這個(gè)東西,當(dāng)Runtime對(duì)象調(diào)用exec(cmd)后,JVM會(huì)啟動(dòng)一個(gè)子進(jìn)程,該進(jìn)程會(huì)與JVM進(jìn)程建立三個(gè)管道連接:標(biāo)準(zhǔn)輸入,標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤流。假設(shè)該程序不斷在向標(biāo)準(zhǔn)輸出流和標(biāo)準(zhǔn)錯(cuò)誤流寫數(shù)據(jù),而JVM不讀取的話,當(dāng)緩沖區(qū)滿之后將無(wú)法繼續(xù)寫入數(shù)據(jù),最終造成阻塞在waitfor()這里。 知道問題所在,我們解決問題就好辦了。查看網(wǎng)上說的方法多數(shù)是開兩個(gè)線程在waitfor()命令之前讀出窗口的標(biāo)準(zhǔn)輸出緩沖區(qū)和標(biāo)準(zhǔn)錯(cuò)誤流的內(nèi)容。代碼如下:
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")); //獲取進(jìn)程的標(biāo)準(zhǔn)輸入流 final InputStream is1 = p.getInputStream(); //獲取進(jìn)城的錯(cuò)誤流 final InputStream is2 = p.getErrorStream(); //啟動(dòng)兩個(gè)線程,一個(gè)線程負(fù)責(zé)讀標(biāo)準(zhǔn)輸出流,另一個(gè)負(fù)責(zé)讀標(biāo)準(zhǔn)錯(cuò)誤流 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){} } }
這個(gè)方法確實(shí)可以解決調(diào)用waitFor()方法阻塞無(wú)法返回的問題。但是在其中過程中我卻發(fā)現(xiàn)真正起關(guān)鍵作用的緩沖區(qū)是getErrorStream()說對(duì)應(yīng)的那個(gè)緩沖區(qū)沒有被清空,意思就是說其實(shí)只要及時(shí)讀取標(biāo)準(zhǔn)錯(cuò)誤流緩沖區(qū)的數(shù)據(jù)程序就不會(huì)被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();
不過這種寫法不知道是不是適合所有的情況,網(wǎng)上其他人說的需要開兩個(gè)線程可能不是沒有道理。這個(gè)還是具體問題具體對(duì)待吧。
到這里問題的原因也清楚了,問題也被解決了,是不是就結(jié)束了。讓我們回過頭來(lái)再分析一下,問題的關(guān)鍵是處在輸入流緩沖區(qū)那個(gè)地方,子進(jìn)程的產(chǎn)生的輸出流沒有被JVM及時(shí)的讀取最后緩沖區(qū)滿了就卡住了。如果我們能夠不讓子進(jìn)程向輸入流寫入數(shù)據(jù),是不是可以解決這個(gè)問題。對(duì)于這個(gè)想法直接去ffmpeg官網(wǎng)查找,最終發(fā)現(xiàn)真的可以關(guān)閉子進(jìn)程向窗口寫入數(shù)據(jù)。命令如下:
ffmpeg.exe -loglevel quiet -i 1.mp3 -ab 16k -ar 22050 -acodec libmp3lame r.mp3
稍微分析一下:-acodec 音頻流編碼方式 -ab 音頻流碼率(默認(rèn)是同源文件碼率,也需要視codec而定) -ar 音頻流采樣率(大多數(shù)情況下使用44100和48000,分別對(duì)應(yīng)PAL制式和NTSC制式,根據(jù)需要選擇),重點(diǎn)就是-loglevel quiet這句
http://www.ffmpeg.com.cn/index.php/Ffmpeg%E9%80%89%E9%A1%B9%E8%AF%A6%E8%A7%A3
這才是我們想要的結(jié)果:
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("我想被打印...");
最后是自己寫的一個(gè)簡(jiǎn)單的操作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為單位,保留小數(shù)點(diǎn)兩位 */ 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格式的歌曲長(zhǎng)度 * 庫(kù)地址: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("該歌曲的長(zhǎng)度為:"+songTime); } catch (Exception e) { try { bt.close(); fis.close(); } catch (Exception ee) { ee.printStackTrace(); } } return songTime; } /** * 將源MP3向下轉(zhuǎn)碼成低品質(zhì)的文件 * @參數(shù): @param srcPath 源地址 * @參數(shù): @param bitrate 比特率 * @參數(shù): @param desfile 目標(biāo)文件 * @return void * @throws */ public static void mp3Transcoding(String srcPath,String bitrate,String desFile) { //Java調(diào)用CMD命令時(shí),不能有空格 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下調(diào)用是其他寫法 p = rt.exec(command ,null,new File("C:\\ffmpeg-git-670229e-win32-static\\bin")); p.waitFor(); System.out.println("線程返回,轉(zhuǎn)碼后的文件大小為:"+desFile.length()+",現(xiàn)在可以做其他操作了,比如重新寫入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\\陳淑樺 - 夢(mèng)醒時(shí)分.mp3","E:\\Tmp\\1.mp3","E:\\Test1\\走天涯、老貓 - 楊望.acc","E:\\Test1\\因?yàn)閻矍?鈴.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"); } } }
總結(jié)
到此這篇關(guān)于Java Process中waitFor()問題的文章就介紹到這了,更多相關(guān)Java Process中waitFor()問題內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java設(shè)置token有效期的5個(gè)應(yīng)用場(chǎng)景(雙token實(shí)現(xiàn))
Token最常見的應(yīng)用場(chǎng)景之一就是身份驗(yàn)證,本文主要介紹了Java設(shè)置token有效期的5個(gè)應(yīng)用場(chǎng)景(雙token實(shí)現(xiàn)),具有一定的參考價(jià)值,感興趣的可以來(lái)了解一下2024-04-04Java使用自定義注解實(shí)現(xiàn)為事件源綁定事件監(jiān)聽器操作示例
這篇文章主要介紹了Java使用自定義注解實(shí)現(xiàn)為事件源綁定事件監(jiān)聽器操作,結(jié)合實(shí)例形式分析了java自定義注解、注解處理、事件監(jiān)聽與響應(yīng)等相關(guān)操作技巧,需要的朋友可以參考下2019-10-10SpringBoot整合MyCat實(shí)現(xiàn)讀寫分離的方法
這篇文章主要介紹了SpringBoot整合MyCat實(shí)現(xiàn)讀寫分離的方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04Spring cloud restTemplate 傳遞復(fù)雜參數(shù)的方式(多個(gè)對(duì)象)
這篇文章主要介紹了Spring cloud restTemplate 傳遞復(fù)雜參數(shù)的方式(多個(gè)對(duì)象),需要的朋友可以參考下2018-05-05SpringBoot整合MyBatis和MyBatis-Plus請(qǐng)求后不打印sql日志的問題解決
本文主要介紹了SpringBoot整合MyBatis和MyBatis-Plus請(qǐng)求后不打印sql日志的問題解決文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-07-07SpringBoot統(tǒng)一功能處理示例詳解(攔截器)
這篇文章主要介紹了SpringBoot統(tǒng)一功能處理(攔截器),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-08-08Spring?Boot?使用觀察者模式實(shí)現(xiàn)實(shí)時(shí)庫(kù)存管理的步驟
在現(xiàn)代軟件開發(fā)中,實(shí)時(shí)數(shù)據(jù)處理非常關(guān)鍵,本文提供了一個(gè)使用SpringBoot和觀察者模式開發(fā)實(shí)時(shí)庫(kù)存管理系統(tǒng)的詳細(xì)教程,步驟包括創(chuàng)建項(xiàng)目、定義實(shí)體類、實(shí)現(xiàn)觀察者模式、集成Spring框架、創(chuàng)建RESTful?API端點(diǎn)和測(cè)試應(yīng)用等,這將有助于開發(fā)者構(gòu)建能夠即時(shí)響應(yīng)庫(kù)存變化的系統(tǒng)2024-09-09Java實(shí)現(xiàn)簡(jiǎn)單學(xué)生管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)簡(jiǎn)單學(xué)生管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07