文字編碼總結
〇、引(“聯通事件”)
做一個試驗。
新建一個文本文件,然後用記事本打開,輸入“聯通”,保存,關閉。
再次用記事本打開這個文本文件,你看到了什麼?
這被人戲稱是聯通幹不過移動的根本原因——連自己的名字都存不下來。
下面對字符的編碼進行一下總結,會在其中說明聯通消失的原因。
一、字符編碼的發展
1-1、第一階段:ASCII碼
ASCII = American Standard Code for Information Interchange, “美國信息交換標準碼”
ASCII碼規定每個字符例使用1個字節來表示,也就是8位的二進制組合,那麼就有00000000-11111111一共256種組合,也就是可以表示256個不同的字符。
但是實際上ASCII共計有128個,從0到127,也就是從00000000-01111111,最高位都是0。
-
0-31:是控制字符或通訊專用字符(不可以顯示的字符,其餘爲可顯示字符),如控制符:LF(換行)、CR(回車)等。
-
32-126:是字符,其中32是空格,48-57爲0-9的阿拉伯數字,65-90爲26個大寫英文字母,97-122爲26個小寫英文字母,其餘的是一些標點符號,運算符號等。
1-2、第二階段:ANSI編碼(本地化編碼)
目的:解決漢字等英文字母以外字符的顯示問題。
基本方法:使用最高位是1的字符(防止與ASCII衝突),2個字節表示一個漢字。
編碼轉換方法舉例:
- 以“中”字爲例。採用的編碼標準爲GB2312(中國最新通行的一套兼容編碼標準爲GBK,大字符集)
- “中”的區位碼爲5448(所謂區位碼,就是將所有漢字都用4位數字來表示,主要應用於發電報,以及DOS時代的中文輸入)
- “中”的國標碼爲爲5448的十六進制+2020H=5650H
- “中”的機內碼(就是我們最終要的這個編碼)=國標碼+8080H=D6D0H,也就是說在系統內存儲爲[0xD6,0xD0] 這兩個字節
這些使用 2 個字節來代表一個字符的各種文字延伸編碼方式,稱爲 ANSI 編碼。
注意ANSI編碼指是“本地化”編碼,在各個國家對應的編碼體系是不同的。
在中文環境下以ANSI編碼存儲的文件,在日文環境下打開是亂碼。因爲一個是GB2312編碼,一個是JIS編碼。
(順便吐槽,有個國標組織是很幸福的事情,日本通用的編碼方式至少有四套,特麼的兩個公司做的系統之間通信,弄的跟國際化似的還要轉換編碼,比如:神奇的みずほ銀行)
1-3、第三階段:unicode(國際化編碼)
爲了使國際間信息交流更加方便,國際組織制定了unicode字符集。
爲各種語言中的每一個字符設定了統一併且唯一的數字編號,以滿足跨語言、跨平臺進行文本轉換、處理的要求。
unicode用數字0-0x10FFFF來映射這些字符,最多可以容納1114112個字符。
unicode編碼中,不管什麼文字統一使用4字節表示一個文字。
二、unicode 與 utf
2-1、utf的產生
unicode中,每個字符用 4 個字節在存儲、傳輸時會產生浪費。
UTF-8、UTF-16、UTF-32都是unicode的“緊湊”編碼。都是 可變長度 編碼。(所以想一下java中計算字符串長度時,碰到漢字的時候得到的到底是什麼的長度?)
UTF = Unicode Transformation Format。
其中UTF-32使用32位整數編碼,還是佔4個字節(32bit),所以基本上不會使用。
UTF-8或UTF-16,分別由 8-bit 或 16-bit 爲一個單元組成,下標值較小的編碼點佔用的字節數也少。
2-2、utf-8 的編碼方式說明
utf-8 使用 1~4 個不等的字節來存儲字符編碼。
以“鄭”字爲例,說明從unicode到utf-8的轉換。
- “鄭”字的unicode碼是 \u90d1 (可以通過這樣的網站查詢:站長工具 。或者查詢unicode官網:unicode碼錶)
- 從上表中可以看到,90d1位於第三行(0000 0800 - 0000 FFFF),所以是使用三個字節存儲的。其格式爲 1110yyyy 10yyyyxx 10xxxxxx
- 從“鄭”的最後一個二進制位開始,依次從後向前填入格式中的x,多出的位補0。
- 90d1的二進制碼是 1001 0000 11 010001(已經按照上述格式分割),套用進去就是11101001 10000011 10010001,轉爲十六進制是 0xE98391,也就是“鄭”字的utf-8編碼
2-3、utf-8的便利性
UTF-8 有一個方便的屬性,即最開始128 個字符(ASCII字符)被編碼爲單個字節。
- 任何已經是 ASCII 編碼的字符串和文件無需轉換就可以被 UTF-8 識別。
- 大量的廣泛使用的編程慣例——比如 NULL 結尾,各種控制符\n\t等——在 UTF-8 中也是可以被識別的
2-4、所謂的utf-bom、utf-無bom
使用 NotePad++ 這樣的文本編輯器時,可以將文件“以 UTF-8 無 BOM 格式編碼”。
所謂的BOM,全稱是Byte order mark。
作用是在文件最開始加入一個標識符,讓文本編輯器明確知道讀入的文件是以何種方式編碼的。
常用的BOM如下:
- UTF-8:EF BB BF
- UTF-16:FE FF
- unicode:FF FE (這說法其實不是特別準確,參看後面的2-8節)
2-5、聯通爲什麼保不住自己的名字?
記事本默認的編碼是 ANSI,對於中文系統中就是 GBK 編碼。
“聯”字的編碼是 0xc1aa,二進制 1100 0001 1010 1010。
“通”字的編碼是 0xcda8,二進制 1100 1101 1010 1000
→ 這玩意跟編碼規則中第二行是相符的。
所以記事本再次打開這個文件的時候,將其識別成了“UTF-8 無 BOM 格式”,所以全程按照utf-8編碼規則解析,就變成了亂碼。
人家移動倆字就沒這事。電信啥的也都沒事。
結論:當文檔中的所有字符的二進制編碼在C0≤AA(第一個字節)≤DF 80≤BB(第二個字節)≤BF時,記事本都無法確認文本的編碼格式,就按照UTF-8的格式來顯示。
2-6、UCS 編碼
在第一章提到的第三個階段(國際化)的初期,其實有兩套國際化編碼。
- 國際標準化組織(ISO),1984年制定一份“通用字符集”(Universal Character Set,簡稱UCS),並最終制定了ISO 10646標準
- 統一碼聯盟,由Xerox、Apple等軟件製造商於1988年組成,並且開發了Unicode標準
- 1991年,兩個組織開始合併雙方的工作成果,創立了單一編碼表。但是兩套標準仍然獨立存在,只是unicode使用傳播更爲廣泛。
UCS-2 是 ISO 10646標準爲“通用字符集”(UCS)定義的16位固定長度編碼。
它包含65536個編碼空間,存儲的是全世界最常用的65536個字符編碼。
可以認爲 UCS-2 是 UTF-16 的一個子集,編碼相同。其實UCS-2就是原始的雙字節Unicode編碼。
2-7、Little endian 和 Big endian
UCS-2 這種兩字節定長編碼,在存儲的時候,有兩種格式。
參看 Notepad++ 的編碼菜單,裏面有“以 UCS-2 Little endian 格式編碼”以及“以 UCS-2 Big endian 格式編碼”
比如“鄭”的編碼是 90D1 (沒錯,對於這個字的編碼,unicode、ucs-2和utf-16是相同的)
如果存儲爲 90D1,叫做BE(Big endian);倒過來存爲 D190 的話,稱爲LE(Little endian)。
習慣windows系統的人可能根本沒見過LE,但是在Unix/Linux中這種情況並不少見。
在 UCS-2/unicode(兼容) 編碼標準中,規定在每一個文件的最前面分別加入一個表示編碼順序的字符,這個字符的名字叫做"零寬度非換行空格"(zero width no-break space)。
- 如果頭兩個字節是 FE FF,就表示該文件採用大端方式
- 如果頭兩個字節是 FF FE,就表示該文件採用小端方式
→ 等等,文件頭上的信息不是BOM嗎?
2-8、BOM的更完整、更準確的表述
完整的BOM編碼
- UTF-8:EF BB BF
- UTF-16 (big-endian):FE FF
- UTF-16/UCS-2 (little-endian):FF FE
- UTF-32/UCS-2 (big-endian):00 00 FE FF
- UTF-32 (little-endian): FF FE 00 00
也就是說,表達編碼種類以及BE、LE的工作都是由BOM來完成的
2-8-1、關於 UTF-8 的BOM
其實Linux默認UTF-8編碼應該不帶BOM的。
儘管 Unicode 標準允許在 UTF-8 中使用 BOM,但不含 BOM 的 UTF-8 纔是標準形式,在 UTF-8 文件中放置 BOM 主要是微軟的習慣。
2-8-2、爲什麼2-5的列表中,把BOM爲FF FE的編碼標記爲unicode?
因爲把帶有BOM的小端(LE)的 UTF-16 稱作「Unicode」也是微軟的習慣
2-8-3、對於無BOM的UTF-8文檔,windows的文本編輯器到底是怎麼判斷的?
猜(參看聯通事件)
2-9、MySQL中的utf-8
mysql支持的 utf8 編碼最大字符長度爲3字節,而標準的utf-8最大字符長度爲4字節。
三個字節的 UTF-8 最大能編碼的 Unicode 字符是 0xffff,也就是 Unicode 中所謂的“基本多文種平面(BMP)”。能夠應對絕大多數應用場景。
(MySQL剛開發的時候,unicode本身也沒有提出“輔助平面”,所以3字節的設計是無可厚非的)
但是包括 Emoji 表情、一些特殊漢字在內的字符是無法存儲的。
MySQL 5.5.3 版本以後,推出utf8mb4字符集,用來對應標準的utf-8。
2-10、unicode的編碼規則
可以參看這篇文章中的“Unicode 介紹”一節
三、Base64
3-1、編碼目的
簡單來說,就是把所有字符統一轉換成可見字符。
Base64是一種基於64個可打印字符來表示二進制數據的表示方法。
(ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/)
Base64常用於在通常處理文本數據的場合,表示、傳輸、存儲一些二進制數據,包括MIME的電子郵件及XML的一些複雜數據。
3-2、編碼規則
由於 2的6次冪=64,所以Base64編碼中,以6個比特爲一個單元,對應某個可打印字符。
比如,3個字節一共24比特,那麼就對應4個Base64單元。
也就是說,編碼後的數據長度爲原來的 4/3。
若原數據長度不是3的倍數時且剩下1個輸入數據,則在編碼結果後加2個=;若剩下2個輸入數據,則在編碼結果後加1個=。
舉例:
如果要編碼的字節數不能被3整除,最後會多出1個或2個字節,那麼可以使用下面的方法進行處理:先使用0字節值在末尾補足,使其能夠被3整除,然後再進行Base64的編碼。在編碼後的Base64文本後加上一個或兩個=號,代表補足的字節數。也就是說,當最後剩餘兩個八位字節(2個byte)時,最後一個6位的Base64字節塊有四位是0值,最後附加上兩個等號;如果最後剩餘一個八位字節(1個byte)時,最後一個6位的base字節塊有兩位是0值,最後附加一個等號。
3-3、UTF-7
UTF-7是一個修改版Base64(Modified Base64)。
主要是將UTF-16的數據,用Base64的方法編碼爲可打印的ASCII字符序列。目的是傳輸Unicode數據。
主要的區別在於不用等號=補餘,因爲該字符通常需要大量的轉譯。
四、urlcode
4-1、編碼目的
URL編碼(URL encoding),也稱作百分號編碼(Percent-encoding)。
適用於統一資源標識符(URI)的編碼,也用於爲"application/x-www-form-urlencoded" MIME準備數據。
4-2、編碼規則
將需要轉碼的字符轉爲16進制,然後從右到左,取4位(不足4位直接處理),每2位做一位,前面加上%,編碼成%XY格式。
舉例1:
空格ASCII碼是32,對應16進制是20,那麼urlencode編碼結果是:%20,但在最新標準(RFC-1738)中空格對應的是+。
舉例2:
“中”的GB2312碼是0xD6D0,那麼urlencode編碼結果是:%D6%D0