小結字符集及字符編碼問題

原文轉自我的個人博客——溫馨咖啡小屋


字符編碼問題一直深深困擾着我~無論是網頁還是數據庫抑或是單純的文件字符流,總有各種奇怪的編碼問題。之所以稱之爲奇怪其實主要還是因爲我對於編碼的知識瞭解太淺。近來深刻覺醒編碼問題非解決不行,故將所閱讀的資料內容概括整理如下。



字符編碼與字符集


一直以來我常常把字符編碼和字符集混着說,而周圍的人大多也都不區分它們的含義。不過真要較真的話,字符編碼和字符集其實還是很有區別的。


當然,從簡單字符集的角度來說,按照慣例,人們認爲字符集和字符編碼是同義詞,因爲使用同樣的標準來定義提供什麼字符並且這些字符如何編碼到一系列的代碼單元(通常一個字符一個單元)。


但是對於由統一碼和通用字符集所構成的現代字符編碼模型來說,這些概念之間有了細微的區別。它們將字符編碼的概念分爲:有哪些字符、它們的編號、這些編號如何編碼成一系列的“碼元”(有限大小的數字)以及最後這些單元如何組成八位字節流。區分這些概念的核心思想是建立一個能夠用不同方法來編碼的一個通用字符集。爲了正確地表示這個模型需要更多比“字符集”和“字符編碼”更爲精確的術語表示。現代模型中所用的術語有字符集(Character Set)、編碼字符集(CCS:Coded Character Set)、字符編碼表(CEF:Character Encoding Form)、字符編碼方案(CES:Character Encoding Scheme)等。這裏的定義比較學術,大家感興趣的可以自己查找維基百科,鏈接在本文後面的參考資料裏可以找到。爲了好記,我又找到一些比較通俗的關於字符集與字符編碼區別的說法:


字符集:字符的集合,規定了在這些集合裏面有哪些字符。比如Unicode字符集,目標就是收納了這個世界上所有語言的文字、符號等。


字符編碼:就是規定用一個字節還是用多個字節來存儲一個字符,用固定的二進制碼值表示某個字符。注意,字符集只是規定了有哪些字符,而最終決定採用哪些字符,每一個字符用多個字節表示等問題,則是由編碼來決定的。像Unicode字符集的編碼方式有很多,諸如UTF-8、UFT-16、UTF-32等。


字符編碼舉例


要解決編碼問題,首先要明確究竟都有哪些編碼,它們有什麼樣的特點,相互之間有何種關係。這樣使用起來才能夠有的放矢。


在《Java編程思想》一書中,作者Bruce Eckel就是通過講述文件輸入輸出流發展歷史的方式清晰地展示了Java IO包中的各個stream、reader和writer該如何使用。我個人深感這是一種很好的學習方法,所以這裏借鑑一下,也儘量按照字符編碼的發展歷史來介紹各個編碼,這樣我們就很容易明白這種編碼爲什麼會誕生,以及它的特性了。


首先先解釋一下“字符”與“字節”的區別:


字節(octet):是一個8位的物理存貯單元,取值範圍一定是0~255。


字符(character):是一個文化相關的符號,或說是一個語言上的符號,如“中”字就是一個字符。字符所佔的大小由其編碼方式解決,比如“中”在UTF-8中佔3個字節(0xE4A8AD),而在GBK中,則佔兩個字節(0xD6D0)。


1. 內碼


內碼是操作系統內部所採用的字符編碼,並不特指某種編碼。比如早期的DOS採用的是ASCII編碼,而現在的操作系統大都採用Unicode編碼。


2. ASCII碼


ASCII編碼全稱爲American Standard Code for Information Interchange(美國信息交換標準代碼)。對應的ISO 標準爲ISO646標準。


ASCII字符集:主要包括控制字符(回車鍵、退格、換行鍵等),可顯示字符(英文大小寫字符、阿拉伯數字和西文符號)。基本的ASCII字符集共有128個字符,其中有96個可打印字符,包括常用的字母、數字、標點符號等,另外還有32個控制字符。


ASCII編碼:將ASCII字符集轉換爲計算機可以接受的數字系統,使用7位(bits)表示一個字符,共128字符。每個ASCII碼以1個字節(Byte)儲存,從0(二進制:0000 0000)到127(二進制:0111 1111)代表不同的常用符號。例如大寫A的ASCII碼是65(二進制:0100 0001),小寫a則是97(二進制:0110 0001)。雖然標準ASCII碼是7位編碼,但由於計算機基本處理單位爲字節(1byte = 8bit),所以一般仍以一個字節來存放一個ASCII字符。每個字節中多餘出來的一位(最高位)在計算機內部通常保持爲0(在數據傳輸時可用作奇偶校驗位)。


