Programavimas

Kai „Runtime.exec“ () nebus

Kaip Java kalbos dalį, java.lang paketas yra netiesiogiai importuojamas į kiekvieną „Java“ programą. Šio paketo spąstai dažnai iškyla ir daro įtaką daugumai programuotojų. Šį mėnesį aptarsiu spąstus, tykančius „Runtime.exec“ () metodas.

4 spąstai: kai „Runtime.exec“ () nebus

Klasė java.lang.Runtime pasižymi statiniu metodu, vadinamu getRuntime (), kuris gauna dabartinę „Java Runtime Environment“. Tai vienintelis būdas gauti nuorodą į Veikimo laikas objektas. Naudodamiesi šia nuoroda, galite paleisti išorines programas iškvietę Veikimo laikas klasės exec () metodas. Kūrėjai dažnai vadina šį metodą, norėdami paleisti naršyklę, kad būtų rodomas pagalbos puslapis HTML.

Yra keturios perkrautos exec () komanda:

  • public Process exec (eilutė komanda);
  • viešasis procesas „exec“ (eilutė [] cmdArray);
  • public Process exec (eilutė komanda, eilutė [] envp);
  • viešasis procesas exec (String [] cmdArray, String [] envp);

Kiekvienam iš šių metodų komanda ir galbūt argumentų rinkinys perduodami operacinės sistemos funkcijų iškvietimui. Vėliau sukuriamas operacinei sistemai būdingas procesas (vykdoma programa) su nuoroda į a Procesas klasė grįžo į „Java VM“. Procesas klasė yra abstrakti klasė, nes specifinis Procesas egzistuoja kiekvienai operacinei sistemai.

Šiuos metodus galite perduoti tris galimus įvesties parametrus:

  1. Viena eilutė, nurodanti ir vykdomą programą, ir visus tos programos argumentus
  2. Eilučių masyvas, atskiriantis programą nuo jos argumentų
  3. Aplinkos kintamųjų masyvas

Patvirtinkite aplinkos kintamuosius forma vardas = vertė. Jei naudojate exec () turėdami vieną eilutę programai ir jos argumentams, atkreipkite dėmesį, kad eilutė analizuojama naudojant baltąjį tarpą kaip skiriklį per „StringTokenizer“ klasė.

Užkliuvo į „IllegalThreadStateException“

Pirmoji spąstai, susiję su „Runtime.exec“ () yra „IllegalThreadStateException“. Pirmasis API bandymas yra akivaizdžiausių jos metodų kodavimas. Pavyzdžiui, norėdami vykdyti procesą, kuris nėra išorinis „Java VM“, mes naudojame exec () metodas. Norėdami pamatyti vertę, kurią grąžina išorinis procesas, naudojame exitValue () metodas Procesas klasė. Pirmajame pavyzdyje bandysime paleisti „Java“ kompiliatorių (javac.exe):

4.1 sąrašas BadExecJavac.java

importuoti java.util. *; importuoti java.io. *; viešoji klasė BadExecJavac {public static void main (String args []) {try {Runtime rt = Runtime.getRuntime (); Procesas proc = rt.exec ("javac"); int exitVal = proc.exitValue (); System.out.println ("Process exitValue:" + exitVal); } gaudyti (mesti t) {t.printStackTrace (); }}} 

Bėgimas BadExecJavac gamina:

