字符集和字符編碼

原文出處: Justin Huang 的博客(@Justin_Programer)

在面試的筆試題里出了一道開放性的題:請簡述Unicode與UTF-8之間的關(guān)系韵丑。一道看似簡單的題青伤,能給出滿意答案的卻寥寥無幾 汤纸,確實(shí)挺失望的傻粘。所以今天就結(jié)合我以前做過的一個(gè)關(guān)于字符編碼的分享,總結(jié)一些與字符編碼相關(guān)的知識和問題南片。如果你這方面的知識已經(jīng)掌握的足夠了赏僧,可以忽略這篇文字。但如果你沒法很好的回答我上面的面試題历涝,或經(jīng)常被亂碼的問題所困擾诅需,還是不妨一讀。

基本常識

1.位和字節(jié)

說起編碼荧库,我們必須從最基礎(chǔ)的說起堰塌,位和字節(jié)(別覺得這個(gè)過于簡單不值一說,我還真見過很多個(gè)不能區(qū)分這兩者的程序員)分衫。位(bit)是指計(jì)算機(jī)里存放的二進(jìn)制值(0/1)蔫仙,而8個(gè)位組合成的“位串”稱為一個(gè)字節(jié),容易算出丐箩,8個(gè)位的組合有256( 28 )個(gè)組合方式摇邦,其取值范圍是“00000000-11111111”,常用十六進(jìn)制來表示屎勘。比如“01000001”就是一個(gè)字節(jié)施籍,其對應(yīng)的十六進(jìn)制值為“0x41”。

而我們通常所講的字符編碼概漱,就是指定義一套規(guī)則丑慎,將真實(shí)世界里的字母/字符與計(jì)算機(jī)的二進(jìn)制序列進(jìn)行相互轉(zhuǎn)化。如我們可以針對上面的字節(jié)定義如下的轉(zhuǎn)換規(guī)則:

01000001(0x41)<->  65  <->  'A'

即用字位序“01000001”來表示字母’A’瓤摧。

2.拉丁字符

拉丁字符是當(dāng)今世界使用最廣泛的符號了竿裂。通常我們說的拉丁字母,指的的是基礎(chǔ)拉丁字母,即指常見的”ABCD“等26個(gè)英文字母照弥,這些字母與英語中一些常見的符號(如數(shù)字腻异,標(biāo)點(diǎn)符號)稱為基礎(chǔ)拉丁字符,這些基礎(chǔ)拉丁字符在使用英語的國家廣為流行这揣,當(dāng)然在中國悔常,也被用來當(dāng)作漢語拼音使用影斑。在歐洲其它一些非英語國家,為滿足其語言需要机打,在基礎(chǔ)拉丁字符的基礎(chǔ)上矫户,加上一些連字符,變音字符(如’á’)残邀,形成了派生拉丁字母皆辽,其表示的字符范圍在各種語言有所不同,而完整意義上的拉丁字符是指這些變體字符與基礎(chǔ)拉丁字符的全集芥挣。是比基礎(chǔ)拉丁字符集大很多的一個(gè)集合驱闷。

編碼標(biāo)準(zhǔn)

前文提到,字符編碼是一套規(guī)則九秀。既然是規(guī)則,就必須有標(biāo)準(zhǔn)粘我。下面我就仔細(xì)說說常見的字符編碼標(biāo)準(zhǔn)鼓蜒。

1.拉丁編碼

ASCII的全稱是American Standard Code for Information Interchange(美國信息交換標(biāo)準(zhǔn)代碼)。顧名思義征字,這是現(xiàn)代計(jì)算機(jī)的發(fā)明國美國人設(shè)計(jì)的標(biāo)準(zhǔn)都弹,而美國是一個(gè)英語國家,他們設(shè)定的ASCII編碼也只支持基礎(chǔ)拉丁字符匙姜。ASCII的設(shè)計(jì)也很簡單畅厢,用一個(gè)字節(jié)(8個(gè)位)來表示一個(gè)字符,并保證最高位的取值永遠(yuǎn)為’0’氮昧。即表示字符含義的位數(shù)為7位框杜,不難算出其可表達(dá)字符數(shù)為27 =128個(gè)。這128個(gè)字符包括95個(gè)可打印的字符(涵蓋了26個(gè)英文字母的大小寫以及英文標(biāo)點(diǎn)符號能)與33個(gè)控制字符(不可打印字符)袖肥。例如下表咪辱,就是幾個(gè)簡單的規(guī)則對應(yīng):

