字符編碼的基本概念

1 字符編碼

1.1 什么是字符編碼

眾所周知型诚,在計算機的世界中,不管是存儲還是傳輸都是通過二進制(單位:比特,bit)來表示數(shù)據(jù)的婚瓜,而我們?nèi)祟惸芾斫獾奈淖直摹?biāo)點符號、特殊符號巴刻、emoji表情等字符只有被映射為二進制才能被計算機所識別愚铡,而這個映射則被稱之為字符編碼(Character Encoding,以下簡稱編碼)胡陪,反之稱為解碼沥寥。

1.2 現(xiàn)代編碼模型

關(guān)于編碼涉及的概念非常多,Unicode Technical Report #17給出的字符編碼模型主要包括抽象字符庫柠座、編碼字符集邑雅、字符編碼模式、字符編碼方案四個層次妈经。盡管是unicode出的編碼模型蒂阱,對于套用在一些其他的編碼上依然適用(比如ISO8859-1、GBK等)狂塘。

(1)抽象字符庫(Abstract character repertoire)

抽象字符庫是一個系統(tǒng)支持的所有抽象字符的集合录煤。通常情況下可以簡單地理解為是一大堆字母、數(shù)字荞胡、控制字符(如空格妈踊、回車等)、標(biāo)點符號等字符組成的集合泪漂。這里的字符是“抽象”的廊营,與字形是不一樣的,比如“A”字符萝勤,用微軟雅黑是長這樣露筒,用宋體又是另外一個樣了。

(2)編碼字符集(CCS:Coded Character Set)

如果將抽象字符庫中每一個字符都映射成非負整數(shù)(對)的話(如Unicode“A”-> 65敌卓,GB2312的 '啊' -> (16,01))慎式,那么這些映射的集合就稱之為編碼字符集(也有直接稱字符集)。用于映射的非負整數(shù)的范圍趟径,則被稱之為編碼空間(encoding space)瘪吏,編碼空間中的一個位置(position)稱為碼位(code point),例如在ASCII編碼字符集中蜗巧,字符 'A' 的碼位值為65掌眠。編碼字符集也可以理解成把抽象字符映射為碼位值。簡單理解就是字符與整數(shù)的映射幕屹。
另外蓝丙,編碼空間可以用一個整數(shù)來描述级遭,例如:ISO-8859-1的編碼空間是256;可以用一對整數(shù)來描述渺尘,例如:GB2312的漢字編碼空間是94 x 94挫鸽;也可以用字符的存儲單元尺寸來描述,例如:ISO-8859-1是一個8-bit的編碼空間沧烈;還可以用其子集來表述掠兄,如行像云、列锌雀、面(plane)等。但不管如何描述迅诬,字符在編碼空間內(nèi)都有對應(yīng)的碼位值(一個非負整數(shù))腋逆。

(3)字符編碼模式(CEF:Character Encoding Form)

非負整數(shù)確定后,這時候就需要把它映射成二進制的序列了侈贷。這個映射往往不能簡單地映射惩歉,例如整數(shù) 2->[10] 8->[1000],因為很明顯俏蛮,當(dāng)解碼[1000] 時撑蚌,到底是 [1000] 還是 [10] + [00]呢?但是假如我定好了4-bit是一個整體(基本單元)搏屑,這時 2->[0010] 8->[1000]争涌,就很好分辨了。這里所說以個整體就是字符編碼模式我們常說的碼元(code unit)辣恋,表示用于處理或交換編碼文本時的基本單元亮垫,碼元的長度是確定的,比如這里的4-bit伟骨、ISO 8859-1的8-bit饮潦、UTF-8的8-bit、UTF-16的16-bit等携狭。

字符編碼模式(CEF继蜡,也有翻譯為字符編碼表),也稱為"storage format"逛腿,規(guī)定了從CCS 中的非負整數(shù)到一組特定碼元序列的映射壹瘟。碼元序列由一個多或多個碼元組成。

根據(jù)碼元組成的序列長度是否可變鳄逾,CEF可以區(qū)分為兩大類:

  1. 固定寬度 (fixed width) 編碼模式:字符由一個碼元確定稻轨,碼元序列就是CCS中整數(shù)的對應(yīng)碼元長度的二進制(不夠則前面補0),如UCS-2雕凹、UCS-4殴俱、UTF-32等
  2. 可變寬度 (variable width) 編碼模式:碼元的數(shù)量可以有多個政冻,如UTF-8、UTF-16等

我們常說的GBK編碼线欲、UTF-8/16編碼等明场,通常指的就是這一層干的活。

(4)字符編碼方案(CES:Character Encoding Scheme)