ASCII碼的缺點:只能顯示26個基本拉丁字母、阿拉伯數字和英式標點符號,因此只能用於顯示現代美國英語(而且在處理英語當中的外來詞如naïve、café、élite等等時,所有重音符號都不得不去掉,即使這樣做會違反拼寫規則)。


3. 擴展ASCII碼(Extended ASCII)


由於ASCII碼只用了字節的七位,最高位並不使用,所以後來又將最高的一位也編入這套編碼中,成爲八位的擴展ASCII碼。對應的標準爲ISO2022標準,它規定了在保持與 ISO646兼容的前提下將ASCII字符集擴充爲8位代碼的統一方法。


擴展ASCII字符集:ISO陸續制定了一批適用於不同地區的擴充ASCII字符集,每種擴充ASCII 字符集分別可以擴充128個字符,這些擴充字符的編碼均爲高位爲1的8位代碼(即十進制數128~255)。


擴展ASCII碼:爲了表示更多的歐洲常用字符對ASCII進行了擴展,ASCII擴展字符集使用8位(bits)表示一個字符,共256字符。


EASCII碼的缺點:雖然解決了部份西歐語言的顯示問題,但對更多其他語言依然無能爲力。


4. 區位碼


每個漢字或圖形符號分別用兩位的十進制區碼(行碼)和兩位的十進制位碼(列碼)表示,不足的地方補0,組合起來就是區位碼。比如“啊”的區位碼是1601,寫成16進制是0x10,0x01(二進制:00010000 00000001)。


不過這和計算機廣泛使用的ASCII編碼衝突。爲了兼容00-7f的 ASCII編碼,我們在區位碼的高、低字節上分別加上0xA0(二進制:10100000)。這樣“啊”的編碼就成爲0xB0A1。我們將加過兩個0xA0的編碼也稱爲GB2312編碼,雖然 GB2312的原文根本沒提到這一點。


5. 國標碼


把區位碼按一定的規則轉換成的二進制代碼叫做信息交換碼(簡稱國標碼)。


國標碼共有漢字6763個(一級漢字,是最常用的漢字,按漢語拼音字母順序排列,共3755個;二級漢字,屬於次常用漢字,按偏旁部首的筆劃順序排列,共3008個),數字、字母、符號等682個,共7445個。


漢字信息在計算機內部也是以二進制方式存放。由於漢字數量多,用一個字節的128種狀態不能全部表示出來,因此在1980年我國頒佈的《信息交換用漢字編碼字符集——基本集》,即國家標準GB2312-80方案中規定用兩個字節的十六位二進制表示一個漢字,每個字節都只使用低7位(與ASCII碼相同), 即有128×128=16384種狀態。由於ASCII碼的34個控制代碼在漢字系統中也要使用,爲不致發生衝突,不能作爲漢字編碼,128除去34只剩 94種,所以漢字編碼表的大小是94×94=8836,用以表示國標碼規定的7445個漢字和圖形符號。


具體詳見後面的GB2312(80)中。


6. 機內碼(漢字內碼,內碼)


由於國標碼不能直接存儲在計算機內,爲方便計算機內部處理和存儲漢字,又區別於ASCII碼,將國標碼中的每個字節在最高位改設爲1,這樣就形成了在計算機內部用來進行漢字的存儲、運算的編碼叫機內碼。內碼既與國標碼有簡單的對應關係,易於轉換,又與ASCII碼有明顯的區別,且有統一的標準(內碼是惟一的)。


7. 漢字外碼(漢字輸入碼)


無論是區位碼或國標碼都不利於輸入漢字,爲方便漢字的輸入而制定的漢字編碼,稱爲漢字輸入碼。漢字輸入碼屬於外碼。不同的輸入方法,形成了不同的漢字外碼。常見的輸入法有以下幾類:


(1)按漢字的排列順序形成的編碼(流水碼):如區位碼;


(2)按漢字的讀音形成的編碼(音碼):如全拼、簡拼、雙拼等;


(3)按漢字的字形形成的編碼(形碼):如五筆字型、鄭碼等;


(4)按漢字的音、形結合形成的編碼(音形碼):如自然碼、智能ABC。


