UTF-8 字元集基礎
字元集簡史
在所有字元集中,最知名可能要數被稱為ASCII的7位字元集了。它是美國資訊交換標準委員會(American Standards Committee for Information Interchange)的縮寫, 為美國英語通訊所設計。它由128個字元組成,包括大小寫字母、數字0-9、標點符號、非列印字元(分行符號、定位字元等4個)以及控制字元(退格、響鈴等)組成。
但是,由於他是針對英語設計的,當處理帶有音調標號(形如漢語的拼音)的歐洲文字時就會出現問題。因此,建立出了一些包括255個字元的由ASCII擴充的字元集。其中有一種通常被成為IBM字元集,它把值為128-255之間的字元用於畫圖和畫線,以及一些特殊的歐洲字元。另一種8位字元集是ISO 8859-1 Latin 1,也簡稱為ISO Latin-1。它把位於128-255之間的字元用於拉丁字母表中特殊語言字元的編碼,也因此而得名。
歐洲語言不是地球上的唯一語言,因此亞洲和非洲語言並不能被8位字元集所支援。僅漢語(或pictograms)字母表就有80000以上個字元。但是把漢語、日語和越南語的一些相似的字元結合起來,在不同的語言裡,使不同的字元代表不同的字,這樣只用2個位元組就可以編碼地球上幾乎所有地區的文字。因此,建立了UNICODE編碼。它通過增加一個高位元組對ISO Latin-1字元集進行擴充,當這些高位元組位為0時,低位元組就是ISO Latin-1字元。UNICODE支援歐洲、非洲、中東、亞洲(包括統一標準的東亞像形漢字和韓國像形文字)。但是,UNICODE並沒有提供對諸如Braille, Cherokee, Ethiopic, Khmer, Mongolian, Hmong, Tai Lu, Tai Mau文字的支援。同時它也不支援如Ahom, Akkadian, Aramaic, Babylonian Cuneiform, Balti, Brahmi, Etruscan, Hittite, Javanese, Numidian, Old Persian Cuneiform, Syrian之類的古老的文字。
事實證明,對可以用ASCII表示的字元使用UNICODE並不高效,因為UNICODE比ASCII佔用大一倍的空間,而對ASCII來說高位元組的0對他毫無用處。為瞭解決這個問題,就出現了一些中間格式的字元集,他們被稱為通用轉換格式,既UTF(Universal Transformation Format)。目前存在的UTF格式有:UTF-7, UTF-7.5, UTF-8, UTF-16, 以及 UTF-32。本文討論UTF-8字元集的基礎。
UTF_8字元集
UTF-8是UNICODE的一種變長字元編碼,由Ken Thompson於1992年建立。現在已經標準化為RFC 3629。UTF-8用1到6個位元組編碼UNICODE字元。如果UNICODE字元由2個位元組表示,則編碼成UTF-8很可能需要3個位元組,而如果UNICODE字元由4個位元組表示,則編碼成UTF-8可能需要6個位元組。用4個或6個位元組去編碼一個UNICODE字元可能太多了,但很少會遇到那樣的UNICODE字元。
UFT-8轉換表表示如下:
UNICODE UTF-8
00000000 - 0000007F 0xxxxxxx
00000080 - 000007FF 110xxxxx 10xxxxxx
00000800 - 0000FFFF 1110xxxx 10xxxxxx 10xxxxxx
00010000 - 001FFFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
00200000 - 03FFFFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
04000000 - 7FFFFFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
實際表示ASCII字元的UNICODE字元,將會編碼成1個位元組,並且UTF-8表示與ASCII字元表示是一樣的。所有其他的UNCODE字元轉化成UTF-8將需要至少2個位元組。每個位元組由一個換碼序列開始。第一個位元組由唯一的換碼序列,由n位1加一位0組成。n位1表示字元編碼所需的位元組數。
樣本
UNICODE uCA(11001010) 編碼成UTF-8將需要2個位元組:
uCA -> C3 8A
1100 1010
110xxxxx 10xxxxxx
1100 1010 -> 110xxxxx 10xxxxxx
-> 110xxxxx 10xxxxx0
-> 110xxxxx 10xxxx10
-> 110xxxxx 10xxx010
-> 110xxxxx 10xx1010
-> 110xxxxx 10x01010
-> 110xxxxx 10001010
-> 110xxxx1 10001010
-> 110xxx11 10001010
-> 11000011 10001010
-> C3 8A
UNICODE uF03F (11110000 00111111) 編碼成UTF-8將需要3個位元組:
u F03F -> EF 80 BF
1111 0000 0011 1111 -> 1110xxxx 10xxxxxx 10xxxxxx
-> 11101111 10000000 10111111
-> EF 80 BF
譯者註:由上分析可以看到,UNCODE到UTF-8的轉換就是先確定編碼所需要的位元組數,然後用UNICODE編碼位從低位到高位依次填入上面表示為x的位上,不足的高位以0補充。以上是個人經驗,如有錯誤,請不惜指教,謝過先:)
UTF-8編碼的優點:
UTF-8編碼可以通過屏蔽位和移位操作快速讀寫。
字串比較時strcmp()和wcscmp()的返回結果相同,因此使排序變得更加容易。
位元組FF和FE在UTF-8編碼中永遠不會出現,因此他們可以用來表明UTF-16或UTF-32文本(見BOM)
UTF-8 是位元組順序無關的。它的位元組順序在所有系統中都是一樣的,因此它實際上並不需要BOM。
UTF-8編碼的缺點:
你無法從UNICODE字元數判斷出UTF-8文本的位元組數,因為UTF-8是一種變長編碼
它需要用2個位元組編碼那些用擴充ASCII字元集只需1個位元組的字元
ISO Latin-1 是UNICODE的子集,但不是UTF-8的子集
8位字元的UTF-8編碼會被email網關過濾,因為internet資訊最初設計為7為ASCII碼。因此產生了UTF-7編碼。
UTF-8 在它的表示中使用值100xxxxx的幾率超過50%, 而現存的實現如ISO 2022, 4873, 6429, 和8859系統,會把它錯認為是C1 控制碼。因此產生了UTF-7.5編碼。
修正的UTF-8:
java使用UTF-16表示內部文本,並支援用於字串序列化的非標準的修正UTF-8編碼。標準UTF-8和修正的UTF-8有兩點不同:
修正的UTF-8中,null字元編碼成2個位元組(11000000 00000000) 而不是標準的1個位元組(00000000),這樣作可以保證編碼後的字串中不會嵌入null字元。因此如果在類C語言中處理字串,文本不會在第一個null字元時截斷(C字串以null結尾)。
在標準UTF-8編碼中,超出基本多語言範圍(BMP - Basic Multilingual Plain)的字元被編碼為4位元組格式,但是在修正的UTF-8編碼中,他們由代理編碼對(surrogate pairs)表示,然後這些代理編碼對在序列中分別重新編碼。結果標準UTF-8編碼中需要4個位元組的字元,在修正後的UTF-8編碼中將需要6個位元組。
位序標誌BOM
BOM(Byte Order Mark)是一個字元,它表明UNICODE文本的UTF-16,UTF-32的編碼位元組順序(高位元組低位元組順序)和編碼方式(UTF-8,UTF-16,UTF-32, 其中UTF-8編碼是位元組順序無關的)。
如下所示:
Encoding Representation
UTF-8 EF BB BF
UTF-16 Big Endian FE FF
UTF-16 Little Endian FF FE
UTF-32 Big Endian 00 00 FE FF
UTF-32 Little Endian FF FE 00 00
UTF-8 C++ 程式編碼樣本:
下面是四個C++函數,他們分別實現2位元組和4位元組UNICODE和UTF-8之間的轉換。
#define MASKBITS 0x3F
#define MASKBYTE 0x80
#define MASK2BYTES 0xC0
#define MASK3BYTES 0xE0
#define MASK4BYTES 0xF0
#define MASK5BYTES 0xF8
#define MASK6BYTES 0xFC
typedef unsigned short Unicode2Bytes;
typedef unsigned int Unicode4Bytes;
void UTF8Encode2BytesUnicode(std::vector< Unicode2Bytes > input,
std::vector< byte >& output)
{
for(int i=0; i < input.size(); i++)
{
// 0xxxxxxx
if(input[i] < 0x80)
{
output.push_back((byte)input[i]);
}
// 110xxxxx 10xxxxxx
else if(input[i] < 0x800)
{
output.push_back((byte)(MASK2BYTES | input[i] >> 6));
output.push_back((byte)(MASKBYTE | input[i] & MASKBITS));
}
// 1110xxxx 10xxxxxx 10xxxxxx
else if(input[i] < 0x10000)
{
output.push_back((byte)(MASK3BYTES | input[i] >> 12));
output.push_back((byte)(MASKBYTE | input[i] >> 6 & MASKBITS));
output.push_back((byte)(MASKBYTE | input[i] & MASKBITS));
}
}
}
void UTF8Decode2BytesUnicode(std::vector< byte > input,
std::vector< Unicode2Bytes >& output)
{
for(int i=0; i < input.size();)
{
Unicode2Bytes ch;
// 1110xxxx 10xxxxxx 10xxxxxx
if((input[i] & MASK3BYTES) == MASK3BYTES)
{
ch = ((input[i] & 0x0F) << 12) | (
(input[i+1] & MASKBITS) << 6)
| (input[i+2] & MASKBITS);
i += 3;
}
// 110xxxxx 10xxxxxx
else if((input[i] & MASK2BYTES) == MASK2BYTES)
{
ch = ((input[i] & 0x1F) << 6) | (input[i+1] & MASKBITS);
i += 2;
}
// 0xxxxxxx
else if(input[i] < MASKBYTE)
{
ch = input[i];
i += 1;
}
output.push_back(ch);
}
}
void UTF8Encode4BytesUnicode(std::vector< Unicode4Bytes > input,
std::vector< byte >& output)
{
for(int i=0; i < input.size(); i++)
{
// 0xxxxxxx
if(input[i] < 0x80)
{
output.push_back((byte)input[i]);
}
// 110xxxxx 10xxxxxx
else if(input[i] < 0x800)
{
output.push_back((byte)(MASK2BYTES | input[i] > 6));
output.push_back((byte)(MASKBYTE | input[i] & MASKBITS));
}
// 1110xxxx 10xxxxxx 10xxxxxx
else if(input[i] < 0x10000)
{
output.push_back((byte)(MASK3BYTES | input[i] >> 12));
output.push_back((byte)(MASKBYTE | input[i] >> 6 & MASKBITS));
output.push_back((byte)(MASKBYTE | input[i] & MASKBITS));
}
// 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
else if(input[i] < 0x200000)
{
output.push_back((byte)(MASK4BYTES | input[i] >> 18));
output.push_back((byte)(MASKBYTE | input[i] >> 12 & MASKBITS));
output.push_back((byte)(MASKBYTE | input[i] >> 6 & MASKBITS));
output.push_back((byte)(MASKBYTE | input[i] & MASKBITS));
}
// 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
else if(input[i] < 0x4000000)
{
output.push_back((byte)(MASK5BYTES | input[i] >> 24));
output.push_back((byte)(MASKBYTE | input[i] >> 18 & MASKBITS));
output.push_back((byte)(MASKBYTE | input[i] >> 12 & MASKBITS));
output.push_back((byte)(MASKBYTE | input[i] >> 6 & MASKBITS));
output.push_back((byte)(MASKBYTE | input[i] & MASKBITS));
}
// 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
else if(input[i] < 0x8000000)
{
output.push_back((byte)(MASK6BYTES | input[i] >> 30));
output.push_back((byte)(MASKBYTE | input[i] >> 18 & MASKBITS));
output.push_back((byte)(MASKBYTE | input[i] >> 12 & MASKBITS));
output.push_back((byte)(MASKBYTE | input[i] >> 6 & MASKBITS));
output.push_back((byte)(MASKBYTE | input[i] & MASKBITS));
}
}
}
void UTF8Decode4BytesUnicode(std::vector< byte > input,
std::vector< Unicode4Bytes >& output)
{
for(int i=0; i < input.size();)
{
Unicode4Bytes ch;
// 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
if((input[i] & MASK6BYTES) == MASK6BYTES)
{
ch = ((input[i] & 0x01) << 30) | ((input[i+1] & MASKBITS) << 24)
| ((input[i+2] & MASKBITS) << 18) | ((input[i+3]
& MASKBITS) << 12)
| ((input[i+4] & MASKBITS) << 6) | (input[i+5] & MASKBITS);
i += 6;
}
// 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
else if((input[i] & MASK5BYTES) == MASK5BYTES)
{
ch = ((input[i] & 0x03) << 24) | ((input[i+1]
& MASKBITS) << 18)
| ((input[i+2] & MASKBITS) << 12) | ((input[i+3]
& MASKBITS) << 6)
| (input[i+4] & MASKBITS);
i += 5;
}
// 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
else if((input[i] & MASK4BYTES) == MASK4BYTES)
{
ch = ((input[i] & 0x07) << 18) | ((input[i+1]
& MASKBITS) << 12)
| ((input[i+2] & MASKBITS) << 6) | (input[i+3] & MASKBITS);
i += 4;
}
// 1110xxxx 10xxxxxx 10xxxxxx
else if((input[i] & MASK3BYTES) == MASK3BYTES)
{
ch = ((input[i] & 0x0F) << 12) | ((input[i+1] & MASKBITS) << 6)
| (input[i+2] & MASKBITS);
i += 3;
}
// 110xxxxx 10xxxxxx
else if((input[i] & MASK2BYTES) == MASK2BYTES)
{
ch = ((input[i] & 0x1F) << 6) | (input[i+1] & MASKBITS);
i += 2;
}
// 0xxxxxxx
else if(input[i] < MASKBYTE)
{
ch = input[i];
i += 1;
}
output.push_back(ch);
}
}