字符編碼方案(CES)李丰,也稱作"serialization format"苦锨,將一個個CEF中的碼元映射到字節(jié)(8-bit)序列,以便編碼后的數(shù)據(jù)的文件存儲或網(wǎng)絡(luò)傳輸趴泌。這時侯時真正將二進制序列放入到硬件中了舟舒。
在Unicode的相關(guān)編碼方案中,當(dāng)一個字符對應(yīng)的碼元序列為多個字節(jié)時嗜憔,根據(jù)字節(jié)存儲的順序可以分為大端序(big-endian)和小端序(little-endian)秃励。例如字符'我'在UTF-16中對應(yīng)的碼元序列為[01100010 00010001],當(dāng)采取大端序存儲時吉捶,碼元序列的高字節(jié)放在存儲器的低地址中夺鲜,存儲器從低地址到高地址順序讀取時,讀取到的第一個字節(jié)為[01100010]呐舔,第二個為[00010001]币励;而采用小端序時,則反過來了珊拼,數(shù)據(jù)的低字節(jié)放在低地址中食呻,順序讀取時第一個字節(jié)為[00010001],然后再是[01100010]杆麸。
為了告訴計算機是到底是大端序還是小端序搁进,還需要在編碼后的字節(jié)流的開頭指定一個字節(jié)順序標(biāo)記(byte-order mark,BOM)昔头,0xFEFF表示大端饼问,0xFFFE表示小端。

2.編碼的歷史

字符編碼的歷史揭斧,其實就是一個在不斷制定和更新標(biāo)準(zhǔn)的過程莱革。范圍從英文到歐洲字符、漢字乃至全世界的文字和符號讹开。

2.1 初出茅廬ASCII

上世紀(jì)60年代盅视,美國制定了一套基于拉丁字母的編碼標(biāo)準(zhǔn),即ASCII(American Standard Code for Information Interchange旦万,美國信息交換標(biāo)準(zhǔn)代碼)闹击,ASCII字符集共收錄了95個顯示字符(英文字母、數(shù)字成艘、一般的符號)和33個控制字符赏半,共128個字符贺归,至少7bit來表達所有字符,即000 0000-111 1111断箫,十進制為0-127拂酣,例如'A' 對應(yīng)的碼位值為65。因為ASCII的普及范圍廣仲义,后來的編碼字符集一般都會對ASCII做兼容婶熬。

值得一提的是,“字節(jié)”最初的長度表示用于編碼單個字符所需要的比特數(shù)量埃撵,曾基于硬件為1-48比特不等赵颅,而直到二十世紀(jì)70年代開始,1字節(jié)才被標(biāo)準(zhǔn)化為8比特(某硬件的流行盯另?)性含。正是因為這樣洲赵,現(xiàn)代的字符編碼(UTF-8鸳惯、GBK等),碼元的長度通常為8的整數(shù)倍叠萍。ASCII用單個字節(jié)存儲的時芝发,最開始最高位用于校驗,后來則用作了ASCII編碼拓展苛谷。

2.2 歐洲發(fā)展EASCII與ISO 8859

伴隨著歐洲強國對計算機的引入和發(fā)展辅鲸,各個廠商或者是國家為了滿足自己的需求,均推出了自己的一套EASCII編碼(Extended ASCII腹殿,延伸美國標(biāo)準(zhǔn)信息交換碼)独悴,其中比較出名的有IBM PC的Code page 437注意:EASCII指的是對ASCII空置的 [1000 0000-1111 1111] 進行拓展的那一類編碼锣尉,而不是特指哪一個刻炒。正因為這樣,當(dāng)時的編碼格局顯得有些混亂自沧。

為了統(tǒng)一ASCII的相關(guān)拓展編碼坟奥,國際標(biāo)準(zhǔn)化組織(ISO)及國際電工委員會(IEC)聯(lián)合制定的一系列8位元(單字節(jié))字符集的標(biāo)準(zhǔn),定義了共15個字符集拇厢。分別是:

  • ISO/IEC 8859-1 (Latin-1) - 西歐語言
  • ISO/IEC 8859-2 (Latin-2) - 中歐語言
  • ISO/IEC 8859-3 (Latin-3) - 南歐語言爱谁。世界語也可用此字符集顯示。
  • ISO/IEC 8859-4 (Latin-4) - 北歐語言
  • ISO/IEC 8859-5 (Cyrillic) - 斯拉夫語言
  • ISO/IEC 8859-6 (Arabic) - 阿拉伯語
  • ISO/IEC 8859-7 (Greek) - 希臘語
  • ISO/IEC 8859-8 (Hebrew) - 希伯來語(視覺順序)
  • ISO 8859-8-I - 希伯來語(邏輯順序)
  • ISO/IEC 8859-9(Latin-5 或 Turkish)- 它把Latin-1的冰島語字母換走孝偎,加入土耳其語字母
  • ISO/IEC 8859-10(Latin-6 或 Nordic)- 北日耳曼語支访敌,用來代替Latin-4
  • ISO/IEC 8859-11 (Thai) - 泰語,從泰國的 TIS620 標(biāo)準(zhǔn)字集演化而來
  • ISO/IEC 8859-13(Latin-7 或 Baltic Rim)- 波羅的語族
  • ISO/IEC 8859-14(Latin-8 或 Celtic)- 凱爾特語族
  • ISO/IEC 8859-15 (Latin-9) - 西歐語言衣盾,加入Latin-1欠缺的芬蘭語字母和大寫法語重音字母寺旺,以及歐元(€)符號
  • ISO/IEC 8859-16 (Latin-10) - 東南歐語言荡陷。主要供羅馬尼亞語使用,并加入歐元符號

這時歐洲幾乎所有國家的字符都有其對應(yīng)的編碼標(biāo)準(zhǔn)了(盡管你只能選擇其中一個)迅涮。

加入了96個西歐字母及符號的ISO 8859-1(Latin-1)废赞,因為標(biāo)準(zhǔn)化早且覆蓋區(qū)域廣,成為了歐美十分流行的編碼叮姑,mysql唉地、tomcat等軟件的一些早期版本默認的編碼就是ISO-8859-1。

套于編碼模型上传透,EASCII和ISO 8859編碼空間均為8bit耘沼,碼元長度8-bit。因為是定長朱盐、單字節(jié)存儲群嗤,編碼十分簡單。如 'A' -> 碼位值為 [110 0101] 兵琳,碼元序列 8-bit 固定長度狂秘,前面補個0得:[0110 0101],實際存儲沒那么多花哨的東西躯肌,直接存儲單個字節(jié) [0110 0101] 即可者春。

2.3 中國崛起GB2312與GBK

GB2312起源

就如最初的歐洲一般,我國也是沒有自己文字的編碼清女,直到1980年钱烟,中國國家標(biāo)準(zhǔn)總局發(fā)布中華人民共和國國家標(biāo)準(zhǔn)簡體中文字符集,即GB 2312(GB即"國標(biāo)"拼音首聲母)嫡丙。GB 2312標(biāo)準(zhǔn)共收錄6763個漢字拴袭,其中一級漢字3755個,二級漢字3008個曙博;同時收錄了包括拉丁字母拥刻、希臘字母、日文平假名及片假名字母羊瘩、俄語西里爾字母在內(nèi)的682個字符泰佳。為了裝下這幾千個字符,GB2312字符均用雙字節(jié)編碼尘吗。

分區(qū)

GB2312編碼對字符進行了“分區(qū)”處理逝她,共94個區(qū),每個區(qū)有94個漢字/符號睬捶。區(qū)號和位號組成一個區(qū)位碼(碼位):

  • 01~09區(qū)(682個):特殊符號黔宛、數(shù)字、英文字符擒贸、制表符等臀晃,包括拉丁字母觉渴、希臘字母、日文平假名及片假名字母徽惋、俄語西里爾字母等在內(nèi)的682個全角字符案淋;
  • 16-55區(qū)(3755個):一級漢字,按拼音排序险绘。
  • 56-87區(qū)(3008個):二級漢字踢京,按部首/筆畫排序。
  • 10-15區(qū)宦棺、88-94區(qū):空區(qū)瓣距,留待擴展。
    比如 '啊' 是GB2312編碼字符集中的第一個一級漢字代咸,區(qū)位碼為(16, 01)蹈丸,十六進制為:0x1001,二進制:[00010110 00000001]呐芥。

國標(biāo)碼

GB2312字符集中并沒有定義控制字符逻杖,而是沿用了ASCII中的那32個控制字符,而這時候就出現(xiàn)了個問題了贩耐,如果直接使用弧腥,比如"啊"的區(qū)位碼作為二進制序列的話為:[00010000 00000001]厦取,而 [00010000] 在ASCII表示DLE(跳出數(shù)據(jù)通訊)潮太,這時候軟件解析時就不知道是取單個字節(jié)的DLE還是雙字節(jié)的"啊"了。
GB2312采用的辦法是將字符對應(yīng)的區(qū)位碼均偏移32(每個字節(jié)大小加上32虾攻、即0x20)铡买,解析時,只要是小于等于32的字節(jié)則認為是ASCII控制字符霎箍,否則為GB2312的雙字節(jié)字符奇钞。這個區(qū)位碼+32的碼就是我們常說的“國標(biāo)碼/交換碼”,比如'啊'的國標(biāo)碼=0x1001+0x2020=0x3021漂坏。

機內(nèi)碼

國標(biāo)碼發(fā)布之后景埃,我們又發(fā)現(xiàn)了一個問題。盡管GB2312定義了英文字母和數(shù)字顶别,但是這些都是和漢字等大的字母和數(shù)字(全角)谷徙,如果有一篇文章的字母既有全角又有半角的話,國標(biāo)碼就不能滿足了驯绎。國標(biāo)碼只是解決了控制字符的沖突問題完慧,卻沒有解決其他ASCII字符沖突問題。
后來微軟在搞Windows中文版本時剩失,索性在國標(biāo)碼的基礎(chǔ)上屈尼,最高位都取1册着,這時候就和ASCII就都兼容了,這個這就是我們常說的“機內(nèi)碼”脾歧。機內(nèi)碼=國標(biāo)碼+0x8080 [1000 0000]=區(qū)位碼+0x2020 + 0x8080=區(qū)位碼+0xA0A0甲捏,例如 '啊' 的機內(nèi)碼=0x3021+0x8080=0x1001+0xA0A0=0xB0A1。當(dāng)字節(jié)小于128 [1000 0000]時鞭执,直接取單個字節(jié)解析為一個ASCII字符摊鸡;大于等于128的,取兩個字節(jié)組成一個GB2312字符蚕冬。例如“A啊”在存儲器中的十六進制為“41 B0 A1”免猾,共三個字節(jié)。這種儲存方法也被稱之為EUC-CN(Extended Unix Code CN囤热,一個使用8位編碼來表示字符的方法猎提,主要是為了兼容ASCII)。94區(qū)機內(nèi)碼的范圍為0xA1-0xFE旁蔼。

GB 2312的出現(xiàn)锨苏,基本滿足了漢字的計算機處理需要,它所收錄的漢字已經(jīng)覆蓋中國大陸99.75%的使用頻率棺聊。然而對于人名伞租、古漢語等方面出現(xiàn)的罕用字和繁體字,GB 2312并不支持限佩,直到后來的GBK出現(xiàn)葵诈,這些問題才得以解決。

GBK

1995年12月祟同,中華人民共和國全國信息技術(shù)標(biāo)準(zhǔn)化技術(shù)委員發(fā)布了漢字內(nèi)碼擴展規(guī)范作喘,即GBK(K為"擴展"拼音第一個聲母),GBK共收錄21886個漢字和圖形符號晕城,其中漢字21003個泞坦,圖形符號883個。

GBK是完全兼容GB2312編碼的(例如'啊'機內(nèi)碼均是'B0A1')砖顷,而拓展的可能:
(1)GB2312原本未編碼的10-15贰锁、88-94區(qū);
(2)當(dāng)初取最高位1時(+[1000 0000])滤蝠,是在國標(biāo)碼的基礎(chǔ)上的豌熄,事實上,我們可以直接繞過國標(biāo)碼這個過時的玩意(或者說繞過EUC-CN的存儲方式)几睛,直接在區(qū)位碼最高位取1(區(qū)位碼+[1000 0000]房轿,當(dāng)然原來的國標(biāo)碼最高位取1依然需要兼容),原本0xA1作為機內(nèi)碼起始點,現(xiàn)在可以從0x81開始了(0x81-0xFE共126項)囱持。而當(dāng)?shù)谝粋€字節(jié)確定非ASCII字符時夯接,第二個字節(jié)則沒必要從0x80[1000 0000]開始了。這時候就有足夠的編碼空間了纷妆。

除了漢字盔几,還有韓文、日文等均有自己國家的編碼標(biāo)準(zhǔn)(通常是雙字節(jié)編碼)掩幢,這時候似乎又回到了歐洲編碼EASCII混亂的格局了逊拍。

2.4 一統(tǒng)江湖Unicode

unicode起源

為了統(tǒng)一全世界字符編碼,解決各種編碼的相互沖突的問題际邻,20世紀(jì)80年代末芯丧,在電腦普及和資訊國際化的前提下,一些商業(yè)機構(gòu)以及國際標(biāo)準(zhǔn)化組織(ISO)分別成立了Unicode組織和ISO-10646工作小組世曾。他們不久便發(fā)現(xiàn)對方的存在缨恒,大家為著相同的目的而工作。1991年轮听,Unicode 組織與ISO/IEC委員會同意保持Unicode碼表與ISO 10646標(biāo)準(zhǔn)保持兼容并密切協(xié)調(diào)各自標(biāo)準(zhǔn)近一步的擴展骗露。雖然實際上兩者的字集編碼相同,但實質(zhì)上兩者確實為兩個不同的標(biāo)準(zhǔn)血巍。Unicode 1.1對應(yīng)于ISO 10646-1:1993萧锉,Unicode 3.0對應(yīng)于ISO 10646-1:2000,Unicode 3.2對應(yīng)于ISO 10646-2:2001述寡,Unicode 4.0對應(yīng)于ISO 10646:2003柿隙,Unicode 5.0對應(yīng)于ISO 10646:2003及附錄1–3[1]。可以認為unicode標(biāo)準(zhǔn)下的unicode字符集和ISO-10646標(biāo)準(zhǔn)下的通用字符集(Universal Character Set, UCS)是名稱不同但是實際內(nèi)容差不多的東西辨赐。