E: \ class \ com \ javaworld \ jpitfalls \ article2> java BadExecJavac java.lang.IllegalThreadStateException: procesas neišėjo iš java.lang.Win32Process.exitValue (vietinio metodo) adresu BadExecJavac.main (BadExecJavac.java:13: 

Jei išorinis procesas dar nėra baigtas, exitValue () metodas meta „IllegalThreadStateException“; todėl ši programa nepavyko. Nors dokumentuose nurodomas šis faktas, kodėl šis metodas negali laukti, kol jis gali pateikti teisingą atsakymą?

Išsamiau pažvelgti į metodus, esančius Procesas klasė atskleidžia a laukti() metodas, kuris daro būtent tai. Iš tiesų, laukti() taip pat grąžina išėjimo vertę, o tai reiškia, kad nenaudosite exitValue () ir laukti() kartu, bet verčiau rinktųsi vieną ar kitą. Vienintelis galimas laikas, kurį panaudosite exitValue () vietoj laukti() būtų, kai nenorite, kad jūsų programa blokuotų laukimą išoriniame procese, kuris niekada nebus baigtas. Užuot naudoję laukti() metodo, norėčiau perduoti loginį parametrą, vadinamą laukti į exitValue () metodas nustatyti, ar dabartinė gija turėtų laukti. Būlo reikšmė būtų naudingesnė, nes exitValue () yra tinkamesnis šio metodo pavadinimas ir nebūtina, kad du metodai atliktų tą pačią funkciją skirtingomis sąlygomis. Tokia paprasta sąlygų diskriminacija yra įvesties parametro sritis.

Todėl, norėdami išvengti šių spąstų, arba pagaukite „IllegalThreadStateException“ arba palaukite, kol procesas bus baigtas.

Dabar išspręskime problemą 4.1 sąraše ir palaukite, kol procesas bus baigtas. Sąraše 4.2 programa vėl bando vykdyti javac.exe ir tada laukia, kol išorinis procesas bus baigtas:

Sąrašas 4.2 BadExecJavac2.java

importuoti java.util. *; importuoti java.io. *; public class BadExecJavac2 {public static void main (String args []) {try {Runtime rt = Runtime.getRuntime (); Procesas proc = rt.exec ("javac"); int exitVal = proc.waitFor (); System.out.println ("Process exitValue:" + exitVal); } gaudyti (mesti t) {t.printStackTrace (); }}} 

Deja, bėgimas BadExecJavac2 neduoda produkcijos. Programa pakimba ir niekada neužbaigiama. Kodėl javac procesas niekada nėra baigtas?

Kodėl „Runtime.exec“ () pakimba

JDK „Javadoc“ dokumentuose pateikiamas atsakymas į šį klausimą:

Kadangi kai kurios vietinės platformos standartiniams įvesties ir išvesties srautams suteikia tik ribotą buferio dydį, nesugebėjus greitai parašyti įvesties srauto ar perskaityti antrinio proceso išvesties srauto, antrinis procesas gali būti blokuojamas ir netgi aklavietė.

Ar tai tik tas atvejis, kai programuotojai neskaito dokumentų, kaip numanoma dažnai cituojamame patarime: perskaitykite puikų vadovą (RTFM)? Atsakymas iš dalies yra teigiamas. Tokiu atveju perskaičius „Javadoc“ pateksite pusiaukelėje; jame paaiškinama, kad reikia tvarkyti savo išorinio proceso srautus, tačiau nenurodoma, kaip.

Čia žaidžiamas dar vienas kintamasis, tai rodo daugybė programuotojų klausimų ir klaidingų nuomonių, susijusių su šia API naujienų grupėse: „Runtime.exec“ () o „Proceso“ API atrodo labai paprastos, kad paprastumas klaidina, nes paprastas arba akivaizdus API naudojimas yra linkęs į klaidas. API dizainerio pamoka yra rezervuoti paprastas API paprastoms operacijoms. Operacijos, linkusios į sudėtingumą ir nuo platformos priklausančias priklausomybes, turėtų tiksliai atspindėti sritį. Gali būti, kad abstrakcija bus nunešta per toli. JConfig biblioteka pateikia išsamesnės API, skirtos tvarkyti failų ir procesų operacijas, pavyzdį (daugiau informacijos rasite toliau esančiuose šaltiniuose).

Dabar vadovaukimės JDK dokumentais ir tvarkykime javac procesą. Kai bėgsi javac be jokių argumentų, jis sukuria naudojimo teiginių rinkinį, kuriame aprašoma, kaip paleisti programą ir visų galimų programos parinkčių reikšmė. Žinant, kad tai vyksta stderr srautą, galite lengvai parašyti programą, kad šis srautas būtų išnaudotas prieš laukiant, kol procesas bus baigtas. 4.3 sąrašas užbaigia tą užduotį. Nors šis požiūris veiks, jis nėra geras bendras sprendimas. Taigi „Listing 4.3“ programa yra pavadinta Vidutiniškas „ExecJavac“; jis pateikia tik vidutinišką sprendimą. Geresnis sprendimas ištuštintų tiek standartinį klaidų srautą, tiek standartinį išvesties srautą. Geriausias sprendimas ištuštintų šiuos srautus vienu metu (tai pademonstruosiu vėliau).

4.3 sąrašas „MediocreExecJavac.java“

importuoti java.util. *; importuoti java.io. *; public class MediocreExecJavac {public static void main (String args []) {try {Runtime rt = Runtime.getRuntime (); Procesas proc = rt.exec ("javac"); „InputStream“ stderr = proc.getErrorStream (); InputStreamReader isr = nauja InputStreamReader (stderr); BufferedReader br = naujas BufferedReader (isr); Styginių eilutė = nulis; System.out.println (""); while ((line = br.readLine ())! = null) System.out.println (eilutė); System.out.println (""); int exitVal = proc.waitFor (); System.out.println ("Process exitValue:" + exitVal); } gaudyti (mesti t) {t.printStackTrace (); }}} 

Bėgimas Vidutiniškas „ExecJavac“ generuoja:

E: \ class \ com \ javaworld \ jpitfalls \ article2> java MediocreExecJavac Naudojimas: javac kur yra: -g Generuoti visą derinimo informaciją -g: none Generuoti jokią derinimo informaciją -g: {eilutės, vars, source} Generuoti tik dalį derinimo informacijos -O optimizuoti; gali trukdyti derinti arba padidinti klasės failus -įspėti Generuoti be įspėjimų -verbose Išvesties pranešimai apie tai, ką daro kompiliatorius -deprecation Išvesties šaltinio vietos, kuriose naudojamos nebenaudojamos API -bootclasspath Nepaisyti įkrovos klasės failų vietos -extdirs Nepaisyti įdiegtų plėtinių vietos -d Nurodyti kur talpinti sugeneruotus klasės failus -kodavimas Nurodyti simbolių kodavimą, kurį naudoja šaltinio failai -target Kurti klasės failus konkrečiai VM versijai Proceso exitValue: 2 

Taigi, Vidutiniškas „ExecJavac“ dirba ir sukuria išėjimo vertę 2. Paprastai išėjimo vertė yra 0 rodo sėkmę; bet kokia nulio reikšmė nurodo klaidą. Šių išėjimo reikšmių reikšmė priklauso nuo konkrečios operacinės sistemos. Win32 klaida, kurios vertė yra 2 yra klaida „failas nerastas“. Tai prasminga, nes javac tikisi, kad vykdysime programą su šaltinio kodo rinkmena, kurią sudarysime.

Taigi, norint apeiti antrąją spąstą - amžinai kabantį „Runtime.exec“ () - jei jūsų paleista programa sukuria išvestį arba tikisi įvesties, įsitikinkite, kad apdorojate įvesties ir išvesties srautus.

Tarkime, kad komanda yra vykdoma programa

Pagal „Windows“ operacinę sistemą suklūsta daug naujų programuotojų „Runtime.exec“ () bandant jį naudoti tokioms nevykdomoms komandoms kaip rež ir kopija. Vėliau jie susiduria „Runtime.exec“ ()trečioji spąstai. 4.4 sąrašas tiksliai parodo:

4.4 sąrašas BadExecWinDir.java

importuoti java.util. *; importuoti java.io. *; public class BadExecWinDir {public static void main (String args []) {try {Runtime rt = Runtime.getRuntime (); Procesas proc = rt.exec ("dir"); „InputStream“ stdin = proc.getInputStream (); InputStreamReader isr = nauja InputStreamReader (stdin); BufferedReader br = new BufferedReader (isr); Stygos eilutė = nulis; System.out.println (""); while ((line = br.readLine ())! = null) System.out.println (eilutė); System.out.println (""); int exitVal = proc.waitFor (); System.out.println ("Process exitValue:" + exitVal); } gaudyti (metamas t) {t.printStackTrace (); }}} 

Bėgimas „BadExecWinDir“ gamina:

E: \ class \ com \ javaworld \ jpitfalls \ article2> java BadExecWinDir java.io.IOException: CreateProcess: dir klaida = 2 at java.lang.Win32Process.create (vietinis metodas) adresu java.lang.Win32Process. (Nežinomas šaltinis) ne java.lang.Runtime.execInternal (vietinis metodas) at java.lang.Runtime.exec (nežinomas šaltinis) at java.lang.Runtime.exec (nežinomas šaltinis) at java.lang.Runtime.exec (nežinomas šaltinis) java .lang.Runtime.exec (nežinomas šaltinis) BadExecWinDir.main (BadExecWinDir.java:12) 

Kaip minėta anksčiau, paklaidos vertė 2 reiškia „failas nerastas“, o tai šiuo atveju reiškia, kad vykdomasis failas buvo pavadintas dir.exe nepavyko rasti. Taip yra todėl, kad direktorijos komanda yra „Windows“ komandų interpretatoriaus dalis, o ne atskiras vykdomasis failas. Norėdami paleisti „Windows“ komandų vertėją, vykdykite bet kurį komanda.com arba cmd.exe, atsižvelgiant į jūsų naudojamą „Windows“ operacinę sistemą. 4.5 sąraše vykdoma „Windows“ komandų vertėjo kopija ir vykdoma vartotojo pateikta komanda (pvz., rež).

Pateikiama 4.5 „GoodWindowsExec.java“