字符類型 字符 二進(jìn)制 16進(jìn)制 10進(jìn)制
可打印字符 A 01000001 0x41 65
可打印字符 a 01100001 0x61 97
控制字符 \r 00001101 0x0D 13
控制字符 \n 00001010 0xA 10

前面說到了,ASCII是美國人設(shè)計(jì)的椎组,只能支持基礎(chǔ)拉丁字符油狂,而當(dāng)計(jì)算機(jī)發(fā)展到歐洲,歐洲其它不只是用的基礎(chǔ)拉丁字符的國家(即用更大的派生拉丁字符集)該怎么辦呢寸癌?

當(dāng)然专筷,最簡單的辦法就是將美國人沒有用到的第8位也用上就好了,這樣能表達(dá)的字符個(gè)數(shù)就達(dá)到了28 =256個(gè)蒸苇,相比較原來磷蛹,增長了一倍, 這個(gè)編碼規(guī)則也常被稱為EASCII溪烤。EASCII基本解決了整個(gè)西歐的字符編碼問題弦聂。但是對于歐洲其它地方如北歐鸟辅,東歐地區(qū),256個(gè)字符還是不夠用莺葫,如是出現(xiàn)了ISO 8859,為解決256個(gè)字符不夠用的問題匪凉,ISO 8859采取的不再是單個(gè)獨(dú)立的編碼規(guī)則,而是由一系列的字符集(共15個(gè))所組成捺檬,分別稱為ISO 8859-n(n=1,2,3…11,13…16,沒有12)再层。其每個(gè)字符集對應(yīng)不同的語言,如ISO 8859-1對應(yīng)西歐語言,ISO 8859-2對應(yīng)中歐語言等堡纬。其中大家所熟悉的Latin-1就是ISO 8859-1的別名,它表示整個(gè)西歐的字符集范圍聂受。 需要注意的一點(diǎn)的是,ISO 8859-n與ASCII是兼容的烤镐,即其0000000(0x00)-01111111(0x7f)范圍段與ASCII保持一致蛋济,而10000000(0x80)-11111111(0xFF)范圍段被擴(kuò)展用到不同的字符集。

2.中文編碼

以上我們接觸到的拉丁編碼炮叶,都是單字節(jié)編碼碗旅,即用一個(gè)字節(jié)來對應(yīng)一個(gè)字符。但這一規(guī)則對于其它字符集更大的語言來說镜悉,并不適應(yīng)祟辟,比如中文,而是出現(xiàn)了用多個(gè)字節(jié)表示一個(gè)字符的編碼規(guī)則侣肄。常見的中文GB2312(國家簡體中文字符集)就是用兩個(gè)字節(jié)來表示一個(gè)漢字(注意是表示一個(gè)漢字旧困,對于拉丁字母,GB2312還是是用一個(gè)字節(jié)來表示以兼容ASCII)稼锅。我們用下表來說明各中文編碼之間的規(guī)則和兼容性吼具。

image

對于中文編碼,其規(guī)則實(shí)現(xiàn)上是很簡單的矩距,一般都是簡單的字符查表即可馍悟,重要的是要注意其相互之間的兼容性問題。如如果選擇BIG5字符集編碼剩晴,就不能很好的兼容GB2312锣咒,當(dāng)做繁轉(zhuǎn)簡時(shí)有可能導(dǎo)致個(gè)別字的沖突與不一致,但是GBK與GB2312之間就不存在這樣的問題赞弥。

3.Unicode

以上可以看到毅整,針對不同的語言采用不同的編碼,有可能導(dǎo)致沖突與不兼容性绽左,如果我們打開一份字節(jié)序文件悼嫉,如果不知道其編碼規(guī)則,就無法正確解析其語義拼窥,這也是產(chǎn)生亂碼的根本原因戏蔑。有沒有一種規(guī)則是全世界字符統(tǒng)一的呢蹋凝?當(dāng)然有,Unicode就是一種总棵。為了能獨(dú)立表示世界上所有的字符鳍寂,Unicode采用4個(gè)字節(jié)表示一個(gè)字符,這樣理論上Unicode能表示的字符數(shù)就達(dá)到了231 = 2147483648 = 21 億左右個(gè)字符,完全可以涵蓋世界上一切語言所用的符號情龄。我們以漢字”微信“兩字舉例說明:

  • 微 <-> \u5fae <-> 00000000 00000000 01011111 10101110
  • 信 <-> \u4fe1 <-> 00000000 00000000 01001111 11100001

容易從上面的例子里看出迄汛,Unicode對所有的字符編碼均需要四個(gè)字節(jié),而這對于拉丁字母或漢字來說是浪費(fèi)的骤视,其前面三個(gè)或兩個(gè)字節(jié)均是0,這對信息存儲來說是極大的浪費(fèi)鞍爱。另外一個(gè)問題就是,如何區(qū)分Unicode與其它編碼這也是一個(gè)問題专酗,比如計(jì)算機(jī)怎么知道四個(gè)字節(jié)表示一個(gè)Unicode中的字符睹逃,還是分別表示四個(gè)ASCII的字符呢?

以上兩個(gè)問題祷肯,困擾著Unicode沉填,讓Unicode的推廣上一直面臨著困難。直至UTF-8作為Unicode的一種實(shí)現(xiàn)后躬柬,部分問題得到解決拜轨,才得以完成推廣使用抽减。說到此允青,我們可以回答文章一開始提出的問題了,UTF-8是Unicode的一種實(shí)現(xiàn)方式卵沉,而Unicode是一個(gè)統(tǒng)一標(biāo)準(zhǔn)規(guī)范颠锉,Unicode的實(shí)現(xiàn)方式除了UTF-8還有其它的,比如UTF-16等史汗。

話說當(dāng)初大牛Ben Thomson吃飯時(shí)琼掠,在一張餐巾紙上,設(shè)計(jì)出了UTF-8停撞,然后回到房間瓷蛙,實(shí)現(xiàn)了第一版的UTF-8。關(guān)于UTF-8的基本規(guī)則戈毒,其實(shí)簡單來說就兩條(來自阮一峰老師的總結(jié)):

  • 規(guī)則1:對于單字節(jié)字符艰猬,字節(jié)的第一位為0,后7位為這個(gè)符號的Unicode碼埋市,所以對于拉丁字母冠桃,UTF-8與ASCII碼是一致的。

  • 規(guī)則2:對于n字節(jié)(n>1)的字符道宅,第一個(gè)字節(jié)前n位都設(shè)為1食听,第n+1位為0胸蛛,后面字節(jié)的前兩位一律設(shè)為10,剩下沒有提及的位樱报,全部為這個(gè)符號的Unicode編碼葬项。

通過,根據(jù)以上規(guī)則肃弟,可以建立一個(gè)Unicode取值范圍與UTF-8字節(jié)序表示的對應(yīng)關(guān)系玷室,如下表,

image

舉例來說笤受,’微’的Unicode是’\u5fae’穷缤,二進(jìn)制表示是”00000000 00000000 01011111 10101110“,其取值就位于’0000 0800-0000 FFFF’之間箩兽,所以其UTF-8編碼為’11100101 10111110 10101110’ (加粗部分為固定編碼內(nèi)容)津肛。

通過以上簡單規(guī)則,UTF-8采取變字節(jié)的方式汗贫,解決了我們前文提到的關(guān)于Unicode的兩大問題身坐。同時(shí),作為中文使用者需要注意的一點(diǎn)是Unicode(UTF-8)與GBK落包,GB2312這些漢字編碼規(guī)則是完全不兼容的部蛇,也就是說這兩者之間不能通過任何算法來進(jìn)行轉(zhuǎn)換,如需轉(zhuǎn)換,一般通過GBK查表的方式來進(jìn)行咐蝇。

常見問題及解答

1.windows Notepad中的編碼ANSI保存選項(xiàng)涯鲁,代表什么含義?

ANSI是windows的默認(rèn)的編碼方式有序,對于英文文件是ASCII編碼抹腿,對于簡體中文文件是GB2312編碼(只針對Windows簡體中文版,如果是繁體中文版會采用Big5碼)旭寿。所以警绩,如果將一個(gè)UTF-8編碼的文件,另存為ANSI的方式盅称,對于中文部分會產(chǎn)生亂碼肩祥。

2.什么是UTF-8的BOM?

BOM的全稱是Byte Order Mark缩膝,BOM是微軟給UTF-8編碼加上的混狠,用于標(biāo)識文件使用的是UTF-8編碼,即在UTF-8編碼的文件起始位置逞盆,加入三個(gè)字節(jié)“EE BB BF”檀蹋。這是微軟特有的,標(biāo)準(zhǔn)并不推薦包含BOM的方式。采用加BOM的UTF-8編碼文件俯逾,對于一些只支持標(biāo)準(zhǔn)UTF-8編碼的環(huán)境贸桶,可能導(dǎo)致問題。比如桌肴,在Go語言編程中皇筛,對于包含BOM的代碼文件,會導(dǎo)致編譯出錯(cuò)坠七。詳細(xì)可見我的這篇文章水醋。

3.為什么數(shù)據(jù)庫Latin1字符集(單字節(jié))可以存儲中文呢?

其實(shí)不管需要使用幾個(gè)字節(jié)來表示一個(gè)字符彪置,但最小的存儲單位都是字節(jié),所以拄踪,只要能保證傳輸和存儲的字節(jié)順序不會亂即可。作為數(shù)據(jù)庫拳魁,只是作為存儲的使用的話惶桐,只要能保證存儲的順序與寫入的順序一致,然后再按相同的字節(jié)順序讀出即可潘懊,翻譯成語義字符的任務(wù)交給應(yīng)用程序姚糊。比如’微’的UTF-8編碼是’0xE5 0xBE 0xAE’,那數(shù)據(jù)庫也存儲’0xE5 0xBE 0xAE’三個(gè)字節(jié)授舟,其它應(yīng)用按順序從數(shù)據(jù)庫讀取救恨,再按UTF-8編碼進(jìn)行展現(xiàn)。這當(dāng)然是一個(gè)看似完美的方案释树,但是只要寫入肠槽,存儲,讀取過程中岔出任何別的編碼躏哩,都可能導(dǎo)致亂碼署浩。

4.Mysql數(shù)據(jù)庫中多個(gè)字符集變量(其它數(shù)據(jù)庫其實(shí)也類似)揉燃,它們之間分別是什么關(guān)系扫尺?

image

我們分別解釋:

character_set_client:客戶端來源的數(shù)據(jù)使用的字符集,用于客戶端顯式告訴客戶端所發(fā)送的語句中的的字符編碼炊汤。

character_set_connection:連接層的字符編碼正驻,mysql一般用character_set_connection將客戶端的字符轉(zhuǎn)換為連接層表示的字符。

character_set_results:查詢結(jié)果從數(shù)據(jù)庫讀出后抢腐,將轉(zhuǎn)換為character_set_results返回給前端姑曙。

而我們常見的解決亂碼問題的操作:

MySQL


mysql_query('SET NAMES GBK')

其相當(dāng)于將以上三個(gè)字符集統(tǒng)一全部設(shè)置為GBK,這三者一致時(shí)迈倍,一般就解決了亂碼問題伤靠。

character_set_database:當(dāng)前選中數(shù)據(jù)庫的默認(rèn)字符集,如當(dāng)create table時(shí)沒有指定字符集啼染,將默認(rèn)選擇該字符集宴合。

character_set_database已經(jīng)character_set_system焕梅,一般用于數(shù)據(jù)庫系統(tǒng)內(nèi)部的一些字符編碼,處理數(shù)據(jù)亂碼問題時(shí)卦洽,我們基本可以忽略贞言。

5.什么情況下,表示信息丟失阀蒂?

對于mysql數(shù)據(jù)庫该窗,我們可以通過hex(colname)函數(shù)(其它數(shù)據(jù)庫也有類似的函數(shù),一些文本文件編輯器也具有這個(gè)功能)蚤霞,查看實(shí)際存儲的字節(jié)內(nèi)容酗失,如:

image

通過查看存儲的字節(jié)序,我們可以從根本上了解存儲的內(nèi)容是什么編碼了昧绣。而當(dāng)發(fā)現(xiàn)存儲的內(nèi)容全部是’3F’時(shí)级零,就表明存儲的內(nèi)容由于編碼問題,信息已經(jīng)丟失了滞乙,無法再找回奏纪。

