iPad 的發佈不啻是最近科技界的熱門話題之一,沒有看過YouTube影片的可以稍微看一下:





我知道有很多Apple的死忠支持者,所以可能別人一說什麼不好聽的話就群起圍剿之,但是憑良心說,這次 iPad 較之前 iPod 與 iPhone 的發佈時所帶給人驚艷的感覺確實是少了許多。可能是我本來期許在硬體上有什麼重大突破吧!尤其關注彩色顯示器的功耗。很可惜並沒有看到這方面的改善,看起來就像是放大版的 iPhone ,影片也一直強調多媒體的效果。雖說硬體上看不出什麼突破,但軟體上卻有著顯著的進步,用一個類似書架的方式呈現電子書感覺真的很高明。多少可以從側面瞭解 Apple 優秀的軟體工程師。同樣身為軟體工程師,真的是相當敬佩。影片上說明的待機時間10小時,跟 Amamzon Kindle 可以待機7天比起來真的是少了一點,如果以 eBook 的角度而言實在不怎麼方便,有些書不是10小時可以看完的,所以可能的情況是邊充電邊看書,想起來真的很麻煩。接下來就是看市場是否賞臉了。

現在的趨勢下似乎把平板電腦(或類似的裝置)分成兩派,一派是以舒適閱讀為主,一派是以酷炫玩樂為主。玩樂似乎比較受到年輕族群的喜愛,未來Apple似乎也打算把筆電整合到iPod,這是可預期的一條路。可惜的是,在某些領域(例如:教育)我還是認為舒適才是最重要的考量,畢竟長時間觀看TFT對眼睛還是有一定程度傷害,可以預期的是隨著科技的進步,未來在效能上應該可以作到接近TFT的效能,以長遠而言這仍然是手持閱讀裝置最終的發展目標。

寄件者 光與影的對話

現在的電腦越來越少有RS-232 serial port了,最近有需要所以特別尋找了一下,目前市面上比較普遍使用的晶片有FTDI, Prolific跟Atmel,相容性最高的是FTDI,所以如果有需要建議找FTDI的會比較穩定。而且目前linux的kernal幾乎都可以認得到這個裝置,對於把linux當成預設工作環境的人相當方便。

或許大部分有寫過Java程式的人都知道java.lang.Runtime這個class有一個method叫做exec(),可以被用來呼叫(調用)外部的程式。然而大部分的人都不知道這個method存在著幾個機車的小陷阱,一個不小心就會發生災難了,待我娓娓道來...

這個method有幾個overloaded的版本如下:


Process
exec(String[] progArray, String[] envp, File directory)
Executes the specified command and its arguments in a separate native process.
Process
exec(String prog, String[] envp)
Executes the specified program in a separate native process.
Process
exec(String prog)
Executes the specified program in a separate native process.
Process
exec(String prog, String[] envp, File directory)
Executes the specified program in a separate native process.
Process
exec(String[] progArray)
Executes the specified command and its arguments in a separate native process.
Process
exec(String[] progArray, String[] envp)
Executes the specified command and its arguments in a separate native process.

其中prog這個參數是要執行的外部可執行檔名稱,progArray則是可執行檔名稱和一些參數,當然你也可以把progArray合成一個prog參數(中間必須用空白格開),不過,問題多多。envp表示環境變數,directory表示可執行檔的目錄,更詳細的參數說明請參考Java文件。


請看以下的程式碼隱藏著什麼樣的危機?

import java.util.*;
import java.io.*;

public class BadExample1 {
    public static void main (String args[]) {
        try {            
            Runtime rt = Runtime.getRuntime ();
            Process proc = rt.exec ("javac");
            int exitVal = proc.exitValue ();
            System.out.println ("Process exitValue: " + exitVal);
        } catch (Exception e) {
            e.printStackTrace ();
        }
    }
}

結果是:

java BadExample1
java.lang.IllegalThreadStateException: process has not exited
        at java.lang.Win32Process.exitValue(Native Method)
        at BadExample1.main(BadExample1.java:13)

奇怪,這麼簡單的程式碼,不過就是請exec執行一行javac,怎麼就跳出Exception了呢?讓我們來分析一下,首先這段程式用getRuntime()這個static method取得Runtime物件,然後呼叫exec去執行"javac"外部可執行檔,最後取得回傳值並把它輸出在標準輸出。問題來了,exec會create一個新的process來執行外部可執行檔,如果呼叫proc.exitValue()的時候javac還沒執行完畢,JVM就會丟出IllegalThreadStateException (記得嗎?在多工的作業系統裡,每個process幾乎可以被看成是同時運行的,既然如此沒有人可以保證process執行完成的先後順序。) ,有方法可以等外部process執行完畢嗎?YES,Process這個class提供了一個waitFor() method正好可以用來解決這個問題,而且waitFor()還會回傳等同於
exitValue()回傳值,所以程式再度改寫如下:

