也許在 Windows 中,你用滑鼠點兩下 .txt 檔案,就會使用「記事本」之類的程式開啟「純文字檔案」,顯示其中的一些文字,你鍵入幾個文字後存檔,下次再開啟檔案,仍可以看到原本輸入的文字。
有些檔案,如果你硬是用「記事本」之類的「純文字編輯器」開啟,例如 .exe,那麼會看到一堆「亂碼」,通常稱這種檔案叫「二進位檔案」。
其實所有的檔案,都是用二進位方式儲存,並沒有純文字檔案或二進位檔案的區別,即使是所謂純文字檔案,也是用二進位方式儲存,例如在檔案中輸入 Justin,若用可以檢視二進位或 16 進位的編輯器來觀看,會看到:
J、u、s、t、i、n 五個字元分別儲存為二進位的位元組(Byte)值,以 16 進位值來顯示就是 4A、75、73、74、69、6E,如果對照 ASCII 表格,4A、75、73、74、69、6E 就是編碼表中的 J、u、s、t、i、n 字元。
所謂純文字文件,不過是編輯器讀取檔案的二進位資料,嘗試對照某個編碼之後,再繪製出對應的字元外觀,那麼,若新增一個所謂「純文字檔案」,那編輯器會預設用哪個編碼來儲存檔案呢?例如方才的 J 這個字元,編輯器為什麼儲存為 4A(1001010)呢?
通常系統內建的編輯器會使用作業系統預設編碼,舉例來說,在正體中文 Windows 中預設的編碼為 MS950
,其中英文字元的編碼相容於 ASCII,所以鍵入英文字元 J,才會儲存為 4A 的位元組,那如果鍵入中文字元呢?例如輸入「測試」,用 16 進位方式來檢視就會是:
在 MS950
編碼中,中文字是用兩個位元組來儲存,MS950
可視為 Big5
的擴充,如果查 Big5
編碼表,會發現 B4FA 就是「測」字的編碼, B8D5 就是「試」字的編碼。
那麼在正體中文 Windows 中,若文字檔案中同時存在著英文字與中文字,那文字編輯器如何知道哪些位元組是英文字的資料,哪兩個位元組要合在一起顯示中文字元呢?
以 Big5
為例,為了與 ASCII 相容,採第一個位元組範圍為 0xA4 至 0xF9,而第二個位元組為 0x40 到 0x7E 以及 0xA1 至 0xFE,兩個位元組來組成一個中文字,讀取時若要先讀到位元組是在 0xA4 至 0xF9,就表示它可能是一個中文字的前半,此時再讀入下一個位元組,然後再依編碼表繪製出中文字,如果先讀到的位元組是在 0xA3 以內,直接用該位元組繪製出文字。下面這個簡單的程式,依以上所述規則,對 Big5
中文字與非中文字作簡單的判斷:
package cc.openhome;
import java.nio.file.Files;
import java.nio.file.Paths;
import static java.lang.System.out;
public class Main {
public static void main(String[] args) throws Exception {
byte[] bytes = Files.readAllBytes(Paths.get("sample.txt"));
int i = 0;
while(i < bytes.length) {
if(notBig5(toInt(bytes[i]))) {
byte[] other = {0, bytes[i]};
i++;
print(other);
} else {
byte[] big5 = {bytes[i], bytes[i + 1]};
i += 2;
print(big5);
}
}
}
private static int toInt(byte b) {
return b & 0x00FF;
}
private static boolean notBig5(int dec) {
return dec < 0xA4 || dec >= 0xF9;
}
private static void print(byte[] bytes) {
out.printf("%-3h", toInt(bytes[0]));
out.printf("%-3h", toInt(bytes[1]));
out.printf("%s%n", new String(bytes));
}
}
如果在正體中文 Windows 下開一個純文字檔案sample.txt,用預設編碼儲存「這T是e個s測t試」的文字,使用十六進位編輯器檢祝會有以下的結果:
使用以上程式會顯示如下結果:
C:\workspace>java cc.openhome.Main sample.txt
b3 6f 這
0 54 T
ac 4f 是
0 65 e
ad d3 個
0 73 s
b4 fa 測
0 74 t
b8 d5 試
可以分別對照看看,圖片與執行結果中的十六進位號碼是相符的。