字符編碼常識及問題解析

基本常識

1. 位和字節

提及編碼,咱們必須從最基礎的提及,位和字節(別以爲這個過於簡單不值一說,我還真見過不少個不能區分這二者的程序員)。位(bit)是指計算機裏存放的二進制值(0/1),而 8 個位組合成的「位串」稱爲一個字節,容易算出, 8 個位的組合有 256(28)個組合方式,其取值範圍是 00000000-11111111,經常使用十六進制來表示。好比 01000001 就是一個字節,其對應的十六進制值爲 0×41前端

而咱們一般所講的字符編碼,就是指定義一套規則,將真實世界裏的字母/字符與計算機的二進制序列進行相互轉化。如咱們能夠針對上面的字節定義以下的轉換規則:mysql

01000001(0x41)<-> 65 <-> 'A'

即用字位序 01000001 來表示字母 A程序員

2. 拉丁字符

拉丁字符是當今世界使用最普遍的符號了。一般咱們說的拉丁字母,指的的是基礎拉丁字母,即指常見的 ABCD 等 26 個英文字母,這些字母與英語中一些常見的符號(如數字、標點符號)稱爲基礎拉丁字符,這些基礎拉丁字符在使用英語的國家廣爲流行,固然在中國,也被用來看成漢語拼音使用。在歐洲其它一些非英語國家,爲知足其語言須要,在基礎拉丁字符的基礎上,加上一些連字符,變音字符(如 Á),造成了派生拉丁字母,其表示的字符範圍在各類語言有所不一樣,而完整意義上的拉丁字符是指這些變體字符與基礎拉丁字符的全集。是比基礎拉丁字符集大不少的一個集合。算法

編碼標準

前文提到,字符編碼是一套規則。既然是規則,就必須有標準。下面我就仔細說說常見的字符編碼標準。sql

1. 拉丁編碼

ASCII 的全稱是 American Standard Code for Information Interchange(美國信息交換標準代碼)。顧名思義,這是現代計算機的發明國美國人設計的標準,而美國是一個英語國家,他們設定的 ASCII 編碼也只支持基礎拉丁字符。ASCII 的設計也很簡單,用一個字節(8 個位)來表示一個字符,並保證最高位的取值永遠爲 0。即表示字符含義的位數爲 7 位,不難算出其可表達字符數爲 27 = 128 個。這 128 個字符包括 95 個可打印的字符(涵蓋了 26 個英文字母的大小寫以及英文標點符號能)與 33 個控制字符(不可打印字符)。例以下表,就是幾個簡單的規則對應:數據庫

字符類型 字符 二進制 16 進制 10 進制
可打印字符 A 01000001 0×41 65
可打印字符 a 01100001 0×61 97
控制字符 \r 00001101 0x0D 13
控制字符 \n 00001010 0xA 10

前面說到了,ASCII 是美國人設計的,只能支持基礎拉丁字符。而當計算機發展到歐洲,歐洲其它不僅是用的基礎拉丁字符的國家(即用更大的派生拉丁字符集)該怎麼辦呢?編程

固然,最簡單的辦法就是將美國人沒有用到的第 8 位也用上就行了,這樣能表達的字符個數就達到了 28 = 256 個,相比較原來,增加了一倍, 這個編碼規則也常被稱爲 EASCII。EASCII 基本解決了整個西歐的字符編碼問題。可是對於歐洲其它地方如北歐,東歐地區,256 個字符仍是不夠用,如是出現了ISO 8859,爲解決 256 個字符不夠用的問題,ISO 8859 採起的再也不是單個獨立的編碼規則,而是由一系列的字符集(共 15 個)所組成,分別稱爲 ISO 8859-n (n=1,2,3…11,13…16, 沒有12)。其每一個字符集對應不一樣的語言,如 ISO 8859-1 對應西歐語言,ISO 8859-2 對應中歐語言等。其中你們所熟悉的 Latin-1 就是 ISO 8859-1 的別名,它表示整個西歐的字符集範圍。 須要注意的一點的是,ISO 8859-n 與 ASCII 是兼容的,即其 0000000(0×00)-01111111(0x7f) 範圍段與 ASCII 保持一致,而 10000000(0×80)-11111111(0xFF) 範圍段被擴展用到不一樣的字符集。segmentfault

