正確理解UNICODE UTF
如果你開發過的軟體項目中涉及到多語言支持的問題,那麼相信你沒少碰到過亂碼問題,然後在尋求解決問題的途徑過程中被一些概念如ASCII, ISO-8859-1, Unicode,UTF-8,GBK,GB2312等等所困擾。本文有助於你正確的理解這些概念。
1. ASCII
用7位編碼將英文字元和一些常用的符號存諸為從0到127的數值。
2. ISO-8859-1
法語、西班牙語和德語之類的西歐語言都使用叫做ISO-8859-1的編碼系統(也叫做「latin-1」)。它使用7位ASCII字元表示從0到127的字元,但接著擴展到了128-255的範圍來表示如n上帶有一個波浪線(241),和u上帶有兩個點(252)的字元等等。可以說ASCII是ISO-8859-1的子集。
3. Unicode
Unicode用一個2位元組數字表示每個字元,從0到65535。每個 2 位元組數字表示至少在一種世界語言中使用的一個唯一字元。(在多種語言中都使用的字元具有相同的數字碼。)這樣就確保每個字元一個數字,並且每個數字一個字元。Unicode數據永遠不會模稜兩可。Unicode使用相同的數字表示ASCII和ISO-8859-1中的字元。只是這兩種編碼用一個位元組表示,而Unicode用兩個位元組表示。所以Unicode表示這兩種編碼的字元時只要用低位元組就可以了,高位元組為0。
4. UTF-8
UTF-8是一種變長的編碼方式,每個UTF-8的編碼可以是1至6個位元組長。它將Unicode編碼的字元採用變長的方式進行編碼。對Unicode中屬於ISO-8859-1的編碼採用和ISO-8859-1相同的單位元組編碼。其他字元採用兩位元組以上的編碼。實際上對於兩個位元組的Unicode編碼,UTF-8隻要三個位元組即可表示。第一個位元組由n個1(1< n <= 6)開始, n表示編碼的位元組數,後面每個位元組都以10開始,後面6位為有效位。將第一位的剩餘位和後面的所有位元組的後六位連接起來就是對應的Unicode編碼的數值。例如漢字「中」的編碼:
Unicode: 4E 2D
01001110 00101101
UTF-8: E4 B8 AD
11100100 10111000 10101101
可以通過以下方式進行證實:
用記事本創建一個文本文件,輸入漢字「中」分別保存為Unicode格式和UTF-8格式。 將UltraEdit的自動識別UTF-8文件格式選項禁止,然後用其打開這兩個文件,選用二進位查看方式,可以看到:
UTF-8格式文件編碼為「EF BB BF E4 B8 AD」。其中有個三位元組的前綴「EF BB BF」,這是UTF-8格式文本文件的標識。不過這個前綴不要,某些文本查看軟體也可以通過編碼判斷出UTF-8格式。「E4 B8 AD」就是「中」的UTF-8編碼。
Unicode格式的文件的完整編碼是「FF FE 2D 4E」。前面有個雙位元組前綴「FF FE」,這是Unicode格式文本文檔的編碼標識。而我們看到的編碼是「2D 4E」,而不是如我前面所說的「4E 2D」,為什麼呢?因為數字是按照低位元組在先高位元組在後的順序存儲的,所以實際的Unicode編碼恰恰是「4E2D」。
5. GB2312和GBK
這兩種編碼都是漢字的編碼標準,其中前者是後者的子集。GBK編碼的文本文檔中對於ASCII中的字元用相同的單位元組表示;對於漢字和漢語中的標點符號等採用雙位元組編碼,其中高位元組大於0x80,而ASCII的所有字元的編碼均小於0x80, 所以ASCII和GBK的字元是可以混和起來的。GBK的字符集和Unicode進行轉換是無規則的,需要轉換表才能進行轉換。
6.下面給出我用Java語言寫的Unicode和UTF-8轉換的程序片段,供大家參考。
因為Java語言的字元使用Unicode編碼的,所以程序中是對UTF-8編碼的字元串的位元組數組和Java的String類型進行轉換。String對象中的每一個Character就是一個Unicode編碼的字元。
public String utf8Bytes2String(byte[] buff){
if(buff == null)
return null;
StringBuffer sb = new StringBuffer();
int idx = 0;
if(buff[0] == (byte)0xEF &&
buff[1] == (byte)0xBB &&
buff[2] == (byte)0xBF)
idx = 3;//Skip UTF8 header
while(idx < buff.length){
int hB = buff[idx] & 0xFF;
int bCnt = 0;
int check = 0x80;
for(int i=0; i<8; i++){
if((hB & check) != 0){
bCnt ++;
check >>= 1;
}else
break;
}
if(bCnt <= 1){
char c = 0;
c |= buff[idx] & 0xFF;
sb.append(c);
idx++;
}else if(bCnt == 2){
char c = 0;
c |= buff[idx] & 0x03;
c <<= 6;
if((buff[idx+1] & 0xC0) != 0x80)
return null;
c |= buff[idx+1] & 0x
idx += 2;
sb.append(c);
}else if(bCnt == 3){
char c = 0;
c |= buff[idx] & 0x
c <<= 6;
if((buff[idx+1] & 0xC0) != 0x80)
return null;
c |= buff[idx+1] & 0x
c <<= 6;
if((buff[idx+2] & 0xC0) != 0x80)
return null;
c |= buff[idx+2] & 0x
idx += 3;
sb.append(c);
}else
return null;
}
return sb.toString();
}
public byte[] string2Utf8Bytes(String str){
if(str == null)
return null;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
string2Utf8Stream(str, bos);
} catch (IOException e) {
e.printStackTrace();
}
return bos.toByteArray();
}
public void string2Utf8Stream(String str, OutputStream os) throws IOException {
if(str == null || os == null)
return;
for(int i=0; i<str.length(); i++){
char c = str.charAt(i);
if(c < 0x80){
os.write((byte)c);
}else if(c >=0x80 && c < 0x100){
int hi = c >> 6;
hi |= 0xC0;
int lo = c & 0x
lo |= 0x80;
os.write(hi);
os.write(lo);
}else{
int first = c >> 12;
first |= 0xE0;
int second = c >> 6;
second &= 0x
second |= 0x80;
int third = c & 0x
third |= 0x80;
os.write(first);
os.write(second);
os.write(third);
}
}
}
推薦閱讀:
※如何理解「飯後服藥」
※如何理解兩個數的最大公因數和最小公倍數的關係?
※如何理解貼現現金流的估值方法?
※*最難理解*的*就是人類* [圖片] [Flash]
TAG:理解 |