Encoding Gossip: Unicode 與 UTF

来源:互联网 发布:matlab 矩阵拼接 编辑:程序博客网 时间:2024/05/16 10:05

http://caterpillar.onlyfun.net/Gossip/Encoding/UnicodeUTF.html


由於Big5用了第一個位元組的某個範圍來作為識別是否為中文字,可儲存的文字範圍就大大減少(約為一萬九千多個),如果你儲存的字元,不在Big5/MS950編碼範圍內,那會如何?如果是Windows中的記事本程式,會出現以下的提示,要你轉存為Unicode格式:


在你按下「取消」後,會出現「另存新檔」的對話框,在右下會有個「編碼」選項,下拉的話,會有Unicode、Unicode big endian與UTF-8三個選項。

Unicode是由The UnicodeConsortium非營利組織所主導的編碼標準,給予每個字元唯一的編碼,在表達時,於「U+」之後接著一組十六進位數字來表示編碼數字,如果使用四個十六進位制數字,可以表達六萬多個字元,如果使用五或六個十六進位制,則可以表達更多的字元。

Unicode是指編碼方式,一個文字的Unicode編碼是固定的,但如何用位元組來表達則視目的而有不同的實作方式,Unicode的實作方式就稱為Unicode/UCS Transformation Format,簡稱UTF

如果你直接將方才的範例選擇「Unicode」儲存,用十六進位檢視,你會看到:


「犇」這個字的Unicode編碼是U+7287,對於Windows的記事本若選擇使用「Unicode」儲存,則使用兩個位元組來儲存,Windows的記事本選擇「Unicode」選項時,實際上採用UCS-2/UTF-16
儲存,一開頭的兩個位元組(ff fe)是用來識別檔案採用的位元組順序,稱為BOM(byte order mark),之後使用兩個位元組來儲存每個Unicode字元。

要注意的是,
「犇」這個字的Unicode編碼是U+7287,實際儲存時的位元組卻是87、72,也就是說,Windows記事本選擇「Unicode」儲存兩個以上位元組的資料時,是先存低位元組,再存高位元組,這樣的儲存方式,是採Little Endian 的方式,也就是Windows記事本選擇「Unicode」時,實際上採用的是UCS-2/UTF-16 Little Endian。

Unicode指定BOM編碼為U+FEFF。
如果讀取案開頭的BOM順序是0xfeff,表示檔案採用Big Endian,如果讀取案開頭的BOM順序是0xfffe,表示檔案採用Little Endian

如果使用Windows記事本儲存時的選項是「
Unicode big endian」,同樣儲存「犇」,結果會如下:


可以看到,用來表示為Unicode檔案的兩個位元組為fe、ff,與先前的ff、fe相反,而「犇」這個字現在儲存為72、87,與先前的87、72也是相反。如果儲存兩個以上位元組資料時,採先存高位元,再存低位元方式,則這樣的儲存方式,是採Big Endian 的方式。

這會有什麼影響?如果你使用以下的Java程式來讀取一個存有「這T是e個s測t試」的文字檔案,而文字檔案採Windows記事本中的「Unicode」選項儲存,那麼你會得到亂碼:

import java.io.*;public class Main {        public static void main(String[] args) throws Exception {        FileInputStream in = new FileInputStream(args[0]);        byte[] data = new byte[2];        while(in.read(data) != -1) {            int hex = ((data[0] << 8) | (data[1] & 0xFF)) & 0XFFFF;            System.out.printf("%h ", hex);             System.out.println(new String(data, "UTF-16"));        }        in.close();    }}

C:\workspace>java Main sample.txt
fffe
1990 ?
5400 ?
2f66 ?
6500 攀
b50 ?
7300 猀
2c6e ?
7400 琀
668a 暊


如果
存有「這T是e個s測t試」的文字檔案,是使用Windows記事本「Unicode big endian」選項儲存,也就是採UCS-2/UTF-16 Big Endian,則以上程式可以得到正確的文字顯示:
C:\workspace>java Main sample.txt
feff
9019 這
54 T
662f 是
65 e
500b 個
73 s
6e2c 測
74 t
8a66 試


這是因為JVM本身是採Big Endian的方式來處理位元組,對於一個文字檔案採Windows記事本中的「Unicode」選項儲存,則是採Little Endian,用以上的程式讀取當然會是亂碼,如果要正確讀取Windows記事本中的「Unicode」選項儲存的文字檔案,方法之一是在Java中要自行調換位元組順序:
import java.io.*;public class Main {        public static void main(String[] args) throws Exception {        FileInputStream in = new FileInputStream(args[0]);        byte[] data = new byte[2];        byte[] bigEndian = new byte[2];        while(in.read(data) != -1) {            int hex = ((data[0] << 8) | (data[1] & 0xFF)) & 0XFFFF;            System.out.printf("%h ", hex);             bigEndian[0] = data[1];            bigEndian[1] = data[0];            System.out.println(new String(bigEndian, "UTF-16"));        }        in.close();    }}

或者是指定使用UTF-16LE,例如:
import java.io.*;public class Main {        public static void main(String[] args) throws Exception {        FileInputStream in = new FileInputStream(args[0]);        byte[] data = new byte[2];        while(in.read(data) != -1) {            int hex = ((data[0] << 8) | (data[1] & 0xFF)) & 0XFFFF;            System.out.printf("%h ", hex);             System.out.println(new String(data, "UTF-16LE"));        }        in.close();    }}

這兩個程式,都可以正確讀取,例如:
C:\workspace>java Main sample.txt
fffe
1990 這
5400 T
2f66 是
6500 e
b50 個
7300 s
2c6e 測
7400 t
668a 試


有關於UTF-8,則在 下一篇 再繼續說明....XD