2. 中文編碼

以上咱們接觸到的拉丁編碼,都是單字節編碼,即用一個字節來對應一個字符。但這一規則對於其它字符集更大的語言來講,並不適應,好比中文,而是出現了用多個字節表示一個字符的編碼規則。常見的中文 GB2312(國家簡體中文字符集)就是用兩個字節來表示一個漢字(注意是表示一個漢字,對於拉丁字母,GB2312 仍是是用一個字節來表示以兼容 ASCII)。咱們用下表來講明各中文編碼之間的規則和兼容性。windows

中文編碼

對於中文編碼,其規則實現上是很簡單的,通常都是簡單的字符查表便可,重要的是要注意其相互之間的兼容性問題。若是選擇 BIG5 字符集編碼,就不能很好的兼容 GB2312,當作繁轉簡時有可能致使個別字的衝突與不一致,可是 GBK 與 GB2312 之間就不存在這樣的問題。微信

3. Unicode

以上能夠看到,針對不一樣的語言採用不一樣的編碼,有可能致使衝突與不兼容性,若是咱們打開一份字節序文件,若是不知道其編碼規則,就沒法正確解析其語義,這也是產生亂碼的根本緣由。有沒有一種規則是全世界字符統一的呢?固然有,Unicode 就是一種。爲了能獨立表示世界上全部的字符,Unicode 採用 4 個字節表示一個字符,這樣理論上 Unicode 能表示的字符數就達到了 231 = 2147483648 = 21 億左右個字符,徹底能夠涵蓋世界上一切語言所用的符號。咱們以漢字「微信」兩字舉例說明:

微 <-> \u5fae <-> 00000000 00000000 01011111 10101110 
信 <-> \u4fe1 <-> 00000000 00000000 01001111 11100001

容易從上面的例子裏看出,Unicode 對全部的字符編碼均須要四個字節,而這對於拉丁字母或漢字來講是浪費的,其前面三個或兩個字節均是 0,這對信息存儲來講是極大的浪費。另一個問題就是,如何區分 Unicode 與其它編碼這也是一個問題,好比計算機怎麼知道四個字節表示一個Unicode 中的字符,仍是分別表示四個 ASCII 的字符呢?

以上兩個問題,困擾着 Unicode,讓 Unicode 的推廣上一直面臨着困難。直至 UTF-8 做爲 Unicode 的一種實現後,部分問題獲得解決,才得以完成推廣使用。說到此,咱們能夠回答文章一開始提出的問題了,UTF-8 是 Unicode 的一種實現方式,而 Unicode 是一個統一標準規範,Unicode的實現方式除了 UTF-8 還有其它的,好比 UTF-16 等

話說當初大牛 Ben Thomson 吃飯時,在一張餐巾紙上,設計出了 UTF-8,而後回到房間,實現了初版的 UTF-8。關於 UTF-8 的基本規則,其實簡單來講就兩條(來自阮一峯老師的總結):

  • 規則 1:對於單字節字符,字節的第一位爲 0,後 7 位爲這個符號的 Unicode 碼,因此對於拉丁字母,UTF-8 與 ASCII 碼是一致的。
  • 規則 2:對於 n 字節(n > 1)的字符,第一個字節前 n 位都設爲 1,第 n+1 位爲 0,後面字節的前兩位一概設爲 10,剩下沒有說起的位,所有爲這個符號的 Unicode 編碼。

經過,根據以上規則,能夠創建一個 Unicode 取值範圍與 UTF-8 字節序表示的對應關係,以下表

p

舉例來講, 的 Unicode 是 \u5fae,二進制表示是 00000000 00000000 01011111 10101110,其取值就位於 0000 0800-0000 FFFF 之間,因此其 UTF-8 編碼爲 11100101 10111110 10101110(加粗部分爲固定編碼內容)。

經過以上簡單規則,UTF-8 採起變字節的方式,解決了咱們前文提到的關於 Unicode 的兩大問題。同時,做爲中文使用者須要注意的一點是 Unicode(UTF-8) 與 GBK,GB2312 這些漢字編碼規則是徹底不兼容的,也就是說這二者之間不能經過任何算法來進行轉換,如需轉換,通常經過 GBK 查表的方式來進行。

常見問題及解答

windows Notepad 中的編碼 ANSI 保存選項,表明什麼含義?

ANSI 是 windows 的默認的編碼方式,對於英文文件是 ASCII 編碼,對於簡體中文文件是 GB2312 編碼(只針對 Windows 簡體中文版,若是是繁體中文版會採用 Big5 碼)。因此,若是將一個 UTF-8 編碼的文件,另存爲 ANSI 的方式,對於中文部分會產生亂碼。

什麼是 UTF-8 的 BOM?

BOM 的全稱是 Byte Order Mark,BOM 是微軟給 UTF-8 編碼加上的,用於標識文件使用的是 UTF-8 編碼,即在 UTF-8 編碼的文件起始位置,加入三個字節 EE BB BF。這是微軟特有的,標準並不推薦包含 BOM 的方式。採用加 BOM 的 UTF-8 編碼文件,對於一些只支持標準 UTF-8 編碼的環境,可能致使問題。好比,在 Go 語言編程中,對於包含 BOM 的代碼文件,會致使編譯出錯。詳細可見個人這篇文章。

爲何數據庫 Latin1 字符集(單字節)能夠存儲中文呢?

其實無論須要使用幾個字節來表示一個字符,但最小的存儲單位都是字節,因此,只要能保證傳輸和存儲的字節順序不會亂便可。做爲數據庫,只是做爲存儲的使用的話,只要能保證存儲的順序與寫入的順序一致,而後再按相同的字節順序讀出便可,翻譯成語義字符的任務交給應用程序。好比 的UTF-8編碼是 0xE5 0xBE 0xAE,那數據庫也存儲 0xE5 0xBE 0xAE 三個字節,其它應用按順序從數據庫讀取,再按 UTF-8 編碼進行展示。這固然是一個看似完美的方案,可是隻要寫入,存儲,讀取過程當中岔出任何別的編碼,均可能致使亂碼。

MySQL 數據庫中多個字符集變量(其它數據庫其實也相似),它們之間分別是什麼關係?

p2

咱們分別解釋:

  • character_set_client:客戶端來源的數據使用的字符集,用於客戶端顯式告訴客戶端所發送的語句中的的字符編碼。
  • character_set_connection:鏈接層的字符編碼,MySQL 通常用 character_set_connection 將客戶端的字符轉換爲鏈接層表示的字符。
  • character_set_results:查詢結果從數據庫讀出後,將轉換爲 character_set_results 返回給前端。

而咱們常見的解決亂碼問題的操做:

  • mysql_query('SET NAMES GBK')
    其至關於將以上三個字符集統一所有設置爲 GBK,這三者一致時,通常就解決了亂碼問題。
  • character_set_database:當前選中數據庫的默認字符集,如當create table時沒有指定字符集,將默認選擇該字符集。
  • character_set_database 已經 character_set_system,通常用於數據庫系統內部的一些字符編碼,處理數據亂碼問題時,咱們基本能夠忽略。

什麼狀況下,表示信息丟失?

對於 MySQL 數據庫,咱們能夠經過 hex (colname) 函數(其它數據庫也有相似的函數,一些文本文件編輯器也具備這個功能),查看實際存儲的字節內容,如:

p3

經過查看存儲的字節序,咱們能夠從根本上了解存儲的內容是什麼編碼了。而當發現存儲的內容所有是 3F 時,就代表存儲的內容因爲編碼問題,信息已經丟失了,沒法再找回。

之因此出現這種信息丟失的狀況,通常是將不能相互轉換的字符集之間作了轉換,好比咱們在前文說到,UTF-8 只能一個個字節地變成 Latin-1,可是根本不能轉換的,由於二者之間沒有轉換規則,Unicode 的字符對應範圍也根本不在 Latin-1 範圍內,因此只能用 ?(0x3F) 代替了。

總結

本文從基礎知識與實際中碰到的問題上,解析了字符編碼相關內容。而之因此要從頭介紹字符編碼的基礎知識,是爲了更好的從原理上了解與解決平常碰到的編碼問題,只有從根本上了解了不一樣字符集的規則及其之間的關係與兼容性,才能更好的解決碰到的亂碼問題,也能避免因爲程序中不正確的編碼轉換致使的信息丟失問題。

原文出處:Justin Huang 的博客(@Justin_Programer)

相關文章
相關標籤/搜索