unicode字符集优俘,通常指的是編碼模型中的第一二層,就是收錄字符(囊括了英文掀序、歐洲字符、中文惭婿、日文不恭、韓文等)以及每個字符都賦予一個對應(yīng)的非負整數(shù),每個字符可以命名為U+{對應(yīng)非負整數(shù)的十六進制}财饥,如“我”-> U+6211换吧。另外emoji等表情符號在unicode中也有對應(yīng)的碼位。
unicode字符集依然兼容ascii钥星,即在ascii中的那128個字符對應(yīng)的整數(shù)與unicode中是一致的沾瓦,比如“A”都是65。

基本平面與輔助平面

如果說GBK字符集用分區(qū)來表示編碼空間的話,那么unicode字符集則可以叫“分面”了贯莺。unicode的編碼空間劃分為17個平面(plane)风喇,每個平面包含65,536(216)個碼位。17個平面的碼位可表示為從U+xx0000到U+xxFFFF缕探,其中xx表示十六進制值從00到10(十進制就是0-16)魂莫,共計17個平面。其中字符使用頻率最高的是第一個平面爹耗,即基本多語言平面(Basic Multilingual Plane, BMP)耙考,或稱第零平面(Plane 0),其他平面稱為輔助平面(Supplementary Planes)潭兽【胧迹基本多語言平面內(nèi),從U+D800到U+DFFF之間的碼位區(qū)段是永久保留不映射到Unicode字符山卦,UTF-16就利用保留下來的0xD800-0xDFFF區(qū)段的碼位來對輔助平面的字符的碼位進行編碼(詳見下文)楣号。

UCS-2和UTF-16

UCS-2表示通用字符集下的一種編碼模式實現(xiàn)方式,把UCS中的碼位映射成碼元長度為2字節(jié)(16-bit)的碼元序列怒坯,UCS-2是一個定長的編碼炫狱,即碼元個數(shù)永遠都是1個(2字節(jié))。
UTF-16(Unicode Transformation Format, unicode轉(zhuǎn)換格式)剔猿,表示Unicode字符集下的一種編碼模式實現(xiàn)方式视译,把unicode字符集中的碼位映射成成碼元長度為16-bit(2字節(jié))的碼元序列,UTF-16是一個變長的編碼归敬,即單個字符對應(yīng)的碼元個數(shù)可以為1或2酷含,即最終的序列長度可以為2字節(jié)或4字節(jié)。

最初的unicode字符集/UCS編碼空間并不大汪茧,unicode到了3.0版本時(1999年9月)也就49,259個字符椅亚,雙字節(jié)共216=65,536項舱污,用于存儲這個字符集綽綽有余呀舔。而因為UCS和unicode字符集的對應(yīng)關(guān)系,UTF-16可看成是UCS-2的父集扩灯。在unicode編碼空間尚未突破216之前(即輔助平面還沒出來之前)媚赖,UTF-16與UCS-2可以認為是同一個編碼。
最初可能大家都捧著“65535 個字符足夠全世界用了”的觀念珠插,為許多編程語言(如C/java等)和流行軟件(如Mysql)留下了不少的坑惧磺。
伴隨著unicode字符集的擴展,自unicode3.1版本開始(2001年3月)捻撑,編碼空間突破65,536磨隘,雙字節(jié)已經(jīng)存不下了缤底。這時候UTF-16順利成章地用上了兩個碼元(即2字節(jié)*2=4字節(jié))。而UCS-2則只能對UCS/unicode字符集較前的部分字符進行編碼番捂。后來為了編碼所有的字符个唧,ISO-100646推出了UCS-4編碼(等同于unicode的UTF-32),即碼元長度為4字節(jié)/32-bit(定長)白嘁。

UTF-16 基本平面編碼

在基本平面中坑鱼,即U+0000至U+FFFF中,除開U+D800~U+DFFF這段無映射字符的區(qū)間絮缅,其他均被直接映射到單個碼元(16-bit)的序列鲁沥。例如“我”-> U+6211,被映射為二進制序列 [0110 0010 0001 0001]

UTF-16 輔助平面編碼

