提及編碼,咱們必須從最基礎的提及,位和字節(別以爲這個過於簡單不值一說,我還真見過不少個不能區分這二者的程序員)。位(bit)是指計算機裏存放的二進制值(0/1),而 8 個位組合成的「位串」稱爲一個字節,容易算出, 8 個位的組合有 256(28)個組合方式,其取值範圍是 00000000-11111111
,經常使用十六進制來表示。好比 01000001
就是一個字節,其對應的十六進制值爲 0×41
。前端
而咱們一般所講的字符編碼,就是指定義一套規則,將真實世界裏的字母/字符與計算機的二進制序列進行相互轉化。如咱們能夠針對上面的字節定義以下的轉換規則:mysql
01000001(0x41)<-> 65 <-> 'A'
即用字位序 01000001
來表示字母 A
。程序員
拉丁字符是當今世界使用最普遍的符號了。一般咱們說的拉丁字母,指的的是基礎拉丁字母,即指常見的 ABCD
等 26 個英文字母,這些字母與英語中一些常見的符號(如數字、標點符號)稱爲基礎拉丁字符,這些基礎拉丁字符在使用英語的國家廣爲流行,固然在中國,也被用來看成漢語拼音使用。在歐洲其它一些非英語國家,爲知足其語言須要,在基礎拉丁字符的基礎上,加上一些連字符,變音字符(如 Á
),造成了派生拉丁字母,其表示的字符範圍在各類語言有所不一樣,而完整意義上的拉丁字符是指這些變體字符與基礎拉丁字符的全集。是比基礎拉丁字符集大不少的一個集合。算法
前文提到,字符編碼是一套規則。既然是規則,就必須有標準。下面我就仔細說說常見的字符編碼標準。sql
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
以上咱們接觸到的拉丁編碼,都是單字節編碼,即用一個字節來對應一個字符。但這一規則對於其它字符集更大的語言來講,並不適應,好比中文,而是出現了用多個字節表示一個字符的編碼規則。常見的中文 GB2312(國家簡體中文字符集)就是用兩個字節來表示一個漢字(注意是表示一個漢字,對於拉丁字母,GB2312 仍是是用一個字節來表示以兼容 ASCII)。咱們用下表來講明各中文編碼之間的規則和兼容性。windows
對於中文編碼,其規則實現上是很簡單的,通常都是簡單的字符查表便可,重要的是要注意其相互之間的兼容性問題。若是選擇 BIG5 字符集編碼,就不能很好的兼容 GB2312,當作繁轉簡時有可能致使個別字的衝突與不一致,可是 GBK 與 GB2312 之間就不存在這樣的問題。微信
以上能夠看到,針對不一樣的語言採用不一樣的編碼,有可能致使衝突與不兼容性,若是咱們打開一份字節序文件,若是不知道其編碼規則,就沒法正確解析其語義,這也是產生亂碼的根本緣由。有沒有一種規則是全世界字符統一的呢?固然有,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 的基本規則,其實簡單來講就兩條(來自阮一峯老師的總結):
經過,根據以上規則,能夠創建一個 Unicode 取值範圍與 UTF-8 字節序表示的對應關係,以下表
舉例來講,微
的 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 數據庫中多個字符集變量(其它數據庫其實也相似),它們之間分別是什麼關係?
咱們分別解釋:
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) 函數(其它數據庫也有相似的函數,一些文本文件編輯器也具備這個功能),查看實際存儲的字節內容,如:
經過查看存儲的字節序,咱們能夠從根本上了解存儲的內容是什麼編碼了。而當發現存儲的內容所有是
3F
時,就代表存儲的內容因爲編碼問題,信息已經丟失了,沒法再找回。之因此出現這種信息丟失的狀況,通常是將不能相互轉換的字符集之間作了轉換,好比咱們在前文說到,UTF-8 只能一個個字節地變成 Latin-1,可是根本不能轉換的,由於二者之間沒有轉換規則,Unicode 的字符對應範圍也根本不在 Latin-1 範圍內,因此只能用
?(0x3F)
代替了。
本文從基礎知識與實際中碰到的問題上,解析了字符編碼相關內容。而之因此要從頭介紹字符編碼的基礎知識,是爲了更好的從原理上了解與解決平常碰到的編碼問題,只有從根本上了解了不一樣字符集的規則及其之間的關係與兼容性,才能更好的解決碰到的亂碼問題,也能避免因爲程序中不正確的編碼轉換致使的信息丟失問題。