之所以出現(xiàn)這種信息丟失的情況,一般是將不能相互轉(zhuǎn)換的字符集之間做了轉(zhuǎn)換斩启,比如我們在前文說到序调,UTF-8只能一個(gè)個(gè)字節(jié)地變成Latin-1,但是根本不能轉(zhuǎn)換的兔簇,因?yàn)閮烧咧g沒有轉(zhuǎn)換規(guī)則发绢,Unicode的字符對應(yīng)范圍也根本不在Latin-1范圍內(nèi),所以只能用’?(0x3F)’代替了垄琐。

總結(jié):

本文從基礎(chǔ)知識與實(shí)際中碰到的問題上边酒,解析了字符編碼相關(guān)內(nèi)容。而之所以要從頭介紹字符編碼的基礎(chǔ)知識狸窘,是為了更好的從原理上了解與解決日常碰到的編碼問題墩朦,只有從根本上了解了不同字符集的規(guī)則及其之間的關(guān)系與兼容性,才能更好的解決碰到的亂碼問題翻擒,也能避免由于程序中不正確的編碼轉(zhuǎn)換導(dǎo)致的信息丟失問題氓涣。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市陋气,隨后出現(xiàn)的幾起案子劳吠,更是在濱河造成了極大的恐慌,老刑警劉巖巩趁,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件痒玩,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蠢古,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門燃观,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人便瑟,你說我怎么就攤上這事缆毁。” “怎么了到涂?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵脊框,是天一觀的道長。 經(jīng)常有香客問我践啄,道長浇雹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任屿讽,我火速辦了婚禮昭灵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘伐谈。我一直安慰自己烂完,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布诵棵。 她就那樣靜靜地躺著抠蚣,像睡著了一般。 火紅的嫁衣襯著肌膚如雪履澳。 梳的紋絲不亂的頭發(fā)上嘶窄,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機(jī)與錄音距贷,去河邊找鬼柄冲。 笑死,一個(gè)胖子當(dāng)著我的面吹牛忠蝗,可吹牛的內(nèi)容都是我干的现横。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼什湘,長吁一口氣:“原來是場噩夢啊……” “哼长赞!你這毒婦竟也來了晦攒?” 一聲冷哼從身側(cè)響起闽撤,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎脯颜,沒想到半個(gè)月后哟旗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年闸餐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了饱亮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡舍沙,死狀恐怖近上,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情拂铡,我是刑警寧澤壹无,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站感帅,受9級特大地震影響斗锭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜失球,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一岖是、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧实苞,春花似錦豺撑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至荧止,卻和暖如春屹电,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背跃巡。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工危号, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人素邪。 一個(gè)月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓外莲,卻偏偏與公主長得像,于是被迫代替她去往敵國和親兔朦。 傳聞我的和親對象是個(gè)殘疾皇子偷线,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

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

  • 計(jì)算機(jī)是如何表示字符的? 計(jì)算中的數(shù)據(jù)都是二進(jìn)制(0和1)沽甥,用一位來存儲1個(gè)0或1声邦,稱為bit。8個(gè)二進(jìn)制序列(8...
    公子拙閱讀 4,034評論 4 17
  • 0 前言 在平時(shí)的開發(fā)過程中大部分人應(yīng)該都遇到過中文亂碼問題摆舟,瀏覽網(wǎng)頁時(shí)也會遇到內(nèi)容顯示亂碼的情況亥曹,一般遇到這種情...
    小豬啊嗚閱讀 2,539評論 1 10
  • 字符是用戶可以讀寫的最小單位媳瞪。計(jì)算機(jī)所能支持的字符組成的集合骗炉,就叫做字符集。字符集通常以二維表的形式存在蛇受。二維表的...
    劉惜有閱讀 8,070評論 2 14
  • 一句葵、概念 字符(Character):各種文字和符號的總稱,包括各國家文字兢仰、標(biāo)點(diǎn)符號笼呆、圖形符號、數(shù)字等旨别。字節(jié)(By...
    TomyZhang閱讀 3,237評論 1 0
  • 前段時(shí)間做leetcode題直接用到了python collections庫里的Counter類(計(jì)數(shù)字典)诗赌,自己...
    ahalshai閱讀 626評論 0 1