為了區(qū)分字符是由單個碼元還是兩個碼元表示耕魄,對于輔助平面上的編碼画恰,UTF-16使用兩個U+D800~U+DFFF區(qū)間的碼元表示一個字符。
以“??”(U+24B62)為例吸奴,說說碼位到碼元序列的映射過程:

  1. 0x24B62-0x10000=0x14B62允扇,結(jié)果二進制為5個4位共20位:[0001 0100 1011 0110 0010]
  2. 將這20位分成兩個10位,即0001 0100 10 和 11 0110 0010则奥,十六進制分別為0x52和0x362
  3. 前10位+0xD800=0x52+0xD800=0xD852考润,作為序列中的第一個碼元(高位);
  4. 后10位+0xDC00=0x362+0xDC00=0xDF62读处,作為序列中的第一個碼元(低位)糊治;
  5. 兩個碼元組成了最終的一對碼元序列,即 “D852 DF62”罚舱,也就是代理對(surrogate pair)井辜,高位代理為前導(dǎo)代理(lead surrogates),低位代理為后尾代理(trail surrogates)管闷。

解碼時反著來就行粥脚。

UTF-8

UTF-16的碼元長度為16-bit(兩個字節(jié)),一個字符可以由一到兩個碼元組成包个;
UTF-8的碼元長度則為8-bit(一個字節(jié))刷允,一個字符可以由一至六個碼元組成,具體字符與字節(jié)的關(guān)系如下:

  1. 128個ASCII字符只需一個字節(jié)編碼(Unicode范圍由U+0000至U+007F)赃蛛。
  2. 帶有附加符號的拉丁文恃锉、希臘文、西里爾字母呕臂、亞美尼亞語、希伯來文肪跋、阿拉伯文歧蒋、敘利亞文及它拿字母則需要兩個字節(jié)編碼(Unicode范圍由U+0080至U+07FF)。
  3. 基本平面中的其余的字符(如大部分的漢字)使用三個字節(jié)編碼(Unicode范圍由U+0800至U+FFFF)。
  4. 其他極少使用的Unicode 輔助平面的字元使用四至六個字節(jié)編碼(Unicode范圍由U+10000至U+1FFFFF使用四字節(jié)谜洽,Unicode范圍由U+200000至U+3FFFFFF使用五字節(jié)萝映,Unicode范圍由U+4000000至U+7FFFFFFF使用六字節(jié))。

UTF-8的優(yōu)勢主要在于一個是和UTF-16一樣阐虚,可以編碼所有的unicode字符序臂;二是ASCII字符僅需要占用一個字節(jié),對于以英文數(shù)字為主的數(shù)據(jù)可以省50%的空間(相對于UTF-16)实束。但是對于漢字字符來說奥秆,會比GBK多50%的大小(不過GBK對于一些特殊字符咸灿,例如目前流行的emoji表情符號就不支持了)构订。

UTF-8 編碼規(guī)則

UTF-8會根據(jù)第一個字節(jié)的“情況”來區(qū)分多少個碼元(字節(jié))表示一個字符。具體的規(guī)則如下:
(1)單個字節(jié)避矢,首字節(jié)最高位為0悼瘾,剩余的7-bit為可編碼位數(shù),unicode的0x00-0x7F的碼位可以映射到這里审胸。
(2)多個字節(jié)亥宿,首字節(jié)最高位開始,連續(xù)的二進制位值為1的個數(shù)就是其編碼的字節(jié)數(shù)砂沛,其余各字節(jié)均以10開頭烫扼。未被指定的其他位會對Unicode碼位進行編碼映射(如下表的"x")

可編碼位數(shù) 碼位范圍 字節(jié)數(shù)量 Byte 1 Byte 2 Byte 3 Byte 4 Byte 5 Byte 6
7 U+0000~U+007F 1 0xxx xxxx
11 U+0080~U+07FF 2 110x xxxx 10xx xxxx
16 U+0800~U+FFFF 3 1110 xxxx 10xx xxxx 10xx xxxx
21 U+10000~U+1FFFFF 4 1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx
26 U+200000~U+3FFFFFF 5 1111 10xx 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx
32 U+4000000~U+7FFFFFFF 6 1111 10xx 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx

例如,“我”-> U+6211尺上,二進制為[0110 0010 0001 0001]材蛛,處于三字節(jié)編碼處,將其填充至對應(yīng)的"x"位置即可:[1110 0110 1000 1000 1001 0001]怎抛,對應(yīng)的十六進制序列為 "E6 88 91"共三個字節(jié)卑吭。

UTF-8并不像UTF-16那樣有區(qū)分大小端序,BOM(字節(jié)順序標(biāo)記)有無并不會影響字符對應(yīng)的二進制序列马绝。UTF-8中豆赏,無論有BOM和無BOM,“我”都是對應(yīng)“E6 88 91”富稻,只不過有BOM在前面多加個標(biāo)記符號(“EF BB BF”)而已掷邦。

