文字編碼總結

〇、引(“聯通事件”)

做一個試驗。

新建一個文本文件,然後用記事本打開,輸入“聯通”,保存,關閉。

再次用記事本打開這個文本文件,你看到了什麼?

這被人戲稱是聯通幹不過移動的根本原因——連自己的名字都存不下來。

下面對字符的編碼進行一下總結,會在其中說明聯通消失的原因。

一、字符編碼的發展

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個字節表示一個漢字。

編碼轉換方法舉例:

  1. 以“中”字爲例。採用的編碼標準爲GB2312(中國最新通行的一套兼容編碼標準爲GBK,大字符集)
  2. “中”的區位碼爲5448(所謂區位碼,就是將所有漢字都用4位數字來表示,主要應用於發電報,以及DOS時代的中文輸入)
  3. “中”的國標碼爲爲5448的十六進制+2020H=5650H
  4. “中”的機內碼(就是我們最終要的這個編碼)=國標碼+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編碼規則
utf-8 使用 1~4 個不等的字節來存儲字符編碼。

以“鄭”字爲例,說明從unicode到utf-8的轉換。

  1. “鄭”字的unicode碼是 \u90d1 (可以通過這樣的網站查詢:站長工具 。或者查詢unicode官網:unicode碼錶
  2. 從上表中可以看到,90d1位於第三行(0000 0800 - 0000 FFFF),所以是使用三個字節存儲的。其格式爲 1110yyyy 10yyyyxx 10xxxxxx
  3. 從“鄭”的最後一個二進制位開始,依次從後向前填入格式中的x,多出的位補0。
  4. 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 編碼

在第一章提到的第三個階段(國際化)的初期,其實有兩套國際化編碼。

  1. 國際標準化組織(ISO),1984年制定一份“通用字符集”(Universal Character Set,簡稱UCS),並最終制定了ISO 10646標準
  2. 統一碼聯盟,由Xerox、Apple等軟件製造商於1988年組成,並且開發了Unicode標準
  3. 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 介紹”一節

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值,最後附加一個等號。

Base64編碼舉例

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章