我不想寫一篇大而全的文章,也無力去查找那么多資料涵蓋所有知識(shí)點(diǎn),如果想了解更多信息,這里有幾篇我認(rèn)為寫得不錯(cuò)的文章可以提供參考,本文也部分借鑒了其中的內(nèi)容。
基礎(chǔ)概念
- 字符
字符是各種文字和符號(hào)的總稱,包括各國家文字、標(biāo)點(diǎn)符號(hào)、圖形符號(hào)、數(shù)字等,甚至還可以包括無法顯示的字符(比如ASCII標(biāo)準(zhǔn)定義了128個(gè)字符,其中33個(gè)字符無法顯示)。 - 字符集
為了使計(jì)算機(jī)能夠處理字符信息,首先要決定選取哪些字符。這樣就形成了一個(gè)集合,或者說一個(gè)表,稱為字符表(character repertoire)。當(dāng)然,也可以認(rèn)為這就是一個(gè)字符集(character set)。
例如,將所有的英文字母放在一起可以組成一個(gè)字符集,將所有的漢字放在一起可以組成一個(gè)字符集,等等。 - 編碼字符集
對一個(gè)字符集中的所有字符進(jìn)行編號(hào),這種編號(hào)后的字符集叫做編碼字符集(這里的編碼僅僅指編號(hào),不同于下文中的編碼)。常見的編碼字符集有ASCII、Unicode、GBK等。
可以這樣來理解:字符串是由字符構(gòu)成,字符在計(jì)算機(jī)硬件中通過二進(jìn)制形式存儲(chǔ),這種二進(jìn)制形式就是編碼。如果直接使用 “字符串?字符?二進(jìn)制表示(編碼)” ,會(huì)增加不同類型編碼之間轉(zhuǎn)換的復(fù)雜性。所以引入了一個(gè)抽象層,“字符串?字符?與存儲(chǔ)無關(guān)的表示?二進(jìn)制表示(編碼)” ,這樣就可以用一種與存儲(chǔ)無關(guān)的形式表示字符,不同的編碼之間轉(zhuǎn)換時(shí)可以先轉(zhuǎn)換到這個(gè)抽象層,然后再轉(zhuǎn)換為其他編碼形式。
舉個(gè)例子:Unicode 就是 “與存儲(chǔ)無關(guān)的表示”,UTF-8 就是 “二進(jìn)制表示”。
ASCII字符集
通常說的ASCII字符集不包括擴(kuò)展集,只有128個(gè)字符,因此其編碼的存儲(chǔ)只需要使用7個(gè)bit,一個(gè)字節(jié)足夠了最高位永遠(yuǎn)都是0。比如字符'0',其代碼是十六進(jìn)制的0x30,二進(jìn)制表示為00110000。
這里說個(gè)和ASCII字符集有關(guān)的編碼方案,GSM標(biāo)準(zhǔn)協(xié)議規(guī)定單條短信最多存儲(chǔ)140個(gè)字節(jié)的內(nèi)容,如果短信內(nèi)容只包含ASCII字符,因?yàn)锳SCII字符的數(shù)據(jù)首位必定是0,所以GSM標(biāo)準(zhǔn)中規(guī)定了7bit編碼的短信格式,只用7個(gè)bit來連續(xù)存儲(chǔ)ASCII字符,這樣原本140個(gè)8bit的存儲(chǔ)空間,就可以存儲(chǔ)160個(gè)7bit的數(shù)據(jù)。如果誰現(xiàn)在手上還有NOKIA手機(jī),可以拿起來看看只包含ASCII字符的短信是不是可以輸入160個(gè)字符,如果短信中包含了非ASCII字符(比如中文),那么所有字符都會(huì)變成雙字節(jié)存儲(chǔ)的編碼(UCS-2編碼),一條短信的內(nèi)容就恢復(fù)成只能發(fā)送70個(gè)字符?,F(xiàn)如今的智能手機(jī)大多都支持短信拼接,并不是說單條短信的容量增加了,而是將你編寫的超過單條短信容量的短信分成多條發(fā)送,運(yùn)營商也是按多條短信計(jì)的。
GB系列字符集
中文環(huán)境下如果要正常顯示字符,僅依靠ASCII字符集是不行的,因此我們國家制訂了一系列的國標(biāo)(GB),其中就包括GB2312、GB13000、GBK、GB18030......,最新的標(biāo)準(zhǔn)是GB18030,包含70244個(gè)字符。
Unicode字符集
Unicode字符集由多語言軟件制造商組成的統(tǒng)一碼聯(lián)盟(Unicode Consortium)與國際標(biāo)準(zhǔn)化組織的ISO-10646工作組制訂,為各種語言中的每個(gè)字符指定統(tǒng)一且唯一的代碼點(diǎn),以滿足跨語言、跨平臺(tái)轉(zhuǎn)換和處理文本的要求。中、日、韓的三種文字占用了Unicode中0x3000到0x9FFF的部分 Unicode目前普遍采用的是UCS-2編碼,它用兩個(gè)字節(jié)來編碼一個(gè)字符, 比如漢字"一"的編碼是0x4E00。事實(shí)上Unicode對漢字支持不怎么好,這也是沒辦法的,簡體和繁體總共有六七萬個(gè)漢字,而UCS-2最多能表示65536個(gè),所以Unicode只能排除一些幾乎不用的漢字,好在GB2312字符集中常用的簡體漢字也不過6763個(gè),為了能表示所有漢字,Unicode也有UCS-4規(guī)范,就是用 4個(gè)字節(jié)來編碼字符。
Unicode代碼點(diǎn)范圍為0x00x10FFFF,共計(jì)1114112個(gè)代碼點(diǎn),劃分為編號(hào)016的17個(gè)字符平面,每個(gè)平面包含65536個(gè)代碼點(diǎn)。其中編號(hào)為0的平面最為常用,稱為基本多語種平面(Basic Multilingual Plane, BMP);其他平面則稱為輔助語言平面。
為了描述一個(gè)代碼點(diǎn),可以采用U加十六進(jìn)制整數(shù)的方法。比如,U+0041表示英文大寫字母A,U+4E00表示漢字”一”。
編碼
關(guān)于編碼方式,當(dāng)然可以采用類似ASCII字符集的編碼方式——代碼點(diǎn)等值轉(zhuǎn)換法(這是我自己起的名字)。既然Unicode代碼點(diǎn)的值的范圍是0~0x10FFF,那么可以用一個(gè)21bit的編碼單元來編碼,直接把代碼點(diǎn)等值轉(zhuǎn)換成21bit的二進(jìn)制序列。
但是這存在一個(gè)空間使用的問題,例如對于使用英語的人而言,ASCII基本可以滿足使用。如果使用ASCII碼,只需要1個(gè)字節(jié)來存儲(chǔ)字符,但是若使用剛才的思路,需要將近3個(gè)字節(jié)來存儲(chǔ),這顯然是浪費(fèi)空間的。
如果需要支持的字符集再少一些,僅支持編號(hào)0的平面,那至少也有65535個(gè)字符,需要16bit的空間(2字節(jié))來存儲(chǔ)一個(gè)字符,即UCS-2編碼,這種編碼用來存儲(chǔ)ASCII字符也是一種浪費(fèi)。
Unicode在很長一段時(shí)間內(nèi)無法推廣,直到互聯(lián)網(wǎng)的出現(xiàn),為解決Unicode如何在網(wǎng)絡(luò)上傳輸?shù)膯栴},于是面向傳輸?shù)谋姸?UTF(UCS Transfer Format)標(biāo)準(zhǔn)出現(xiàn)了,顧名思義,UTF-8就是每次8個(gè)位傳輸數(shù)據(jù),而UTF-16就是每次16個(gè)位。UTF-8就是在互聯(lián)網(wǎng)上使用最廣的一種Unicode的實(shí)現(xiàn)方式,這是為傳輸而設(shè)計(jì)的編碼,并使編碼無國界,這樣就可以顯示全世界上所有文化的字符了。
UTF-8
UTF-8最大的一個(gè)特點(diǎn),就是它是一種變長的編碼方式。它可以使用1~4個(gè)字節(jié)表示一個(gè)符號(hào),根據(jù)不同的符號(hào)而變化字節(jié)長度,當(dāng)字符在ASCII 碼的范圍時(shí),就用一個(gè)字節(jié)表示,保留了ASCII字符一個(gè)字節(jié)的編碼做為它的一部分,注意的是Unicode一個(gè)中文字符占2個(gè)字節(jié),而UTF-8一個(gè)中 文字符占3個(gè)字節(jié))。從Unicode到UTF-8并不是直接的對應(yīng),而是要過一些算法和規(guī)則來轉(zhuǎn)換。
| Unicode符號(hào)范圍(十六進(jìn)制) | UTF-8編碼方式(二進(jìn)制) |
|---|---|
| 0000 0000-0000 007F | 0xxxxxxx |
| 0000 0080-0000 07FF | 110xxxxx 10xxxxxx |
| 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
| 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
舉個(gè)例子:
還是用中文“一”,其Unicode值為0x4E00,落在0800-FFFF的范圍內(nèi),事實(shí)上中文基本上都在這個(gè)區(qū)域。0x4E00的二進(jìn)制表示為01001110 00000000,轉(zhuǎn)換成UTF-8就是11100100 10111000 10000000,對應(yīng)的十六進(jìn)制表示是0xE4 0xB8 0x80。
UTF-16
UTF-16的編碼單元是16bit,對于每個(gè)代碼點(diǎn),采用1個(gè)或者2個(gè)編碼單元來表示,因此這是一個(gè)變長表示。
UTF-32
UTF-32采用代碼點(diǎn)等值轉(zhuǎn)換法,將每個(gè)代碼點(diǎn)編碼為1個(gè)32bit的編碼單元(四字節(jié)),因此空間效率較低,不如其它Unicode編碼應(yīng)用廣泛。
工具推薦
這里有個(gè)網(wǎng)站提供的轉(zhuǎn)碼效果非常好,比起某些站長工具更標(biāo)準(zhǔn)。

上圖是使用該網(wǎng)站查找中文字符“一”的結(jié)果,可以看到其Unicode值為U+4E00,UTF-8編碼為0xE4 0xB8 0x80,在URL中需要轉(zhuǎn)碼為%E4%B8%80,在js腳本中則是\u4e00......。
舉幾個(gè)栗子
有道翻譯
用有道翻譯的API來做演示,我們通過API獲取單詞"word"的中文翻譯。
GET http://fanyi.youdao.com/openapi.do?keyfrom=WoxLauncher&key=1247918016&type=data&doctype=json&version=1.1&q=word
返回的JSON如下所示:
{
"translation": [
"詞"
],
"basic": {
"us-phonetic": "w?d",
"phonetic": "w??d",
"uk-phonetic": "w??d",
"explains": [
"n. [語] 單詞;話語;消息;諾言;命令",
"vt. 用言辭表達(dá)",
"n. (Word)人名;(英)沃德"
]
},
"query": "word",
"errorCode": 0,
"web": [
{
"value": [
"單詞",
"字",
"字 (計(jì)算機(jī))"
],
"key": "word"
},
{
"value": [
"構(gòu)詞法",
"造詞法",
"詞性轉(zhuǎn)換"
],
"key": "Word Formation"
},
{
"value": [
"關(guān)鍵字",
"中心詞",
"關(guān)鍵詞"
],
"key": "key word"
}
]
}
返回的內(nèi)容包含中文,從響應(yīng)頭我們可以看到返回的JSON使用了UTF-8編碼:

用Wireshark抓包看看具體內(nèi)容:

如上圖所示,translation這個(gè)key對應(yīng)的數(shù)組內(nèi)容應(yīng)該是"詞",所以我們看到高亮區(qū)域的內(nèi)容是22 E8 AF 8D 22,0x22對應(yīng)ASCII字符",0xE8 0xAF 0x8D正是中文詞的UTF-8編碼。
ONE·一個(gè)
這個(gè)API是通過抓包抓出來的,誰讓他們不走h(yuǎn)ttps呢?
GET http://v3.wufazhuce.com:8000/api/reading/index
返回?cái)?shù)據(jù)太多,僅截取一小段進(jìn)行分析。
{
"res": 0,
"data": {
"essay": [
{
"content_id": "2176",
"hp_title": "軟糖| “白日夢” _ 初夏的味道",
"hp_makettime": "2017-04-03 06:00:00",
"guide_word": "我們每周會(huì)選擇一個(gè)主題,由七個(gè)作者繪制不同風(fēng)格的短篇漫畫,每天一幅。",
"start_video": "",
"author": [
{
"user_id": "7742828",
"user_name": "雙麒_宋 ",
"desc": "因愛而畫,美好的作品產(chǎn)生于最壓抑的欲望。",
"wb_name": "",
"is_settled": "0",
"settled_type": "0",
"summary": "因愛而畫,美好的作品產(chǎn)生于最壓抑的欲望。",
"fans_total": "574",
"web_url": "http://image.wufazhuce.com/FoPpyeue8ajoRlZ4Fy39a56o4NO-"
}
],
......
}
......
}
返回的內(nèi)容包含中文,但從響應(yīng)頭我們看不到返回的JSON使用了什么編碼格式:

用Wireshark抓包看看具體內(nèi)容:

如上圖所示,這個(gè)API請求返回的JSON數(shù)據(jù)輸出的是中文的Unicode轉(zhuǎn)義字符,這其實(shí)也是JS對中文的標(biāo)準(zhǔn)處理方式,猜測后臺(tái)可能是NodeJS實(shí)現(xiàn)的。
我是咕咕雞,一個(gè)還在不停學(xué)習(xí)的全棧工程師。
熱愛生活,喜歡跑步,家庭是我不斷向前進(jìn)步的動(dòng)力。