2.5關(guān)于Base64與Url編碼

Base64編碼、Url編碼和UTF-8/GBK這些不屬于一個編碼層次椭赋。前者是把UTF-8/GBK編碼后的字節(jié)序列抚岗,再進行一次映射,形成新的二進制序列哪怔,以滿足傳輸環(huán)境的限制(見下文)宣蔚。

Base64

早期email的傳輸和解析只需要滿足英文就行了向抢,也沒考慮說其他一些復(fù)雜的東西。郵箱相關(guān)的一些協(xié)議(如SMTP)胚委,往往是基于純ASCII文本的(ASCII編碼)挟鸠,傳輸過程中對于二進制文件或者GB2312/UTF-8等編碼后的二進制序列并不支持。如果你非要發(fā)這種圖片亩冬、視頻艘希、GB2312編碼后的中文等,這就得先把原來的二進制序列轉(zhuǎn)換為ASCII字符(ASCII編碼)對應(yīng)的二進制序列硅急,這樣才能保證傳輸過程中不會出現(xiàn)問題(比如GB2312的首字節(jié)被ASCII取7-bit解析為終止字符等)
Base64是一種基于64個可打印字符來表示二進制數(shù)據(jù)的表示方法覆享,這64個字符分別是:A-Z,a-z铜秆,0-9淹真,+,/连茧。原本若干個字節(jié)的二進制序列核蘸,每6-bit作為一個整體(26=64),映射到對應(yīng)的可打印字符中啸驯。其映射關(guān)系為:【0-25 -> 'A'-'Z'】【26-51 -> 'a'-'z'】【52-61 -> '0'-'9'】【62 -> '+'】【63 -> '/'】客扎,映射后的字符也就可以使用ASCII編碼了。

假如有個三字節(jié)序列:[0000 1111 0000 1111 0000 1111] 進行Base64編碼:
(1)以6-bit進行切分得到:[0000 11] [11 0000] [1111 00] [00 1111]罚斗,即 3徙鱼、48、60针姿、15袱吆;
(2)找到對應(yīng)的可打印字符:D、w距淫、8绞绒、P
(3)對字符進行ASCII編碼,得新的二進制序列:[0100 0100] [0111 0111] [0011 1000] [0101 0000]共四個字節(jié)
原本3字節(jié)24-bit的數(shù)據(jù)榕暇,現(xiàn)在變成了 24/6=4個字節(jié)大小了蓬衡,Base64編碼后傳輸大小為原來的4/3了;

如果原數(shù)據(jù)的字節(jié)數(shù)不能被3整除(位數(shù)不能被6整除),需要用0補足字節(jié),補上的全0映射至'='字符芜抒。例如:
(1)單字節(jié) [0000 1111],補充到三字節(jié)[0000 1111] [0000 0000][0000 0000]阶冈,切分成 [0000 11] [11 0000][0000 0000] [0000 0000],即 映射成 'D'、'w'担巩、'='讨衣、'='换棚。
(1)雙字節(jié) [0000 1111 0000 1111]式镐,補充到三字節(jié)[0000 1111 0000 1111][0000 0000]反镇,切分成 [0000 11] [11 0000][1111 00] [00 0000],即 映射成 'D'娘汞、'w'歹茶、'8'、'='你弦。
這也是為什么我們經(jīng)常能看到Base64字符串會以'='結(jié)尾的原因惊豺。

Base64目前在Web中的應(yīng)用場景通常是對于圖片的編碼,例如圖片的展示禽作,或者上傳圖片時可以直接連同其他數(shù)據(jù)一起提交至服務(wù)器尸昧,服務(wù)器以接收普通字符一樣接收圖片。

Url編碼

Url編碼(URL encoding)旷偿,也稱百分號編碼(Percent-encoding)烹俗,是指特定上下文的統(tǒng)一資源定位符 (URL)的編碼機制。URI所允許的字符分為保留字符:【!*'();:@&=+$,/?#[]】和非保留字符:【=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~】萍程。保留字符是有特殊含義的幢妄,例如https://wikipedia.org中的'/'用于URL不同部分的分界符。

對于除了非保留字符之外的字符茫负,均要使用百分號編碼(即由若干個 '%XX'組成)蕉鸳,其中保留字符對應(yīng)的百分號編碼為:

字符 百分號編碼 字符 百分號編碼
! %21 + %2B
# %23 , %2C
$ %24 / %2F
& %26 : %3A
' %27 ; %3B
( %28 = %3D
) %29 ? %3F
* %2A @ %40
[ %5B ] %5D

其他的例如漢字,則是進行指定的編碼后(如UTF-8忍法、GBK編碼)潮尝,再轉(zhuǎn)換為百分號編碼。例如'我'饿序,UTF-8編碼后為二進制序列十六進制為“E6 88 91”勉失,對應(yīng)的%編碼即為 “%E6%88%91”。