import java.util.*;
import java.io.*;

public class BadExample2 {
    public static void main (String) {
        try {            
            Runtime rt = Runtime.getRuntime ();
            Process proc = rt.exec ("javac");
            int exitVal = proc.waitFor ();
            System.out.println ("Process exitValue: " + exitVal);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Sorry, Sorry, Sorry...讓我想起當紅的SorrySorry舞,很抱歉,這次程式直接當在那裡給你看,奇怪?為什麼會當,原來JDK裡面有一段話是這麼說的:

Because some native platforms only provide limited buffer size for standard input and output streams, failure to promptly write the input stream or read the output stream of the subprocess may cause the subprocess to block, and even deadlock.

好吧,所以以這個例子而言,應該要把stderr跟stdout裡面的buffer趕快讀走,再度改寫程式如下:

import java.util.*;
import java.io.*;

public class OkExample1 {
    public static void main (String args[]) {
        try {            
            Runtime rt = Runtime.getRuntime ();
            Process proc = rt.exec ("javac");
            String line = null;

            InputStream stderr = proc.getErrorStream ();
            InputStreamReader esr = new InputStreamReader (stderr);
            BufferedReader ebr = new BufferedReader (esr);
            System.out.println ("<error>");
            while ( (line = ebr.readLine ()) != null)
                System.out.println(line);
            System.out.println ("</error>");
            
            InputStream stdout = proc.getInputStream ();
            InputStreamReader osr = new InputStreamReader (stdout);
            BufferedReader obr = new BufferedReader (osr);
            System.out.println ("<output>");
            while ( (line = obr.readLine ()) != null)
                System.out.println(line);
            System.out.println ("</output>");

            int exitVal = proc.waitFor ();
            System.out.println ("Process exitValue: " + exitVal);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

輸出結果:

java OkExample1
<error>
Usage: javac <options> <source files>
where <options> includes:
  -g                     Generate all debugging info
  -g:none                Generate no debugging info
  -g:{lines,vars,source} Generate only some debugging info
  -O                     Optimize; may hinder debugging or enlarge class files
  -nowarn                Generate no warnings
  -verbose               Output messages about what the compiler is doing
  -deprecation           Output source locations where deprecated APIs are used
  -classpath <path>      Specify where to find user class files
  -sourcepath <path>     Specify where to find input source files
  -bootclasspath <path>  Override location of bootstrap class files
  -extdirs <dirs>        Override location of installed extensions
  -d <directory>         Specify where to place generated class files
  -encoding <encoding>   Specify character encoding used by source files
  -target <release>      Generate class files for specific VM version
</error>
Process exitValue: 2

雖然程式醜了點,但至少結果是還OK的,更好的作法是create兩個thread分別把stdout與stderr的資料讀出來。不過這裡有一點需要注意,得到stdout的方法是呼叫getInputStream()名稱上比較容易搞混。

另一個常犯的錯誤是把console指令shell指令當作是可執行檔,例如win32上的dir和copy:

import java.util.*;
import java.io.*;

public class BadExample3 {
    public static void main (String args[]) {
        try {            
            Runtime rt = Runtime.getRuntime ();
            Process proc = rt.exec ("dir");

            InputStream stderr = proc.getErrorStream ();
            InputStreamReader esr = new InputStreamReader (stderr);
            BufferedReader ebr = new BufferedReader (esr);
            System.out.println ("<error>");
            while ( (line = ebr.readLine ()) != null)
                System.out.println(line);
            System.out.println ("</error>");
            
            InputStream stdout = proc.getInputStream ();
            InputStreamReader osr = new InputStreamReader (stdout);
            BufferedReader obr = new BufferedReader (osr);
            System.out.println ("<output>");
            while ( (line = obr.readLine ()) != null)
                System.out.println(line);
            System.out.println ("</output>");

            int exitVal = proc.waitFor ();            
            System.out.println ("Process exitValue: " + exitVal);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

輸出結果:

java BadExample3
java.io.IOException: CreateProcess: dir error=2
        at java.lang.Win32Process.create(Native Method)
        at java.lang.Win32Process.<init>(Unknown Source)
        at java.lang.Runtime.execInternal(Native Method)
        at java.lang.Runtime.exec(Unknown Source)
        at java.lang.Runtime.exec(Unknown Source)
        at java.lang.Runtime.exec(Unknown Source)
        at java.lang.Runtime.exec(Unknown Source)
        at BadExecWinDir.main(BadExecWinDir.java:12)

error=2表示找不到這個檔案,也就是系統並不存在dir.exe,哪是因為dir只是window command interpreter(command.com或cmd.exe)的其中一個指令。所以我們把上面的例子再改寫:

import java.util.*;
import java.io.*;

class StreamConsumer extends Thread {
    InputStream is;
    String type;
    
    StreamConsumer (InputStream is, String type) {
        this.is = is;
        this.type = type;
    }
    
    public void run () {
        try {
            InputStreamReader isr = new InputStreamReader (is);
            BufferedReader br = new BufferedReader (isr);
            String line = null;
            while ((line = br.readLine()) != null)
                System.out.println (type + ">" + line);    
        } catch (IOException ioe) {
            ioe.printStackTrace();  
        }
    }
}

public class GoodExample1 {
    public static void main (String args[]) {
        if (args.length < 1) {
            System.out.println ("USAGE: java GoodWindowsExec <cmd>");
            System.exit (1);
        }
        
        try {            
            String osName = System.getProperty ("os.name");
            String[] cmd = new String[3];
            if (osName.equals ("Windows NT")) {
                cmd[0] = "cmd.exe";
                cmd[1] = "/C";
                cmd[2] = args[0];
            } else if( osName.equals ("Windows 95")) {
                cmd[0] = "command.com";
                cmd[1] = "/C";
                cmd[2] = args[0];
            }
            
            Runtime rt = Runtime.getRuntime ();
            System.out.println ("Execing " + cmd[0] + " " + cmd[1] 
                               + " " + cmd[2]);
            Process proc = rt.exec (cmd);
            // any error message?
            StreamConsumer errorConsumer = new 
                StreamConsumer (proc.getErrorStream(), "error");            
            
            // any output?
            StreamConsumer outputConsumer = new 
                StreamConsumer (proc.getInputStream(), "output");
                
            // kick them off
            errorConsumer.start ();
            outputCosumer.start ();
                                    
            // any error???
            int exitVal = proc.waitFor ();
            System.out.println ("ExitValue: " + exitVal);        
        } catch (Exception e) {
            e.printStackTrace ();
        }
    }
}

輸出結果:

java GoodExample1 "dir *.java"
Execing cmd.exe /C dir *.java
output> Volume in drive E has no label.
output> Volume Serial Number is 5C5F-0CC9
output>
output> Directory of E:\classes\com\javaworld\jpitfalls\article2
output>
output>10/23/00  09:01p                   805 BadExecBrowser.java
output>10/22/00  09:35a                   770 BadExecBrowser1.java
output>10/24/00  08:45p                   488 BadExecJavac.java
output>10/24/00  08:46p                   519 BadExecJavac2.java
output>10/24/00  09:13p                   930 BadExecWinDir.java
output>10/22/00  09:21a                 2,282 BadURLPost.java
output>10/22/00  09:20a                 2,273 BadURLPost1.java
... (省略)
output>10/12/00  09:29p                   151 SuperFrame.java
output>10/24/00  09:23p                 1,814 TestExec.java
output>10/09/00  05:47p                23,543 TestStringReplace.java
output>10/12/00  08:55p                   228 TopLevel.java
output>              22 File(s)         46,661 bytes
output>                         19,678,420,992 bytes free
ExitValue: 0

還有一個常犯的錯誤是認為所有console或shell上可以執行的指令以為都可以透過exec()來達成,例如redirect >,請看:

import java.util.*;
import java.io.*;

public class BadExample4 {
    public static void main (String args[]) {
        try {            
            Runtime rt = Runtime.getRuntime ();
            Process proc = rt.exec ("echo 'Hello World' > test.txt");
            // any error message?
            StreamGobbler errorConsumer = new 
                StreamConsumer (proc.getErrorStream (), "error");            
            
            // any output?
            StreamGobbler outputConsumer = new 
                StreamConsumer (proc.getInputStream (), "output");
                
            // kick them off
            errorConsumer.start();
            outputConsumer.start();
                                    
            // any error???
            int exitVal = proc.waitFor ();
            System.out.println ("ExitValue: " + exitVal);        
        } catch (Exception e) {
            e.printStackTrace ();
        }
    }
}

輸出結果:

java BadExample4
OUTPUT>'Hello World' > test.txt
ExitValue: 0

如果這段command可以成功執行,那麼理論上test.txt應該會有一行'Hello World',但是事實上test.txt並不存在,也就是說redirect無法正確的被執行。

解決的辦法是:

  1. 自己開檔案把字串寫到test.txt裡面去。
  2. 建立一個.bat(win32)或.sh(linux)然後去執行。

參考資料: