一直覺得:一個(gè)合格的程序員必須要了解清楚編碼。
程序員的工作中很頻繁的一個(gè)場景是:啊啊,又(中文)亂碼了输硝,怎么弄, 然后搜索引擎搜了后總是一頓亂試程梦,可能當(dāng)時(shí)解決了点把,下次遇到問題,又不知道怎么辦屿附,一直沒有深入理解其原理郎逃。
先思考以下幾個(gè)問題:
- 為什么要用十六進(jìn)制 (0x7f) 來標(biāo)識(shí)字節(jié)?
- 計(jì)算機(jī)怎么讀韧Ψ荨(文本/二進(jìn)制)數(shù)據(jù)并解碼并顯示褒翰?
- 經(jīng)常遇到中文亂碼是怎么造成的?
- Unicode 與 UTF-8 是什么關(guān)系匀泊?分別解決了什么問題优训?
- 經(jīng)常看到 (不)帶 BOM 的 UTF-8, 其中 BOM 是什么探赫?
從本文下面的一些分析來一一解答型宙。
序言
計(jì)算機(jī)里面存儲(chǔ)的都是二進(jìn)制序列 0101010,思考怎么把它通過一套規(guī)則與現(xiàn)實(shí)世界的字符對應(yīng)起來伦吠,就出現(xiàn)了各種字符集,字符編碼規(guī)范魂拦。
為什么要用十六進(jìn)制毛仪?
很簡單,為了簡潔芯勘,方便箱靴。二進(jìn)制 0101 列起來太多,且浪費(fèi)存儲(chǔ)空間荷愕。
1byte = 8bit = 2hex 一個(gè)字節(jié) 需要8位二進(jìn)制標(biāo)識(shí)衡怀,而只需要2個(gè)十六進(jìn)制即可。
我們經(jīng)常能看到的16進(jìn)制通常以 0x \x 開頭為標(biāo)識(shí)安疗。兩個(gè)十六進(jìn)制為一個(gè)字節(jié)抛杨。下文的很多表示都用的十六進(jìn)制。
已編碼字符集(CCS)
從抽象字符清單到非負(fù)整數(shù)(范圍不必連續(xù))的映射荐类。該整數(shù)稱為抽象字符被賦予的碼點(diǎn)(code point怖现,或稱碼位code position),該字符則稱為已編碼字符。注意屈嗤,碼點(diǎn)并非比特或字節(jié)潘拨,因此與計(jì)算機(jī)表示無關(guān)。碼點(diǎn)的取值范圍由編碼標(biāo)準(zhǔn)限定饶号,該范圍稱為編碼空間(code space)铁追。在一個(gè)標(biāo)準(zhǔn)中,已編碼字符集也稱為字符編碼茫船、已編碼字符清單琅束、字符集定義或碼頁(code page)。
單字節(jié)編碼方案
ASCII
最開始 ASCII (American Standard Code for Information Interchange)美國信息交換標(biāo)準(zhǔn)代碼透硝,是基于拉丁字母 的一套電腦編碼系統(tǒng)狰闪。它主要用于顯示現(xiàn)代英語。
ASCII 使用一個(gè)字節(jié) 8 bit (實(shí)際上是7 bit) 定義了 2^7 = 128 個(gè)字符濒生,如下
二進(jìn)制:0000 0000 - 0111 1111
十進(jìn)制: 0 - 127
十六進(jìn)制:0x00 - 0x7F
ISO-8859-1
ISO-8859-1 (又稱Latin-1或“西歐語言”) 擴(kuò)展了 ASCII 因?yàn)槔碚撋?8 bit 可以表示 2^8-1 = 255 個(gè)字符埋泵。
它以ASCII為基礎(chǔ),在空置的 0xA0-0xFF 的范圍內(nèi)罪治,加入96個(gè)字母及符號(hào)丽声。
可以看到 0x7F、0x80-0x9F 在此字符集中未有定義觉义。例如歐元符號(hào)(0x80 (gbk-cp936))
然后偉大的漢字沒有對應(yīng)的編碼雁社,后來國家自己搞了一套編碼擴(kuò)展了 ASCII 。 例如 GB2312 到后來的 GBK 等晒骇。
多字節(jié)編碼
由于單字節(jié)能表示的字符太少霉撵,且同時(shí)也需要與 ASCII 編碼保持兼容,所以不同國家和地區(qū)紛紛在 ASCII 基礎(chǔ)上制定自己的字符集洪囤。這些字符集使用大于0x80的編碼作為一個(gè)前導(dǎo)字節(jié)徒坡,前導(dǎo)字節(jié)與緊跟其后的第二(甚至第三)個(gè)字節(jié)一起作為單個(gè)字符的實(shí)際編碼;
多字節(jié)編碼的解析
單字節(jié)編碼解析很簡單瘤缩,一個(gè)字節(jié)一個(gè)字節(jié)的讀即可喇完。但是多字節(jié)編碼涉及到字節(jié)解析問題。例如 GBK 使用兩個(gè)字節(jié)編碼剥啤,在遇到大于 0x80 的讀取雙字節(jié)锦溪,小于 0x80 開頭的即為 ASCII 單字節(jié)。(類似于用前綴進(jìn)行區(qū)分)
GBK
GBK 兼容了 ASCII府怯,漢字采用雙字節(jié)編碼刻诊。總體上說第一字節(jié)的范圍是81–FE(也就是不含80和FF)富腊,第二字節(jié)的一部分領(lǐng)域在40–7E坏逢,其他領(lǐng)域在80–FE。
單字節(jié) (0xxx xxxxx)
雙字節(jié) (1xxxx xxxx xxxx xxxx)
需要注意的是:GBK 編碼不支持歐元符號(hào)"€",Windows CP936 碼頁使用 0x80 表示歐元是整,GB18030 編碼則使用0xA2E3表示歐元肖揣。
Unicode 與 UTF-8
Unicode 是一套國際統(tǒng)一標(biāo)準(zhǔn)的字符集(規(guī)范),世界上每一個(gè)可見字符都在 Unicode 里面有一一映射關(guān)系浮入。
每個(gè)字符對應(yīng) Unicode 里面的一個(gè)碼點(diǎn)龙优。
Unicode 使用四個(gè)字節(jié),定義了每個(gè)字符的碼點(diǎn)事秀。然而存儲(chǔ)數(shù)據(jù)如果全部使用 Unicode 會(huì)造成很大的浪費(fèi)彤断。所以出現(xiàn)了 UTF-8 之類的編碼格式。UTF (Unicode Translation Format, UTF )翻譯下就是 Unicode 轉(zhuǎn)換格式易迹。定義了如果將 Unicode 與字節(jié)(二進(jìn)制序列)一一映射宰衙。
UTF-8
UTF-8 是一種針對 Unicode 的可變寬度字符編碼,可表示 Unicode 標(biāo)準(zhǔn)中的任何字符睹欲。UTF-8已逐漸成為電子郵件供炼、網(wǎng)頁及其他存儲(chǔ)或傳輸文字的應(yīng)用中,優(yōu)先采用的編碼窘疮〈撸互聯(lián)網(wǎng)工程工作小組(IETF)要求所有互聯(lián)網(wǎng)協(xié)議都必須支持 UTF-8 編碼。
UTF-8 使用1-4個(gè)字節(jié)為每個(gè)字符編碼闸衫,其規(guī)則如下(x表示可用編碼的比特位):
UTF-8 是定義了 Unicode 映射的字符在計(jì)算機(jī)上如何存儲(chǔ)涛贯。
Unicode碼點(diǎn)(Hex) | UTF-8序列(Bin) | 字節(jié)數(shù) |
---|---|---|
0000 - 007F | 0xxxxxxx | 1 |
0080 - 07FF | 110xxxxx 10xxxxxx | 2 |
0800 - FFFF | 1110xxxx 10xxxxxx 10xxxxxx | 3 |
10000~10FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | 4 |
BOM 是什么?
UTF-8 以字節(jié)為編碼單元蔚出,沒有字節(jié)序的問題弟翘。UTF-16 以兩個(gè)字節(jié)為編碼單元,在解釋一個(gè) UTF-16文本前骄酗,首先要弄清楚每個(gè)編碼單元的字節(jié)序衅胀。例如“奎”的Unicode編碼是594E, “乙”的Unicode編碼是4E59纯路。如果我們收到UTF-16字節(jié)流“594E”持际,那么這是“奎” 還 是“乙”? Unicode 規(guī)范中推薦的標(biāo)記字節(jié)順序的方法是 BOM, Unicode 將幾個(gè)特定的碼點(diǎn) 例如 U+FEFF 的字符定義為字節(jié)順序標(biāo)記(Byte Order Mark, BOM)
不同的編碼方案對零寬不換行字符的解析如下:
編碼方案 | 大字節(jié)序(Hex) | 小字節(jié)序(Hex) |
---|---|---|
UTF-8 | EF BB BF | EF BB BF |
UTF-16 | Fe FF | FF Fe |
UTF-32 | 00 00 Fe FF | FF Fe 00 00 |
UTF-16 和 UTF-32 編碼默認(rèn)為大字節(jié)序站故。UTF-8 以字節(jié)為編碼單元,沒有字節(jié)序問題鲫趁,BOM 只用于表明其編碼格式(signature)*确沸。
Unicode標(biāo)準(zhǔn)并未要求或建議UTF-8編碼使用BOM,但確實(shí)允許BOM出現(xiàn)在文件開頭募闲。帶有BOM的Unicode文件有時(shí)會(huì)帶來一些問題:
- Linux/UNIX系統(tǒng)未使用BOM步脓,因?yàn)樗鼤?huì)破壞現(xiàn)有ASCII文件的語法約定。
- 某些編輯器不會(huì)添加BOM,或者可以選擇是否添加BOM靴患。
- 某些語法分析器可以處理字符串常量或注釋中的UTF-8仍侥,但無法分析文件開頭的BOM。
- 某些程序在文件開頭插入前導(dǎo)字符來聲明文件類型等信息鸳君,這與BOM的用途沖突农渊。