輸入碼在計算機中必須轉換成機內碼,才能進行存儲和處理。


8. 漢字字形碼


爲了將漢字在顯示器或打印機上輸出,把漢字按圖形符號設計成點陣圖,就得到了相應的點陣代碼(字形碼)。


漢字字庫:全部漢字字形碼的集合叫漢字字庫。漢字字庫可分爲軟字庫和硬字庫。軟字庫以文件的形式存放在硬盤上,現多用這種方式,硬字庫則將字庫固化在一個單獨的存儲芯片中,再和其它必要的器件組成接口卡,插接在計算機上,通常稱爲漢卡。


顯示字庫:用於顯示的字庫叫顯示字庫。顯示一個漢字一般採用16×16點陣或24×24點陣或48×48點陣。已知漢字點陣的大小,可以計算出存儲一個漢字所需佔用的字節空間。例:用16×16點陣表示一個漢字,就是將每個漢字用16行,每行16個點表示,一個點需要1位二進制代碼,16個點需用16位二進制代碼(即2個字節),共16行,所以需要16行×2字節/行=32字節,即16×16點陣表示一個漢字,字形碼需用32字節。即:字節數=點陣行數×點陣列數/8。


打印字庫:用於打印的字庫叫打印字庫,其中的漢字比顯示字庫多,而且工作時也不像顯示字庫需調入內存。


9. 代碼頁


所謂代碼頁(code page)就是針對一種語言文字的字符編碼。例如GBK的code page是CP936,BIG5的code page是CP950,GB2312的code page是CP20936。


Windows中有缺省代碼頁的概念,即缺省用什麼編碼來解釋字符。例如Windows的記事本打開了一個文本文件,裏面的內容是字節流:BA、BA、 D7、D6。Windows應該去怎麼解釋它呢?是按照Unicode編碼解釋、還是按照GBK解釋、還是按照BIG5解釋,還是按照ISO-8859-1去解釋?如果按GBK去解釋,就會得到“漢字”兩個字。按照其它編碼解釋,可能找不到對應的字符,也可能找到錯誤的字符。所謂“錯誤”是指與文本作者的本意不符,這時就產生了亂碼。


答案是Windows按照當前的缺省代碼頁去解釋文本文件裏的字節流。缺省代碼頁可以通過控制面板的區域選項設置。記事本的另存爲中有一項ANSI,其實就是按照缺省代碼頁的編碼方法保存。


Windows的內碼是Unicode,它在技術上可以同時支持多個代碼頁。只要文件能說明自己使用什麼編碼,用戶又安裝了對應的代碼頁,Windows就能正確顯示,例如在HTML文件中就可以指定charset。


有的HTML文件作者,特別是英文作者,認爲世界上所有人都使用英文,在文件中不指定charset。如果他使用了0x80-0xff之間的字符,中文Windows又按照缺省的GBK去解釋,就會出現亂碼。這時只要在這個html文件中加上指定charset的語句,例如:


<meta http-equiv=”Content-Type” content=”text/html; charset=ISO8859-1″>


如果原作者使用的代碼頁和ISO8859-1兼容,就不會出現亂碼了。


10. GB2312(80)


GB2312是漢字字符集和編碼的代號,中國國家標準簡體中文字符集,中文全稱爲“信息交換用漢字編碼字符集-基本集”,又稱GB0,由中華人民共和國國家標準總局發佈,一九八一年五月一日實施。GB 是“國標” 二字的漢語拼音縮寫。


GB2312字符集:只收錄簡化字漢字,以及一般常用字母和符號,主要通行於中國大陸地區和新加坡等地。在這些編碼裏,我們還把數學符號、羅馬希臘的字母、日文的假名們都編進去了,連在 ASCII 裏本來就有的數字、標點、字母都統統重新編了兩個字節長的編碼,這就是常說的”全角”字符,而原來在127號以下的那些就叫”半角”字符了。GB2312採用2個字節,共收錄有7445個字符,其中簡化漢字6763個,字母和符號682個。GB2312 將所收錄的字符分爲94個區,編號爲01區至94區;每個區收錄94個字符,編號爲01位至94位。GB2312的每一個字符都由與其唯一對應的區號和位號所確定。例如:漢字“啊”,編號爲16區01位。GB2312 字符集的區位分佈表:


GB2312字符編碼:GB2312是對 ASCII碼的中文擴展,規定:一個小於127的字符的意義與原來相同,但兩個大於127的字符連在一起時,就表示一個漢字,前面的一個字節(稱之爲高字節)從0xA1用到 0xF7(由字符的區號值加上32而形成),後面一個字節(低字節)從0xA1到0xFE(由字符的位號值加上32而形成),這樣我們就可以組合出大約7000多個簡體漢字了。GB2312原始編碼是對所收錄的每個字符都用兩個字節(byte)表示。例如:漢字“啊”,編號爲16區01位。它的高字節爲16 + 32 = 48(0x30),低字節爲01 + 32 = 33(0x21),合併而成的編碼爲0x3021。在區位號值上加32的原因大慨是爲了避開低值字節區間。由於 GB2312 原始編碼與 ASCII 編碼的字節有重疊,現在通行的GB2312編碼是在原始編碼的兩個字節上各加128修改而形成。例如:漢字“啊”,編號爲16區01位。它的原始編碼爲0x3021,通行編碼爲0xB0A1(總體看其實是在區位碼的高字節和低字節上各加上0xA0)。如果不另加說明,GB2312 常指這種修改過的編碼。


GB2312 字符集是Unicode字符集的一個子集。這也就是說,GB2312 所收錄的每一個字符都收錄在Unicode之中。但是GB2312編碼和Unicode編碼確沒有什麼相同之處。同一個漢字,它的GB2312編碼和Unicode編碼確毫不相同。例如:漢字“啊”,它的GB2312編碼爲0xB0A1,但是它的Unicode編碼爲0x554A。


11. GBK


微軟利用GB 2312-80未使用的編碼空間,收錄GB 13000.1-93全部字符制定了GBK編碼。根據微軟資料,GBK是對GB2312-80的擴展,也就是CP936字碼表(Code Page 936)的擴展(之前CP936和GB 2312-80一模一樣),最早實現於Windows 95簡體中文版。雖然GBK收錄GB 13000.1-93的全部字符,但編碼方式並不相同。GBK自身並非國家標準,只是曾由國家技術監督局標準化司、電子工業部科技與質量監督司公佈爲”技術規範指導性文件”。原始GB13000一直未被業界採用,後續國家標準GB18030技術上兼容GBK而非GB13000。


GBK字符集:GBK也採用2個字節,包括了GB2312的所有內容,同時又增加了近20000個新的漢字(包括幾乎所有的Big5中的繁體漢字)和符號。總共21003個字符。


GBK字符編碼:因爲漢字太多,GB2312不夠用,於是乾脆不再要求低字節一定是127號之後的內碼,只要第一個字節是大於127就固定表示這是一個漢字的開始,不管後面跟的是不是擴展字符集裏的內容。結果擴展之後的編碼方案被稱爲GBK 標準。


12. GB18030


全稱爲國家標準GB 18030-2005《信息技術 中文編碼字符集》,是中華人民共和國現時最新的內碼字集,是GB 18030-2000《信息技術 信息交換用漢字編碼字符集 基本集的擴充》的修訂版。與GB 2312-1980完全兼容,與GBK基本兼容,支持GB 13000及Unicode的全部統一漢字。


GB18030字符集:爲了加入幾千個新的少數民族的字,GBK擴成了GB18030。


GB18030字符編碼:它採用了變長的編碼方式,有1、2、4個字節的編碼長度。1個字節編碼與ASCII兼容,2個字節編碼與GBK兼容,4個字節主要是收錄了少數民族的文字等。


