字符與字符編碼
字符
字符和字節(jié)不太一樣,任何一個文字或符號都是一個字符秩彤,但所占字節(jié)不一定谤辜,不同的編碼導(dǎo)致一個字符所占的內(nèi)存不同。
例如:標點符號+是一個字符,漢字我們是兩個字符汞斧,在GBK編碼中一個漢字占2個字節(jié)乘陪,在UTF-8編碼中一個漢字占3個字節(jié)仇穗。
隨著時代的發(fā)展瓷马,程序員們希望在計算機中顯示字符片林,但計算機只能識別0和1的二進制數(shù)。于是就有了編碼規(guī)范末患。
編碼規(guī)范
所謂字符集其實是一套編碼規(guī)范中的子概念隧膏,為了顯示字符贫悄,國際組織就制定了編碼規(guī)范逆趋,希望使用不同的二進制數(shù)來表示代表不同的字符,這樣電腦就可以根據(jù)二進制數(shù)來顯示其對應(yīng)的字符蒋腮。我們通常就稱呼其為XX編碼,XX字符集胰舆。
例如:GBK 編碼規(guī)范扫茅,根據(jù)這套編碼規(guī)范,計算機就可以在中文字符和二進制數(shù)之間相互轉(zhuǎn)換灿意。而使用GBK編碼就可以使計算機顯示中文字符。
編碼規(guī)范里的3個子概念
1.字庫表
一套編碼規(guī)范不一定包含世界上所有的字符,每套編碼規(guī)范都有自己的使用場景捍岳。而字庫表就存儲了編碼規(guī)范中能顯示的所有字符,計算機就是根據(jù)二進制數(shù)從字庫表中找到字符然后顯示給用戶滴麻捻,相當于一個存儲字符的數(shù)據(jù)庫婉宰。
例如:幾乎所有漢字都保存在GBK 編碼規(guī)范的字庫表中。所以可以顯示漢字,但法語桐愉,俄語并不在其字庫表中纳像,所以GBK不能顯示法語,俄語等不包含在其中的字符葵萎。
2.編碼字符集(字符集)
在一個字庫表中,每一個字符都有一個對應(yīng)的二進制地址九孩,而編碼字符集就是這些地址的集合躺彬。字符集和字庫表一一對應(yīng)球涛,相互轉(zhuǎn)換煌张,這是電腦識別字符的關(guān)鍵霹琼。
如果把世界上不同國家文明的所有字符都放在一起組成一個集合,那么我們常見的 ASCII、GB2312枣申、GBK售葡、GB18030、BIG5 字符集都只是包含了該集合的一部分而已忠藤。而 Unicode 字符集是可以包含所有國家文明中的所有字符的挟伙。
3.字符編碼(編碼方式)
知道字庫表和編碼字符集后,我們就可以直接使用二進制地址來得到字符了模孩。但直接使用字符對應(yīng)的二進制地址來顯示文字是十分浪費的尖阔,Unicode 編碼規(guī)范中包括了幾百萬個字符,想要包括幾百萬個不同的字符榨咐,起碼需要3個字節(jié)的容量介却,為了方便將來擴展,Unicode還保留了更多未使用的空間祭芦,最多可以存儲4個字節(jié)的容量筷笨。
因此為了區(qū)分每個字符憔鬼,哪怕是00000000 00000000 00000000 00001111這種其實只占了1個字節(jié)的字符龟劲,我們也要為他分配4個字節(jié)的空間,這就導(dǎo)致一個可以用1G保存的文件轴或,現(xiàn)在需要4G才能保存昌跌,這是極其浪費的做法。
于是程序員制定了一套算法來節(jié)省空間照雁,而每種不同的算法都被稱作一種編碼方式(下文中為了便于理解都將使用編碼方式來稱呼字符編碼)蚕愤。一套編碼規(guī)范可以有多種不同的編碼方式,不同的編碼方式有不同的適應(yīng)場景饺蚊。例如:UTF-8就是一種編碼方式萍诱,Unicode是一種編碼規(guī)范。此外污呼,Unicode還有UTF-16,UTF-32這兩種編碼方式裕坊。不同的編碼方式節(jié)約的空間不同。
總結(jié):一個較短的二進制數(shù)燕酷,通過一種編碼方式籍凝,轉(zhuǎn)換成編碼字符集中正常的地址,然后在字庫表中找到一個對應(yīng)的字符苗缩,最終顯示給用戶饵蒂。
ASCII 編碼
說到字符編碼,不得不說ASCII碼的簡史酱讶。計算機一開始發(fā)明的時候是用來解決數(shù)字計算的問題退盯,后來人們發(fā)現(xiàn),計算機還可以做更多的事,例如文本處理得问。但由于計算機只識“數(shù)”囤攀,因此人們必須告訴計算機哪個數(shù)字來代表哪個特定字符,例如65代表字母‘A’宫纬,66代表字母‘B’焚挠,以此類推。但是計算機之間字符-數(shù)字的對應(yīng)關(guān)系必須得一致漓骚,否則就會造成同一段數(shù)字在不同計算機上顯示出來的字符不一樣蝌衔。因此美國國家標準協(xié)會ANSI制定了一個標準,規(guī)定了常用字符的集合以及每個字符對應(yīng)的編號蝌蹂,這就是ASCII字符集(Character Set)噩斟,也稱ASCII碼(American Standard Code for Information Interchange,美國信息交換標準代碼)孤个。
當時的計算機普遍使用8比特字節(jié)作為最小的存儲和處理單元剃允,加之當時用到的字符也很少,26個大小寫英文字母還有數(shù)字再加上其他常用符號齐鲤,也不到100個斥废,因此使用7個比特位就可以高效的存儲和處理ASCII碼,剩下最高位1比特被用作一些通訊系統(tǒng)的奇偶校驗给郊。
ASCII字符集由95個可打印字符(0x20-0x7E)和33個控制字符(0x00-0x19牡肉,0x7F)組成∠牛可打印字符用于顯示在輸出設(shè)備上统锤,例如熒屏或者打印紙上,控制字符用于向計算機發(fā)出一些特殊指令炭庙,例如0x07會讓計算機發(fā)出嗶的一聲饲窿,0x00通常用于指示字符串的結(jié)束,0x0D和0x0A用于指示打印機的打印針頭退到行首(回車)并移到下一行(換行)焕蹄。
那時候的字符編解碼系統(tǒng)非常簡單逾雄,就是簡單的查表過程。例如將字符序列編碼為二進制流寫入存儲設(shè)備擦盾,只需要在ASCII字符集中依次找到字符對應(yīng)的字節(jié)嘲驾,然后直接將該字節(jié)寫入存儲設(shè)備即可。解碼二進制流的過程也是類似迹卢。
OEM字符集的衍生
當計算機開始發(fā)展起來的時候辽故,人們逐漸發(fā)現(xiàn),ASCII字符集里那可憐的128個字符已經(jīng)不能再滿足他們的需求了腐碱。人們就在想誊垢,一個字節(jié)能夠表示的數(shù)字(編號)有256個掉弛,而ASCII字符只用到了0x00~0x7F,也就是占用了前128個喂走,后面128個數(shù)字不用白不用殃饿,因此很多人打起了后面這128個數(shù)字的主意∮蟪Γ可是問題在于乎芳,很多人同時有這樣的想法,但是大家對于0x80-0xFF這后面的128個數(shù)字分別對應(yīng)什么樣的字符帖池,卻有各自的想法奈惑。這就導(dǎo)致了當時銷往世界各地的機器上出現(xiàn)了大量各式各樣的OEM字符集。
下面這張表是IBM-PC機推出的其中一個OEM字符集睡汹,字符集的前128個字符和ASCII字符集的基本一致(為什么說基本一致呢肴甸,是因為前32個控制字符在某些情況下會被IBM-PC機當作可打印字符解釋),后面128個字符空間加入了一些歐洲國家用到的重音字符囚巴,以及一些用于畫線條畫的字符原在。
事實上,大部分OEM字符集是兼容ASCII字符集的彤叉,也就是說庶柿,大家對于0x00-0x7F這個范圍的解釋基本是相同的,而對于后半部分0x80-0xFF的解釋卻不一定相同姆坚。甚至有時候同樣的字符在不同OEM字符集中對應(yīng)的字節(jié)也是不同的澳泵。
不同的OEM字符集導(dǎo)致人們無法跨機器交流各種文檔实愚。
多字節(jié)字符集(MBCS)和中文字符集
上面我們提到的字符集都是基于單字節(jié)編碼兼呵,也就是說,一個字節(jié)翻譯成一個字符腊敲。這對于拉丁語系國家來說可能沒有什么問題击喂,因為他們通過擴展第8個比特,就可以得到256個字符了碰辅,足夠用了懂昂。但是對于亞洲國家來說,256個字符是遠遠不夠用的没宾。因此這些國家的人為了用上電腦凌彬,又要保持和ASCII字符集的兼容,就發(fā)明了多字節(jié)編碼方式循衰,相應(yīng)的字符集就稱為多字節(jié)字符集铲敛。例如中國使用的就是雙字節(jié)字符集編碼(DBCS,Double Byte Character Set)会钝。
對于單字節(jié)字符集來說伐蒋,代碼頁中只需要有一張碼表即可,上面記錄著256個數(shù)字代表的字符。程序只需要做簡單的查表操作就可以完成編解碼的過程先鱼。而對于多字節(jié)字符集俭正,代碼頁中通常會有很多碼表。那么程序怎么知道該使用哪張碼表去解碼二進制流呢焙畔?答案是掸读,根據(jù)第一個字節(jié)來選擇不同的碼表進行解析。
例如目前最常用的中文字符集GB2312宏多,涵蓋了所有簡體字符以及一部分其他字符寺枉;GBK(K代表擴展的意思)則在GB2312的基礎(chǔ)上加入了對繁體字符等其他非簡體字符(GB18030字符集不是雙字節(jié)字符集,我們在講Unicode的時候會提到)绷落。這兩個字符集的字符都是使用1-2個字節(jié)來表示姥闪。Windows系統(tǒng)采用936代碼頁來實現(xiàn)對GBK字符集的編解碼。在解析字節(jié)流的時候砌烁,如果遇到字節(jié)的最高位是0的話筐喳,那么就使用936代碼頁中的第1張碼表進行解碼,這就和單字節(jié)字符集的編解碼方式一致了函喉。
GB2312(1980年)一共收錄了7445個字符避归,包括6763個漢字和682個其它符號。漢字區(qū)的內(nèi)碼范圍高字節(jié)從B0-F7管呵,低字節(jié)從A1-FE梳毙,占用的碼位是72*94=6768。其中有5個空位是D7FA-D7FE捐下。
GB2312支持的漢字太少账锹。1995年的漢字擴展規(guī)范GBK1.0收錄了21886個符號,它分為漢字區(qū)和圖形符號區(qū)坷襟。漢字區(qū)包括21003個字符奸柬。GBK全稱《漢字內(nèi)碼擴展規(guī)范》,支持國際標準ISO/IEC10646-1和國家標準GB13000-1中的全部中日韓漢字婴程。GBK字符集中所有字符占2個字節(jié)廓奕,不論中文英文都是2個字節(jié)。 沒有特殊的編碼方式档叔,習慣稱呼GBK 編碼桌粉。一般在國內(nèi),漢字較多時使用衙四。
從ASCII铃肯、GB2312到GBK,這些編碼方法是向下兼容的届搁,即同一個字符在這些方案中總是有相同的編碼缘薛,后面的標準支持更多的字符窍育。在這些編碼中,英文和中文可以統(tǒng)一地處理宴胧。區(qū)分中文編碼的方法是高字節(jié)的最高位不為0漱抓。按照程序員的稱呼,GB2312恕齐、GBK都屬于雙字節(jié)字符集 (DBCS)乞娄。
2000年的GB18030是取代GBK1.0的正式國家標準。該標準收錄了27484個漢字显歧,同時還收錄了藏文仪或、蒙文、維吾爾文等主要的少數(shù)民族文字士骤。從漢字字匯上說范删,GB18030在GB13000.1的20902個漢字的基礎(chǔ)上增加了CJK擴展A的6582個漢字(Unicode碼0x3400-0x4db5),一共收錄了27484個漢字拷肌。
CJK就是中日韓的意思到旦。Unicode為了節(jié)省碼位,將中日韓三國語言中的文字統(tǒng)一編碼巨缘。GB13000.1就是ISO/IEC 10646-1的中文版添忘,相當于Unicode 1.1。
GB18030的編碼采用單字節(jié)若锁、雙字節(jié)和4字節(jié)方案搁骑。其中單字節(jié)、雙字節(jié)和GBK是完全兼容的又固。4字節(jié)編碼的碼位就是收錄了CJK擴展A的6582個漢字仲器。 例如:UCS的0x3400在GB18030中的編碼應(yīng)該是8139EF30,UCS的0x3401在GB18030中的編碼應(yīng)該是8139EF31口予。
微軟提供了GB18030的升級包娄周,但這個升級包只是提供了一套支持CJK擴展A的6582個漢字的新字體:新宋體-18030涕侈,并不改變內(nèi)碼沪停。Windows 的內(nèi)碼仍然是GBK。
ANSI標準裳涛、國家標準木张、ISO標準
不同ASCII衍生字符集的出現(xiàn),讓文檔交流變得非常困難端三,因此各種組織都陸續(xù)進行了標準化流程舷礼。例如美國ANSI組織制定了ANSI標準字符編碼(注意,我們現(xiàn)在通常說到ANSI編碼郊闯,通常指的是平臺的默認編碼妻献,例如英文操作系統(tǒng)中是ISO-8859-1蛛株,中文系統(tǒng)是GBK),ISO組織制定的各種ISO標準字符編碼育拨,還有各國也會制定一些國家標準字符集谨履,例如中國的GBK,GB2312和GB18030熬丧。
操作系統(tǒng)在發(fā)布的時候笋粟,通常會往機器里預(yù)裝這些標準的字符集還有平臺專用的字符集,這樣只要你的文檔是使用標準字符集編寫的析蝴,通用性就比較高了害捕。例如你用GB2312字符集編寫的文檔,在中國大陸內(nèi)的任何機器上都能正確顯示闷畸。同時尝盼,我們也可以在一臺機器上閱讀多個國家不同語言的文檔了,前提是本機必須安裝該文檔使用的字符集佑菩。
ISO-8859-1
ISO-8859-1收錄的字符除ASCII收錄的字符外东涡,還包括西歐語言、希臘語倘待、泰語疮跑、阿拉伯語、希伯來語對應(yīng)的文字符號凸舵。因為ISO-8859-1編碼范圍使用了單字節(jié)內(nèi)的所有空間祖娘,在支持ISO-8859-1的系統(tǒng)中傳輸和存儲其他任何編碼的字節(jié)流都不會被拋棄。換言之啊奄,把其他任何編碼的字節(jié)流當作ISO-8859-1編碼看待都沒有問題渐苏。這是個很重要的特性,MySQL數(shù)據(jù)庫默認編碼是Latin1就是利用了這個特性菇夸。ASCII編碼是一個7位的容器琼富,ISO-8859-1編碼是一個8位的容器。
由此可見,ISO-8859-1只占1個字節(jié)裙盾,且MySQL數(shù)據(jù)庫默認編碼就是ISO-8859-1凯旋,有時,tomcat服務(wù)器默認也是使用ISO-8859-1編碼械蹋,然而ISO-8859-1是不支持中文的,有時這就是在瀏覽器上顯示亂碼的原因羞芍。
Unicode的出現(xiàn)
雖然通過使用不同字符集哗戈,我們可以在一臺機器上查閱不同語言的文檔,但是我們?nèi)匀粺o法解決一個問題:在一份文檔中顯示所有字符荷科。為了解決這個問題唯咬,我們需要一個全人類達成共識的巨大的字符集纱注,這就是Unicode字符集。
Unicode的學名是"Universal Multiple-Octet Coded Character Set"胆胰,簡稱為UCS奈附。UCS可以看作是"Unicode Character Set"的縮寫。
Unicode字符集涵蓋了目前人類使用的所有字符煮剧,并為每個字符進行統(tǒng)一編號斥滤,分配唯一的字符碼(Code Point)。Unicode字符集將所有字符按照使用上的頻繁度劃分為17個層面(Plane)勉盅,每個層面上有216=65536個字符碼空間佑颇。其中第0個層面BMP,基本涵蓋了當今世界用到的所有字符草娜。其他的層面要么是用來表示一些遠古時期的文字挑胸,要么是留作擴展。我們平常用到的Unicode字符宰闰,一般都是位于BMP層面上的茬贵。目前Unicode字符集中尚有大量字符空間未使用。
在Unicode出現(xiàn)之前移袍,所有的字符集都是和具體編碼方案綁定在一起的解藻,都是直接將字符和最終字節(jié)流綁定死了,例如ASCII編碼系統(tǒng)規(guī)定使用7比特來編碼ASCII字符集葡盗;GB2312以及GBK字符集螟左,限定了使用最多2個字節(jié)來編碼所有字符,并且規(guī)定了字節(jié)序觅够。這樣的編碼系統(tǒng)通常用簡單的查表胶背,也就是通過代碼頁就可以直接將字符映射為存儲設(shè)備上的字節(jié)流了。
這種方式的缺點在于喘先,字符和字節(jié)流之間耦合得太緊密了钳吟,從而限定了字符集的擴展能力。假設(shè)以后火星人入住地球了窘拯,要往現(xiàn)有字符集中加入火星文就變得很難甚至不可能了红且,而且很容易破壞現(xiàn)有的編碼規(guī)則。
因此Unicode在設(shè)計上考慮到了這一點树枫,將字符集和字符編碼方案分離開直焙。
也就是說,雖然每個字符在Unicode字符集中都能找到唯一確定的編號(字符碼砂轻,又稱Unicode碼),但是決定最終字節(jié)流的卻是具體的字符編碼斤吐。例如同樣是對Unicode字符“A”進行編碼搔涝,UTF-8字符編碼得到的字節(jié)流是0x41厨喂,而UTF-16(大端模式)得到的是0x00 0x41。
UCS只是規(guī)定如何編碼庄呈,并沒有規(guī)定如何傳輸蜕煌、保存這個編碼。例如“漢”字的UCS編碼是6C49诬留,我可以用4個ascii數(shù)字來傳輸斜纪、保存這個編碼;也可以用utf-8編碼:3個連續(xù)的字節(jié)E6 B1 89來表示它文兑。關(guān)鍵在于通信雙方都要認可盒刚。UTF-8、UTF-7绿贞、UTF-16都是被廣泛接受的方案因块。UTF-8的一個特別的好處是它與ISO-8859-1完全兼容。UTF是“UCS Transformation Format”的縮寫籍铁。
UCS-2涡上、UCS-4、BMP
UCS有兩種格式:UCS-2和UCS-4拒名。顧名思義吩愧,UCS-2就是用兩個字節(jié)編碼,UCS-4就是用4個字節(jié)(實際上只用了31位增显,最高位必須為0)編碼耻警。下面讓我們做一些簡單的數(shù)學游戲:
UCS-2有216=65536個碼位,UCS-4有231=2147483648個碼位甸怕。
UCS-4根據(jù)最高位為0的最高字節(jié)分成27=128個group甘穿。每個group再根據(jù)次高字節(jié)分為256個plane。每個plane根據(jù)第3個字節(jié)分為256行 (rows)梢杭,每行包含256個cells温兼。當然同一行的cells只是最后一個字節(jié)不同,其余都相同武契。
group 0的plane 0被稱作Basic Multilingual Plane, 即BMP募判。或者說UCS-4中咒唆,高兩個字節(jié)為0的碼位被稱作BMP届垫。
將UCS-4的BMP去掉前面的兩個零字節(jié)就得到了UCS-2。在UCS-2的兩個字節(jié)前加上兩個零字節(jié)全释,就得到了UCS-4的BMP装处。而目前的UCS-4規(guī)范中還沒有任何字符被分配在BMP之外。
常見的Unicode編碼
如果要我們來實現(xiàn)Unicode字符集中BMP字符的編碼方案浸船,我們會怎么實現(xiàn)妄迁?由于BMP層面上有216=65536個字符碼寝蹈,因此我們只需要兩個字節(jié)就可以完全表示這所有的字符了。
舉個例子登淘,“中”的Unicode字符碼是0x4E2D(01001110 00101101)箫老,那么我們可以編碼為01001110 00101101(大端)或者00101101 01001110 (小端)。
UCS-2和UTF-16對于BMP層面的字符均是使用2個字節(jié)來表示黔州,并且編碼得到的結(jié)果完全一致耍鬓。不同之處在于,UCS-2最初設(shè)計的時候只考慮到BMP字符流妻,因此使用固定2個字節(jié)長度牲蜀,也就是說,他無法表示Unicode其他層面上的字符合冀,而UTF-16為了解除這個限制各薇,支持Unicode全字符集的編解碼,采用了變長編碼君躺,最少使用2個字節(jié)峭判,如果要編碼BMP以外的字符,則需要4個字節(jié)結(jié)對棕叫。
Windows從NT時代開始就采用了UTF-16編碼林螃,很多流行的編程平臺,例如.Net俺泣,Java疗认,Qt還有Mac下的Cocoa等都是使用UTF-16作為基礎(chǔ)的字符編碼。例如代碼中的字符串伏钠,在內(nèi)存中相應(yīng)的字節(jié)流就是用UTF-16編碼過的横漏。
UTF-8
UTF-8應(yīng)該是目前應(yīng)用最廣泛的一種Unicode編碼方案。由于UCS-2/UTF-16對于ASCII字符使用兩個字節(jié)進行編碼熟掂,存儲和處理效率相對低下缎浇,并且由于ASCII字符經(jīng)過UTF-16編碼后得到的兩個字節(jié),高字節(jié)始終是0x00赴肚,很多C語言的函數(shù)都將此字節(jié)視為字符串末尾從而導(dǎo)致無法正確解析文本素跺。因此一開始推出的時候遭到很多西方國家的抵觸,大大影響了Unicode的推行誉券。后來聰明的人們發(fā)明了UTF-8編碼指厌,解決了這個問題。
UTF-8編碼方案采用1-4個字節(jié)來編碼字符踊跟,方法其實也非常簡單踩验。
對于ASCII字符的編碼使用單字節(jié),和ASCII編碼一摸一樣,這樣所有原先使用ASCII編解碼的文檔就可以直接轉(zhuǎn)到UTF-8編碼了晰甚。對于其他字符衙传,則使用2-4個字節(jié)來表示决帖,其中厕九,首字節(jié)前置1的數(shù)目代表正確解析所需要的字節(jié)數(shù),剩余字節(jié)的高2位始終是10地回。例如首字節(jié)是1110yyyy扁远,前置有3個1,說明正確解析總共需要3個字節(jié)刻像,需要和后面2個以10開頭的字節(jié)結(jié)合才能正確解析得到字符畅买。
如上表所示,對于只需要1個字節(jié)的字符细睡,UTF-8采用ASCII碼的編碼方式谷羞,最高位補0來表示。
例如:01000001我們就是用01000001來表示溜徙,對于一個字節(jié)的字符湃缎,其實就是直接使用地址表示。
而對于n個字節(jié)的字符(n>1)蠢壹,即大于一個字節(jié)的字符嗓违,采用第一個字節(jié)前n位補1。第n+1位填0图贸,后面字節(jié)的前兩位一律設(shè)為10蹂季。剩下的沒有提及的二進制位,全部為這個符號的unicode碼疏日。
例如:漢字嚴的Unicode碼是4E25轉(zhuǎn)換成二進制就是01001110 00100101共15位偿洁,根據(jù)上表可知使用UTF-8字符編碼后占3個字節(jié),因此前3位是1沟优,第4位(n+1位)是0涕滋,后面兩個字節(jié)中每個字節(jié)的前兩位都是10,即1110 xxxx 10 xxxxxx 10xxxxxx。填充進去后就變成了1110 0100 10 111000 10 100101共計24位占3個字節(jié)净神。
由此可見何吝,英文在UTF-8字符編碼后只占1個字節(jié),中文占了3個字節(jié)鹃唯。
雖然UTF-8編碼沒有GBK編碼占的空間小爱榕,但他勝在面向全世界,至于使用哪一種編碼還是取決于具體的使用環(huán)境坡慌。
帶簽名的UTF-8指的是什么意思黔酥?
帶簽名指的是字節(jié)流以BOM標記開始。很多軟件會“智能”的探測當前字節(jié)流使用的字符編碼,這種探測過程出于效率考慮跪者,通常會提取字節(jié)流前面若干個字節(jié)棵帽,看看是否符合某些常見字符編碼的編碼規(guī)則。由于UTF-8和ASCII編碼對于純英文的編碼是一樣的渣玲,無法區(qū)分開來逗概,因此通過在字節(jié)流最前面添加BOM標記可以告訴軟件,當前使用的是Unicode編碼忘衍,判別成功率就十分準確了逾苫。但是需要注意,不是所有軟件或者程序都能正確處理BOM標記枚钓,例如PHP就不會檢測BOM標記铅搓,直接把它當普通字節(jié)流解析了。因此如果你的PHP文件是采用帶BOM標記的UTF-8進行編碼的搀捷,那么有可能會出現(xiàn)問題星掰。
UTF-32
UTF-32 是固定長度的編碼,始終占用 4 個字節(jié)嫩舟,足以容納所有的 Unicode 字符氢烘,所以直接存儲 Unicode 編號即可,不需要任何編碼轉(zhuǎn)換至壤。浪費了空間威始,提高了效率。
GB18030
任何能夠?qū)nicode字符映射為字節(jié)流的編碼都屬于Unicode編碼像街。中國的GB18030編碼黎棠,覆蓋了Unicode所有的字符,因此也算是一種Unicode編碼镰绎。只不過他的編碼方式并不像UTF-8或者UTF-16一樣脓斩,將Unicode字符的編號通過一定的規(guī)則進行轉(zhuǎn)換,而只能通過查表的手段進行編碼畴栖。
UTF的字節(jié)序和BOM
UTF-8以字節(jié)為編碼單元随静,沒有字節(jié)序的問題。UTF-16以兩個字節(jié)為編碼單元吗讶,在解釋一個UTF-16文本前燎猛,首先要弄清楚每個編碼單元的字節(jié)序。例如“奎”的Unicode編碼是594E照皆,“乙”的Unicode編碼是4E59重绷。如果我們收到UTF-16字節(jié)流“594E”,那么這是“奎”還是“乙”膜毁?
Unicode規(guī)范中推薦的標記字節(jié)順序的方法是BOM昭卓。BOM不是“Bill Of Material”的BOM表愤钾,而是Byte Order Mark。BOM是一個有點小聰明的想法:
在UCS編碼中有一個叫做"ZERO WIDTH NO-BREAK SPACE"的字符候醒,它的編碼是FEFF能颁。而FFFE在UCS中是不存在的字符,所以不應(yīng)該出現(xiàn)在實際傳輸中倒淫。UCS規(guī)范建議我們在傳輸字節(jié)流前伙菊,先傳輸字符"ZERO WIDTH NO-BREAK SPACE"。
這樣如果接收者收到FEFF昌简,就表明這個字節(jié)流是Big-Endian的占业;如果收到FFFE绒怨,就表明這個字節(jié)流是Little-Endian的纯赎。因此字符"ZERO WIDTH NO-BREAK SPACE"又被稱作BOM。
UTF-8不需要BOM來表明字節(jié)順序南蹂,但可以用BOM來表明編碼方式犬金。字符"ZERO WIDTH NO-BREAK SPACE"的UTF-8編碼是EF BB BF(讀者可以用我們前面介紹的編碼方法驗證一下)。所以如果接收者收到以EF BB BF開頭的字節(jié)流六剥,就知道這是UTF-8編碼了晚顷。
Windows就是使用BOM來標記文本文件的編碼方式的。
Unicode編碼和以前的字符集編碼有什么區(qū)別疗疟?
早期字符編碼该默、字符集和代碼頁等概念都是表達同一個意思。例如GB2312字符集策彤、GB2312編碼栓袖,936代碼頁,實際上說的是同個東西店诗。但是對于Unicode則不同裹刮,Unicode字符集只是定義了字符的集合和唯一編號,Unicode編碼庞瘸,則是對UTF-8捧弃、UCS-2/UTF-16等具體編碼方案的統(tǒng)稱而已,并不是具體的編碼方案擦囊。所以當需要用到字符編碼的時候违霞,你可以寫gb2312,codepage936瞬场,utf-8买鸽,utf-16,但請不要寫unicode泌类。
關(guān)于亂碼
亂碼指的是程序顯示出來的字符文本無法用任何語言去解讀癞谒。一般情況下會包含大量?或者?底燎。亂碼問題是所有計算機用戶或多或少會遇到的問題。造成亂碼的原因就是因為使用了錯誤的字符編碼去解碼字節(jié)流弹砚,因此當我們在思考任何跟文本顯示有關(guān)的問題時双仍,請時刻保持清醒:當前使用的字符編碼是什么。只有這樣桌吃,我們才能正確分析和處理亂碼問題朱沃。
當程序使用特定字符編碼解析字節(jié)流的時候,一旦遇到無法解析的字節(jié)流時茅诱,就會用?或者?來替代逗物。因此,一旦你最終解析得到的文本包含這樣的字符瑟俭,而你又無法得到原始字節(jié)流的時候翎卓,說明正確的信息已經(jīng)徹底丟失了,嘗試任何字符編碼都無法從這樣的字符文本中還原出正確的信息來摆寄。
總結(jié)
經(jīng)過上邊的介紹失暴,我們可以大致認為,現(xiàn)在流行的一些編碼方案都是在兼容 ASCII 的基礎(chǔ)上來實現(xiàn)的微饥。為了滿足各國家地區(qū)的更多字符的編碼需求逗扒,出現(xiàn)了 ANSI 編碼標準,但是該編碼標準在具體各地區(qū)國家的實現(xiàn)上是彼此不兼容的欠橘。為了滿足世界各國字符編碼的兼容性需求矩肩,Unicode 定義了一個統(tǒng)一、完備的字符集肃续。為了實現(xiàn) Unicode 字符集在編碼上的需求黍檩,又誕生了 UTF-8、UTF-16等等編碼方案痹升。
術(shù)語解釋
字符集(Character Set)建炫,字面上的理解就是字符的集合,例如ASCII字符集疼蛾,定義了128個字符肛跌;GB2312定義了7445個字符。而計算機系統(tǒng)中提到的字符集準確來說察郁,指的是已編號的字符的有序集合(不一定是連續(xù))衍慎。
字符碼(Code Point) 指的就是字符集中每個字符的數(shù)字編號。例如ASCII字符集用0-127這連續(xù)的128個數(shù)字分別表示128個字符皮钠;GBK字符集使用區(qū)位碼的方式為每個字符編號稳捆,首先定義一個94X94的矩陣,行稱為“區(qū)”麦轰,列稱為“位”乔夯,然后將所有國標漢字放入矩陣當中砖织,這樣每個漢字就可以用唯一的“區(qū)位”碼來標識了。例如“中”字被放到54區(qū)第48位末荐,因此字符碼就是5448侧纯。而Unicode中將字符集按照一定的類別劃分到0~16這17個層面(Planes)中,每個層面中擁有216=65536個字符碼甲脏,因此Unicode總共擁有的字符碼眶熬,也即是Unicode的字符空間總共有17*65536=1114112。
編碼的過程是將字符轉(zhuǎn)換成字節(jié)流块请。
解碼的過程是將字節(jié)流解析為字符娜氏。
字符編碼(Character Encoding)是將字符集中的字符碼映射為字節(jié)流的一種具體實現(xiàn)方案。例如ASCII字符編碼規(guī)定使用單字節(jié)中低位的7個比特去編碼所有的字符墩新。例如‘A’的編號是65贸弥,用單字節(jié)表示就是0x41,因此寫入存儲設(shè)備的時候就是b’01000001’抖棘。GBK編碼則是將區(qū)位碼(GBK的字符碼)中的區(qū)碼和位碼的分別加上0xA0(160)的偏移(之所以要加上這樣的偏移茂腥,主要是為了和ASCII碼兼容),例如剛剛提到的“中”字切省,區(qū)位碼是5448,十六進制是0x3630帕胆,區(qū)碼和位碼分別加上0xA0的偏移之后就得到0xD6D0朝捆,這就是“中”字的GBK編碼結(jié)果。
代碼頁(Code Page)一種字符編碼具體形式懒豹。早期字符相對少芙盘,因此通常會使用類似表格的形式將字符直接映射為字節(jié)流,然后通過查表的方式來實現(xiàn)字符的編解碼×郴啵現(xiàn)代操作系統(tǒng)沿用了這種方式儒老。例如Windows使用936代碼頁、Mac系統(tǒng)使用EUC-CN代碼頁實現(xiàn)GBK字符集的編碼记餐,名字雖然不一樣驮樊,但對于同一漢字的編碼肯定是一樣的。
大小端的說法源自《格列佛游記》片酝。我們知道囚衔,雞蛋通常一端大一端小,小人國的人們對于剝蛋殼時應(yīng)從哪一端開始剝起有著不一樣的看法雕沿。同樣练湿,計算機界對于傳輸多字節(jié)字(由多個字節(jié)來共同表示一個數(shù)據(jù)類型)時,是先傳高位字節(jié)(大端)還是先傳低位字節(jié)(小端)也有著不一樣的看法审轮,這就是計算機里頭大小端模式的由來了肥哎。無論是寫文件還是網(wǎng)絡(luò)傳輸辽俗,實際上都是往流設(shè)備進行寫操作的過程,而且這個寫操作是從流的低地址向高地址開始寫(這很符合人的習慣)篡诽,對于多字節(jié)字來說榆苞,如果先寫入高位字節(jié),則稱作大端模式霞捡。反之則稱作小端模式坐漏。也就是說,大端模式下碧信,字節(jié)序和流設(shè)備的地址順序是相反的赊琳,而小端模式則是相同的。一般網(wǎng)絡(luò)協(xié)議都采用大端模式進行傳輸砰碴。
參考資料
談?wù)刄nicode編碼躏筏,簡要解釋UCS、UTF呈枉、BMP趁尼、BOM等名詞
字符集和字符編碼學習總結(jié)
字符編碼的概念(UTF-8、UTF-16猖辫、UTF-32都是什么鬼)
關(guān)于字符編碼酥泞,你所需要知道的(ASCII,Unicode,Utf-8,GB2312…)
字符集和編碼詳解(學習,看一篇就夠了)
字符集詳解(一看就懂系列)