用上百分號編碼的地方:

  1. url的 path嗤堰,如https://zh.wikipedia.org/wiki/戴质,實際上請求為:https://zh.wikipedia.org/wiki/%E6%88%91
  2. application/x-www-form-urlencoded類型數(shù)據(jù)請求踢匣。GET請求時告匠,如https://www.baidu.com/s?wd=,實際為:https://www.baidu.com/s?wd=%E6%88%91离唬;POST請求時后专,在請求體中的數(shù)據(jù)也會被瀏覽器進行URL編碼。

不同的瀏覽器输莺,不同的頁面戚哎,可能進行的編碼(可能為UTF-8或者為GBK)裸诽,可以參考這篇文章,注意:處理實際問題時型凳,不同瀏覽器或版本可能不一樣丈冬,具體問題具體分析。

3 編碼選擇

這么多種編碼用哪個甘畅?我的建議是:對于一般業(yè)務(wù)數(shù)據(jù)的存儲和傳輸埂蕊,UTF-8一把梭就完事了;對于我國環(huán)境下疏唾,GBK仍然是個不錯的選擇蓄氧,前提是容忍部分字符的缺失(如emoji,會出現(xiàn)亂碼)槐脏;在一些需要定長的場景下(例如后來的一些編程語言的字符)喉童,可以選擇UTF-32/UCS-4。
值得注意的是顿天,各類編碼本身的定義是一回事堂氯,軟件是否支持又是另外一回事了,一定要結(jié)合具體的場景討論編碼問題露氮。比如Mysql 5.5.3之前的版本的utf8編碼最大只支持3個字節(jié)的字符編碼祖灰,emoji都存不了。直到后面出了個utf8mb4才支持到最大四個字節(jié)畔规。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末局扶,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子叁扫,更是在濱河造成了極大的恐慌三妈,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件莫绣,死亡現(xiàn)場離奇詭異畴蒲,居然都是意外死亡,警方通過查閱死者的電腦和手機对室,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門模燥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人掩宜,你說我怎么就攤上這事蔫骂。” “怎么了牺汤?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵辽旋,是天一觀的道長。 經(jīng)常有香客問我,道長补胚,這世上最難降的妖魔是什么码耐? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮溶其,結(jié)果婚禮上骚腥,老公的妹妹穿的比我還像新娘。我一直安慰自己握联,他們只是感情好桦沉,可當(dāng)我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著金闽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪剿骨。 梳的紋絲不亂的頭發(fā)上代芜,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天,我揣著相機與錄音浓利,去河邊找鬼挤庇。 笑死,一個胖子當(dāng)著我的面吹牛贷掖,可吹牛的內(nèi)容都是我干的嫡秕。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼苹威,長吁一口氣:“原來是場噩夢啊……” “哼昆咽!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起牙甫,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤掷酗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后窟哺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泻轰,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年且轨,在試婚紗的時候發(fā)現(xiàn)自己被綠了浮声。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡旋奢,死狀恐怖泳挥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情黄绩,我是刑警寧澤羡洁,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站爽丹,受9級特大地震影響筑煮,放射性物質(zhì)發(fā)生泄漏辛蚊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一真仲、第九天 我趴在偏房一處隱蔽的房頂上張望袋马。 院中可真熱鬧,春花似錦秸应、人聲如沸虑凛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽桑谍。三九已至,卻和暖如春祸挪,著一層夾襖步出監(jiān)牢的瞬間锣披,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工贿条, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留雹仿,地道東北人。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓整以,卻偏偏與公主長得像胧辽,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子公黑,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內(nèi)容

  • UTF-8 編碼提供了一種簡便而向后兼容的方法, 使得那種完全圍繞 ASCII 設(shè)計的操作系統(tǒng), 比如 Unix,...
    謝大見閱讀 4,738評論 0 3
  • 字符是用戶可以讀寫的最小單位帆调。計算機所能支持的字符組成的集合奠骄,就叫做字符集。字符集通常以二維表的形式存在番刊。二維表的...
    劉惜有閱讀 8,131評論 2 14
  • 我一直都堅信:無論何種選擇,無關(guān)高下枣抱,只有左右熔吗。 上高中我最喜歡的書是《小時代》,上課的時候經(jīng)常墊在課本下偷看佳晶。當(dāng)...
    Gxy_0cf3閱讀 230評論 0 0
  • 《同真 童趣》 一陣兩陣歡笑聲 劃破藍天白云 手中不知是什么 那一把武器 沖破敵人后防線 影響的是心情 打打鬧鬧這...
    向昕閱讀 272評論 4 4