GB18030現在是國家非手持/非嵌入式設備的強制性標準。所有的Unicode編碼都可以轉換爲GB18030,而且GB18030除了兼容GBK以及Unicode的BMP部分外,其餘的Unicode擴展平面和它的4字節擴展平面都是簡單直接的映射。其具體映射關係的計算參見《GB18030編碼研究以及GBK、GB18030與Unicode的映射》:[http://blog.csdn.net/fmddlmyy/archive/2008/04/13/2288312.aspx]


GB 18030主要有以下特點:


(1)與UTF-8相同,採用多字節編碼,每個字可以由1個、2個或4個字節組成。


(2)編碼空間龐大,最多可定義161萬個字符。


(3) 支持中國國內少數民族的文字,不需要動用造字區。


(4)漢字收錄範圍包含繁體漢字以及日韓漢字


13. DBCS


GB2312、GBK、GB18030等一系列漢字編碼的標準被通稱叫做“DBCS”(Double Byte Charecter Set 雙字節字符集)。在DBCS系列標準裏,最大的特點是兩字節長的漢字字符和一字節長的英文字符並存於同一套編碼方案裏,因此他們寫的程序爲了支持中文處理,必須要注意字串裏的每一個字節的值,如果這個值是大於127的,那麼就認爲一個雙字節字符集裏的字符出現了。


14. BIG5


BIG5字符集又稱爲大五碼或五大碼,是使用繁體中文(正體中文)社區中最常用的電腦漢字字符集標準,共收錄13,060個漢字。


BIG5字符集:它是臺灣繁體字集,共包括國標繁體漢字13053個。中文碼分爲中文內碼及交換碼兩類,Big5屬中文內碼,知名的中文交換碼有CCCII、CNS11643。Big5雖普及於臺灣、香港與澳門等繁體中文通行區,但長期以來並非當地的國家標準,而只是業界標準。倚天中文系統、Windows等主要系統的字符集都是以Big5爲基準,但廠商又各自增加不同的造字與造字區,派生成多種不同版本。2003年,Big5被收錄到CNS11643中文標準交換碼的附錄當中,取得了較正式的地位。這個最新版本被稱爲Big5-2003。


BIG5字符編碼:Big5碼是一套雙字節字符集,使用了雙八碼存儲方法,以兩個字節來安放一個字。第一個字節稱爲”高位字節”,第二個字節稱爲”低位字節”。”高位字節”使用了0x81-0xFE,”低位字節”使用了0x40-0x7E,及0xA1-0xFE。在Big5的分區中:


15. ANSI


ANSI是美國國家標準局的縮寫,這裏用來指代一類編碼。使用2個字節來代表一個字符的各種漢字延伸編碼方式,稱爲ANSI編碼。比如,在簡體中文系統下,ANSI編碼代表GB2312編碼;在日文操作系統下,ANSI編碼代表JIS編碼。


非英文系的國家爲了顯示自家的文字,不得不一開始就得面對字符編碼的問題,不同國家不同地區都創建了自己的編碼標準。像是中國大陸是GB2312及後來的GBK,臺灣是BIG5,日本是JIS。ASCII字符集,以及這些由此派生併兼容的字符集稱爲ANSI字符集。


16. ISO-8859-1


ISO 8859,全稱ISO/IEC 8859,是國際標準化組織(ISO)及國際電工委員會(IEC)聯合制定的一系列8位字符集的標準,現時定義了15個字符集。


ISO 8859字符集:ASCII收錄了空格及94個“可印刷字符”,足以給英語使用。但是,其他使用拉丁字母的語言(主要是歐洲國家的語言),都有一定數量的變音字母,故可以使用ASCII及控制字符以外的區域來儲存及表示。除了使用拉丁字母的語言外,使用西裏爾字母的東歐語言、希臘語、泰語、現代阿拉伯語、希伯來語等,都可以使用這個形式來儲存及表示。


* ISO 8859-1 (Latin-1) – 西歐語言;


* ISO 8859-2 (Latin-2) – 中歐語言;


* ISO 8859-3 (Latin-3) – 南歐語言。世界語也可用此字符集顯示;


* ISO 8859-4 (Latin-4) – 北歐語言;


* ISO 8859-5 (Cyrillic) – 斯拉夫語言;


* ISO 8859-6 (Arabic) – 阿拉伯語;


* ISO 8859-7 (Greek) – 希臘語;


* ISO 8859-8 (Hebrew) – 希伯來語(視覺順序);


* ISO 8859-8-I – 希伯來語(邏輯順序);


* ISO 8859-9 (Latin-5 或 Turkish) – 它把Latin-1的冰島語字母換走,加入土耳其語字母;


* ISO 8859-10 (Latin-6 或 Nordic) – 北日耳曼語支,用來代替Latin-4;


* ISO 8859-11 (Thai) – 泰語,從泰國的 TIS620 標準字集演化而來;


* ISO 8859-13 (Latin-7 或 Baltic Rim) – 波羅的語族;


* ISO 8859-14 (Latin-8 或 Celtic) – 凱爾特語族;


* ISO 8859-15 (Latin-9) – 西歐語言,加入Latin-1欠缺的法語及芬蘭語重音字母,以及歐元符號;


* ISO 8859-16 (Latin-10) – 東南歐語言。主要供羅馬尼亞語使用,並加入歐元符號;


很明顯,iso8859-1編碼表示的字符範圍很窄,無法表示中文字符。但是,由於是單字節編碼,和計算機最基礎的表示單位一致,所以很多時候,仍舊使用iso8859-1編碼來表示。而且在很多協議上,默認使用該編碼。


ISO 8859字符編碼:我們知道ASCII碼是從0x00(二進制:0000 0000)到0x7F(二進制:0111 1111),也就是還有1位沒有用到,ISO 8859-1就是在空置的0xA0-0xFF(二進制:1010 0000-1111 1111)的範圍內,加入192個字母及符號,藉以供使用變音符號的拉丁字母語言使用。所以ISO 8859-1又稱Latin-1。


17. Unicode(統一碼、萬國碼、單一碼)


全稱爲Universal Multiple-Octet Coded Character Set,簡稱UCS(通用字符集-Universal Character Set)。


歷史上存在兩個獨立的嘗試創立單一字符集的組織,即國際標準化組織(ISO)和多語言軟件製造商組成的統一碼聯盟(http://www.unicode.org)。前者開發的ISO/IEC 10646項目,後者開發的統一碼項目。1991年前後,兩個項目的參與者都認識到,世界不需要兩個不兼容的字符集。於是,它們開始合併雙方的工作成果,併爲創立一個單一編碼表而協同工作。從Unicode 2.0開始,Unicode採用了與ISO 10646-1相同的字庫和字碼;ISO也承諾,ISO 10646將不會替超出U+10FFFF的UCS-4編碼賦值,以使得兩者保持一致。兩個項目仍都存在,並獨立地公佈各自的標準。但統一碼聯盟和ISO/IEC JTC1/SC2都同意保持兩者標準的碼錶兼容,並緊密地共同調整任何未來的擴展。在發佈的時候,Unicode一般都會採用有關字碼最常見的字型,但ISO 10646一般都儘可能採用Century字型。


Unicode的編碼方式與ISO 10646的通用字符集(UCS)概念相對應,目前的用於實用的Unicode版本對應於UCS-2,使用16位的編碼空間。也就是每個字符佔用2個字節,總共可以組合出65535個不同的字符,這大概已經可以覆蓋世界上所有文化的符號。實際上目前版本的Unicode尚未填滿這16位編碼,保留了大量空間作爲特殊使用或將來擴展。如果還不夠也沒有關係,ISO已經準備了UCS-4方案,說簡單了就是四個字節來表示一個字符,這樣我們就可以組合出21億個不同的字符出來(最高位有其他用途)。


由ISO制定的ISO 10646(或稱ISO/IEC 10646)標準所定義的字符編碼方式,採用4字節編碼。UCS包含了已知語言的所有字符。除了拉丁語、希臘語、斯拉夫語、希伯來語、阿拉伯語、亞美尼亞語、格魯吉亞語,還包括中文、日文、韓文這樣的象形文字,UCS還包括大量的圖形、印刷、數學、科學符號。


* UCS-2: 與Unicode的2byte編碼基本一樣;


* UCS-4: 4byte編碼, 目前是在UCS-2前加上2個全0的byte。


ISO就直接規定必須用兩個字節,也就是16位來統一表示所有的字符,對於ASCII裏的那些“半角”字符,Unicode保持其原編碼不變,只是將其長度由原來的8位擴展爲16位,而其他文化和語言的字符則全部重新統一編碼。由於“半角”英文符號只需要用到低8位,所以其高8位永遠是0,因此這種大氣的方案在保存英文文本時會多浪費一倍的空間。


Unicode 字符集收錄了這世界上所有的文字符號和特殊符號。對於每一個符號都定義了一個值,稱爲代碼點(code point)。代碼點可以用2個字節表示(UCS-2),也可以用4個字節(UCS-4編碼)。


Unicode在制訂時沒有考慮與任何一種現有的編碼方案保持兼容,這使得GBK與Unicode在漢字的內碼編排上完全是不一樣的,沒有一種簡單的算術方法可以把文本內容從Unicode編碼和另一種編碼進行轉換,這種轉換必須通過查表來進行。


18. UTF編碼


UCS編碼雖然定義了每個代碼點的編碼方式,但是沒規定如何傳輸和存儲。比如,在UCS-2碼中,英文符號是在ACSII碼的前面加上一個0 byte,像“A”的ASCII碼 0x41,在UCS碼中就是0x0041,這樣,對於英文系統來講會出現大量的0 byte,造成不必要的浪費。而且容易存在對現在的ASCII碼不兼容的問題。所以這個重擔就落在了UTF編碼身上。


於是面向傳輸的衆多UTF(UCS Transfer Format)標準出現了。顧名思義,UTF8就是每次8個位傳輸數據,而UTF16就是每次16個位,只不過爲了傳輸時的可靠性,從Unicode到UTF時並不是直接的對應,而是要通過一些算法和規則來轉換。


(1)UTF-8(Unicode Transformation Format – 8-bit)


UTF-8是一種針對Unicode的可變長度字符編碼(定長碼),也是一種前綴碼。它可以用來表示Unicode標準中的任何字符,且其編碼中的第一個字節仍與ASCII兼容,這使得原來處理ASCII字符的軟件無需或只需做少部份修改,即可繼續使用。因此,它逐漸成爲電子郵件、網頁及其他存儲或傳送文字的應用中,優先採用的編碼。互聯網工程工作小組(IETF)要求所有互聯網協議都必須支持UTF-8編碼。


UTF-8使用一至四個字節爲每個字符編碼:


* 128個US-ASCII字符只需一個字節編碼(Unicode範圍由U+0000至U+007F)。


*帶有附加符號的拉丁文、希臘文、西裏爾字母、亞美尼亞語、希伯來文、阿拉伯文、敘利亞文及它拿字母則需要二個字節編碼(Unicode範圍由U+0080至U+07FF)。


* 其他基本多文種平面(BMP)中的字符(這包含了大部分常用字)使用三個字節編碼。


*其他極少使用的Unicode輔助平面的字符使用四字節編碼。


在處理經常會用到的ASCII字符方面非常有效。在處理擴展的拉丁字符集方面也不比UTF-16差。對於中文字符來說,比UTF-32要好。同時,UTF-8以字節爲編碼單元,由位操作的天性使然,使用UTF-8不再存在字節順序的問題了。一份以utf-8編碼的文檔在不同的計算機之間是一樣的比特流。


總體來說,在Unicode字符串中不可能由碼點數量決定顯示它所需要的長度,或者顯示字符串之後在文本緩衝區中光標應該放置的位置;組合字符、變寬字體、不可打印字符和從右至左的文字都是其歸因。所以儘管在UTF-8字符串中字符數量與碼點數量的關係比UTF-32更爲複雜,在實際中很少會遇到有不同的情形。


UTF-8的優點:


*UTF-8是ASCII的一個超集。因爲一個純ASCII字符串也是一個合法的UTF-8字符串,所以現存的ASCII文本不需要轉換。爲傳統的擴展ASCII字符集設計的軟件通常可以不經修改或很少修改就能與UTF-8一起使用。


*使用標準的面向字節的排序例程對UTF-8排序將產生與基於Unicode代碼點排序相同的結果。(儘管這隻有有限的有用性,因爲在任何特定語言或文化下都不太可能有仍可接受的文字排列順序。)


*UTF-8和UTF-16都是可擴展標記語言文檔的標準編碼。所有其它編碼都必須通過顯式或文本聲明來指定。


*任何面向字節的字符串搜索算法都可以用於UTF-8的數據(只要輸入僅由完整的UTF-8字符組成)。但是,對於包含字符記數的正則表達式或其它結構必須小心。


* UTF-8字符串可以由一個簡單的算法可靠地識別出來。就是,一個字符串在任何其它編碼中表現爲合法的UTF-8的可能性很低,並隨字符串長度增長而減小。舉例說,字符值C0,C1,F5至FF從來沒有出現。爲了更好的可靠性,可以使用正則表達式來統計非法過長和替代值(可以查看W3 FAQ: Multilingual Forms上的驗證UTF-8字符串的正則表達式)。


UTF-8的缺點:


* 因爲每個字符使用不同數量的字節編碼,所以尋找串中第N個字符是一個O(N)複雜度的操作,即,串越長,則需要更多的時間來定位特定的字符。同時,還需要位變換來把字符編碼成字節,把字節解碼成字符。


(2)UTF-16(Unicode Transformation Format – 16-bit)


UTF-16以兩個字節爲編碼單元。在解釋一個UTF-16文本前,首先要弄清楚每個編碼單元的字節序。Unicode規範中推薦的標記字節順序的方法是BOM(即字節順序標記-Byte Order Mark)。在UCS編碼中有一個叫做“ZERO WIDTH NO-BREAK SPACE”的字符,它的編碼是FEFF。而FFFE在UCS中是不存在的字符,所以不應該出現在實際傳輸中。UCS規範建議我們在傳輸字節流前,先傳輸字符”ZERO WIDTH NO-BREAK SPACE”。這樣如果接收者收到FEFF,就表明這個字節流是Big-Endian的;如果收到FFFE,就表明這個字節流是Little-Endian的。因此字符“ZERO WIDTH NO-BREAK SPACE”又被稱作BOM。Windows就是使用BOM來標記文本文件的編碼方式的。


UTF-16將0–65535範圍內的字符編碼成2個字節,如果真的需要表達那些很少使用的”星芒層(astral plane)”內超過這65535範圍的Unicode字符,則需要使用一些詭異的技巧來實現。UTF-16編碼最明顯的優點是它在空間效率上比UTF-32高兩倍,因爲每個字符只需要2個字節來存儲(除去65535範圍以外的),而不是UTF-32中的4個字節。並且,如果我們假設某個字符串不包含任何星芒層中的字符,那麼我們依然可以在常數時間內找到其中的第N個字符,直到它不成立爲止這總是一個不錯的推斷。其編碼方法是:


*如果字符編碼U小於0x10000,也就是十進制的0到65535之內,則直接使用兩字節表示;


* 如果字符編碼U大於0x10000,由於UNICODE編碼範圍最大爲0x10FFFF,從0x10000到0x10FFFF之間共有0xFFFFF個編碼,也就是需要20個bit就可以標示這些編碼。用U’表示從0-0xFFFFF之間的值,將其前 10 bit作爲高位和16 bit的數值0xD800進行 邏輯or 操作,將後10 bit作爲低位和0xDC00做邏輯or 操作,這樣組成的 4個byte就構成了U的編碼。


(3)UTF-32(Unicode Transformation Format – 32-bit)


使用4字節的數字來表達每個字母、符號,或者表意文字(ideograph),每個數字代表唯一的至少在某種語言中使用的符號的編碼方案,稱爲UTF-32。UTF-32又稱UCS-4,是一種將Unicode字符編碼的協定,對每個字符都使用4字節。就空間而言,是非常沒有效率的。


但這種方法有其優點,最重要的一點就是可以在常數時間內定位字符串裏的第N個字符,因爲第N個字符從第4×Nth個字節開始。雖然每一個碼位使用固定長定的字節看似方便,它並不如其它Unicode編碼使用得廣泛。


常用軟件的默認字符集及其查看方法


(1)window下面保存記事本的文本字符集編碼爲:系統編碼GBK;


(2)linux下面的默認字符集編碼查看方法:/etc/sysconfig/i18n;


(3)利用cpdetector第三方包可以判斷文件或者流的編碼;


(4)查詢oracle的默認字符集編碼方法:select userenv(‘language’) from dual;


(5)早期操作系統的內碼是與語言相關的。現在的Windows在內部統一使用Unicode,然後用代碼頁適應各種語言;


(6)C、C++、Python2內部字符串都是使用當前系統默認編碼;


(7)Python3、Java內部字符串用Unicode保存;


(8)Ruby有一個內部變量$KCODE用來表示可識別的多字節字符串的編碼,變量值爲”EUC” “SJIS” “UTF8″ “NONE”之一。$KCODE的值爲”EUC”時,將假定字符串或正則表達式的編碼爲EUC-JP。同樣地,若爲”SJIS”時則認定爲Shift JIS。若爲”UTF8″時則認定爲UTF-8。若爲”NONE”時,將不會識別多字節字符串。在向該變量賦值時,只有第1個字節起作用,且不區分大小寫字母。”e” “E” 代表 “EUC”,”s” “S” 代表 “SJIS”,”u” “U” 代表 “UTF8″,而”n” “N” 則代表 “NONE”。默認值爲”NONE”。即默認情況下Ruby把字符串當成單字節序列來處理;


(9)ultraedit在默認情況下是把utf-8轉換爲unicode編碼,你看到的是unicode編碼,


如果你想看到真正的utf-8編碼,那麼在ultraedit中,做如下操作


File—>conversions—>unicode/asc2/utf-8 to utf-8(asc2 editing)


關於字符集及字符編碼的問題這次先總結到這裏,下次將對Java等字符編碼進行進一步分析。


參考資料


(1)維基百科-字符編碼


(2)《計算機編碼知識——區位碼、國標碼、機內碼、輸入碼、字形碼》


(3)《計算機內碼與外碼的區別》


(4)《和榮筆記- GB2312 字符集與編碼對照表》


(5)《說說字符集和編碼》


(6)《編碼簡介》


(7)《深入瞭解字符集和編碼》


(8)吳秦 《字符集和字符編碼(Charset & Encoding)》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章