計算機(jī)只能識別 0 和 1,任何數(shù)據(jù)格式,包括數(shù)字、文本、圖像、音樂、視頻等等都表示為一串由 0 和1 組成的二進(jìn)制數(shù)據(jù)串。如何將一串二進(jìn)制數(shù)據(jù)識別為有意義的數(shù)據(jù)類型,是軟件層面的問題,是一種格式和約定。
ASCII編碼
現(xiàn)代計算機(jī)的軟硬件設(shè)計最早都是由一群以英語為母語的人設(shè)計的,英語只有52個字母(大小寫區(qū)別對待),再加上常用的一些標(biāo)點符號和控制符號(比如換行、回車等),也就一百來個左右。1個字節(jié)8個比特位,能表示256種狀態(tài),因此純英文環(huán)境下的各種字符用1個字節(jié)就足夠了。于是自然而然的,就誕生了ASCII 這個最早也是使用最廣泛的單字節(jié)編碼。
ASCII 編碼使用1個字節(jié)的低7位,最高位為0,編碼范圍是00000000-01111111,用十進(jìn)制表示的話就是0-127,其編碼分布如下圖所示。

Latin1 編碼
ASCII 碼表只包含了英文字母,顯然是不夠的。首先就是西歐國家,主要是拉丁語言。和英文一樣,也是字母語言,字符不算多,大概幾十個。于是ASCII最高的1位尚未使用的比特位就被利用起來了,編碼范圍是 10000000-11111111,即十進(jìn)制的128-256,包含了拉丁字母和一些特殊符號,如歐元符號等,這就是 Latin1 編碼。可以看出,Latin1 編碼是 ASCII 的超集,一共能夠表示 256個字符。
GB2312、GBK和GB18030編碼
到了亞洲國家,情況就很復(fù)雜了。以中文為例,僅僅是最常使用,滿足日常閱讀書寫的最少漢字,也要三千個左右,顯然用單字節(jié)是無法容納了。很自然地,人們就想,單字節(jié)不夠用,那就雙字節(jié)唄。
首先形成標(biāo)準(zhǔn)的是 GB2312。GB2312 采用的是變長碼,即 ASCII 字符仍然用單字節(jié)表示,中文則用 2 個字節(jié)表示。具體的技術(shù)規(guī)定是:碼值小于127的字符的意義和ASCII碼相同,但兩個大于127的字符連在一起時,就表示一個漢字,前面的一個字節(jié)(他稱之為高字節(jié))從0xA1 到 0xF7,后面一個字節(jié)(低字節(jié))從 0xA1到 0xFE。
但漢字的數(shù)量實際上是遠(yuǎn)大于這個數(shù)目的,再加上還有繁體字,GB2312 很快就無法滿足需要了。于是 GBK 出現(xiàn)了,具體的技術(shù)方案從兩個方面著手,一是把之前 GB2312 還沒用完的碼點繼續(xù)用完,二是放寬了兩個字符的碼值都大于127才是中文的限制,只要第一個字符大于127就表示中文。GBK 是 GB2312 的超集,完全兼容 GB2312,收錄了2萬多個漢字及相關(guān)字符,包括繁體字。
到了 GBK,基本上溝通交流就不存在太大問題了,但仍然無法滿足文化傳播的需要,一些生僻字以及我國少數(shù)名族的字仍然無法表達(dá)。于是又引入了 GB18030 (全稱《信息技術(shù) 中文編碼字符集)。在 GBK 的基礎(chǔ)上進(jìn)一步擴(kuò)大了覆蓋范圍,共收錄七萬多個漢字、少數(shù)民族字以及日語和韓語中的漢字。因為超過了65535 的兩字節(jié)極限,GB18030 采用了變長編碼,即一個字符有可能是1個字節(jié)(ASCII字符),2個字節(jié)(完全兼容GB2312,基本兼容GBK)和4個字節(jié)。
Unicode
為了適應(yīng)本國需要,很多國家和地區(qū)都制定了自己的文字編碼,要識別不同編碼下的文字,就要安裝相應(yīng)的解碼程序,很是不方便。瀏覽網(wǎng)頁時常常出現(xiàn)的亂碼基本上都是編碼問題導(dǎo)致的。
為了解決這一問題,開發(fā)者就提出用一種編碼統(tǒng)一對全世界的各種文字進(jìn)行編碼,給每一個字符一個唯一的碼點(Code point),這就是 Unicode 。目前 Unicode 的編碼空間共包含 0x10FFFF(1114111)個編碼點,被劃分為17個平面,每個平面包含0xFFFF 即 65535 個字符。其中最重要的是基本多文種平面(Basic Multilingual Plane),包含了各語種最常用的字符編碼,碼點范圍為 0x0000-0xFFFF,其它平面稱之為輔助平面。
從1991年發(fā)布的第一個版本開始,每一年都會有新的字符被編入Unicode中,最新的 Unicode 標(biāo)準(zhǔn)是14.0.0,共收錄了 144,697 個字符。需要注意的是,Unicode 是一個符號集,一種規(guī)范,為每一個字符規(guī)定了唯一對應(yīng)的數(shù)字,即碼點。但并不是編碼標(biāo)準(zhǔn),因為不涉及如何存儲這些碼點,即前面我們所說的用幾個字節(jié)存儲一個字符,采用的具體技術(shù)方案等等。具體的編碼標(biāo)準(zhǔn)目前主要有UTF-8、UTF-16和UTF-32。
UTF-32
UTF-32 是一種定長編碼,用4個字節(jié)來統(tǒng)一表示 Unicode 字符。這個方案簡單明了,碼點值是多少,內(nèi)存中就存多少。好處是每個字符的做占用的空間都是相同的,因此隨機(jī)獲取任意位置的字符就非常簡單,直接在首字符的地址加上一個固定的偏移量就可以了,時間復(fù)雜度是O(1)。
UTF-32 的缺點也是顯而易見的,那就是空間浪費。本來英文字母用1個字節(jié)就可以表示,現(xiàn)在同樣需要4個字節(jié)。
UTF-16
UTF-16 是變長編碼,用2個字節(jié)或者4個字節(jié)存儲 Unicode 碼點。
前面說過,Unicode 碼點的范圍是 0x000000~0x10FFFF。對于 0x0000~0xFFFF 即基本多語種平面的字符,UTF-16 用2個字節(jié)來表示,直接存儲碼點值。
超出 0xFFFF 碼點的值,2字節(jié)存不下,采用4字節(jié)存儲。那么4個字節(jié)如何表示一個 Unicode 碼點呢?首先,不能直接存儲碼點值,因為如果直接存儲碼點值,前2個字節(jié)的值就有可能和基本多語言平面內(nèi)的碼點值相同而導(dǎo)致無法區(qū)分。其次,4個字節(jié)理論上能夠存儲42.9億多個字符,但Unicode 規(guī)范只定義了 1114111 個字符,因為 2 的20次方大致是1,048,576,因此差不多只需要20位,即2個半字節(jié)就可以了。
具體采取的辦法就是將超出 0xFFFF 的碼點值分為前后兩個部分,每個部分各10位。其中前面10位是基本多語種平面尚未分配的碼點范圍:即 0xD800~0xDBFF,共可以表示1024個狀態(tài)。后面10位則規(guī)定為 0xDC00~0xDFFF,也可以表示1024個狀態(tài),合起來一共可以表示1024 * 1024 = 1,048,576 個字符,再加上基本多語言平面的字符,差不多等價于Unicode 碼點范圍,稍微少一點,影響不大。
UTF-8
UTF-8 也是一種變長碼。編碼規(guī)則是:如果首字節(jié)的高位為0,即碼點在 0 - 127 之間,就是單字節(jié)編碼(ASCII碼)。如果第1個字節(jié)的高位為1,就是多字節(jié)編碼,至于是2個字節(jié)、3個字節(jié)疑惑是4個字節(jié),取決于第一個字節(jié)高位有連續(xù)幾個1。若屬于多字節(jié)編碼,那么 UTF-8 除首字節(jié)外,其余字節(jié)均以10 開頭。也就是說,UTF-8 理論上可以到8字節(jié),但由于 Unicode 目前只有一百多萬個碼點,因此最多使用4個字節(jié)就足夠了。
- 單字節(jié)編碼,形式是 0xxxxxxx,完全兼容ASCII 編碼,包含127個字符。
- 雙字節(jié)編碼:形式是 110xxxxx 10xxxxxx,能夠容納 2048 個字符,
- 三字節(jié)編碼:1110xxxx 10xxxxxx 10xxxxxx,能夠容納65536個字符。
- 四字節(jié)編碼:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx:能夠容納 2,097,152 個字符,超出了 Unicode 碼點范圍
UTF-8 最大的好處是靈活,如果文本完全是英文,那么只需要單字節(jié)就可以了。漢字絕大部分是三字節(jié)編碼,少部分是四字節(jié)。
大端序和小端序
計算機(jī)以字節(jié)為最小存儲單位,也就是說一個字節(jié)之內(nèi)是不會拆分的。但如果有兩個或以上的字節(jié)(或單元),存放時誰放在前面就有區(qū)別了.比如"馬"字的Unicode碼值是 0x9A6C ,采用2個字節(jié)存儲時,就有兩種不同的考量,即第一個字節(jié) 9A 放在存儲器的前面(低地址處),還是放在后面(存儲器的高地址處)呢?大端序就是前面的字節(jié)放在低地址處,后面的放在高地址處;否則就是小端序。
字節(jié)序 BOM 即 Byte Order Mark。BOM 是Unicode標(biāo)準(zhǔn)中的一個特殊字符 FEFF. 它沒有定義為任何字符,但在Unicode標(biāo)準(zhǔn)中推薦放在文件或數(shù)據(jù)流的開頭, 用來標(biāo)識字節(jié)存貯的順序. 我們都知道它的碼值是 FEFF. 如果存儲為FE FF,則表明這種方式是大端序(BE), 如果存儲為FF FE,則表明為小尾(LE)。
C# 中處理文本編碼
在 C# 中,System.Text.Encoding 為文本編碼提供了基礎(chǔ)支持,可以通過 GetEncoding 方法獲取任意編碼實例。
// 在 .NET Core中,必須休閑調(diào)用 RegisterProvider:
Encoding.RegisterProvider (CodePagesEncodingProvider.Instance);
// 獲取當(dāng)前系統(tǒng)支持的所有編碼
foreach (EncodingInfo info in Encoding.GetEncodings())
{
Console.WriteLine (info.Name)
}
// 獲取 GB18030 編碼
var chineseEncoding = Encoding.GetEncoding ("GB18030");
var utf8Encoding = Encoding.UTF8;
對于 ASCII、Latin1、 UTF-8 、UTF-16 和 UTF-32 等編碼,C# 還提供了專門的子類。
- Encoding.ASCII
- Encoding.Latin1
- Encoding.UTF8
- Encoding.Unicode ,UTF-16編碼,小端序
- Encoding.BigEndianUnicod,UTF-16編碼,大端序
- Encoding.UTF32,UTF-32編碼,小端序
在讀取文件時,可以指定編碼,如果不指定,一般默認(rèn)為 UTF-8。
System.IO.File.WriteAllText ("data.txt", "床前明月光", Encoding.Unicode);