從本質上搞懂頭痛的亂碼問題!

2021-03-06 CodeSheep

來源丨cenalulu

cnblogs.com/cenalulu/p/4251639.html

煙囪巖和安肯帕格裡國家森林(© Cory Marshall)

字符集 和 編碼無疑是IT菜鳥甚至是各種大神的頭痛問題。當遇到紛繁複雜的字符集,各種火星文和亂碼時,問題的定位往往變得非常困難。本文將會從原理方面對字符集和編碼做個簡單的科普介紹,同時也會介紹一些通用的亂碼故障定位方法以方便讀者以後能夠更從容的定位相關問題。

在介紹字符集之前,先了解下為什麼要有字符集。我們在計算機屏幕上看到的是實體化的文字,而在計算機存儲介質中存放的實際是二進位的比特流。那麼在這兩者之間的轉換規則就需要一個統一的標準,否則把我們的U盤查到老闆的電腦上文檔就亂碼了,小夥伴QQ上傳過來的文件在我們本地打開又亂碼了。(PS:這裡科普下 亂碼的英文native說法是 mojibake)。於是為了實現轉換標準,各種字符集標準就出現了。簡單的說字符集就規定了某個文字對應的二進位數字存放方式(編碼)和某串二進位數值代表了哪個文字(解碼)的轉換關係。

那麼為什麼會有那麼多字符集標準呢?這個問題實際非常容易回答。問問自己為什麼我們的插頭拿到英國就不能用了呢?為什麼顯示器同時有DVI,VGA,HDMI,DP這麼多接口呢?很多規範和標準在最初制定時並不會意識到這將會是以後全球普適的準則,或者處於組織本身利益就想從本質上區別於現有標準。於是,就產生了那麼多具有相同效果但又不相互兼容的標準了。

說了那麼多我們來看一個實際例子,下面就是 「強」這個字在各種編碼下的十六進位和二進位編碼結果

字符集只是一個規則集合的名字,對應到真實生活中,字符集就是對某種語言的稱呼。例如:英語,漢語,日語。而如何用英語來表達 你狠強的意思就是英語詞法語法所需要具體描述的內容了。而對於一個字符集來說要正確編碼轉碼一個字符需要三個關鍵元素:字庫表(character repertoire)、編碼字符集(coded character set)、字符編碼(character encoding form)。其中字庫表是一個相當於所有可讀或者可顯示字符的資料庫,字庫表決定了整個字符集能夠展現表示的所有字符的範圍。編碼字符集,即用一個編碼值 code point來表示一個字符在字庫中的位置。字符編碼,將編碼字符集和實際存儲數值之間的轉換關係。一般來說都會直接將 code point的值作為編碼後的值直接存儲。例如在ASCII中 A在表中排第65位,而編碼後 A的數值是 01000001也即十進位的65的二進位轉換結果。

看到這裡,可能很多讀者都會有和我當初一樣的疑問:字庫表和 編碼字符集看來是必不可少的,那既然字庫表中的每一個字符都有一個自己的序號,直接把序號作為存儲內容就好了。為什麼還要多此一舉通過 字符編碼把序號轉換成另外一種存儲格式呢?其實原因也比較容易理解:統一字庫表的目的是為了能夠涵蓋世界上所有的字符,但實際使用過程中會發現真正用的上的字符相對整個字庫表來說比例非常低。例如中文地區的程序幾乎不會需要日語字符,而一些英語國家甚至簡單的ASCII字庫表就能滿足基本需求。而如果把每個字符都用字庫表中的序號來存儲的話,每個字符就需要3個字節(這裡以Unicode字庫為例),這樣對於原本用僅佔一個字符的ASCII編碼的英語地區國家顯然是一個額外成本(存儲體積是原來的三倍)。算的直接一些,同樣一塊硬碟,用ASCII可以存1500篇文章,而用3位元組Unicode序號存儲只能存500篇。於是就出現了UTF-8這樣的變長編碼。在UTF-8編碼中原本只需要一個字節的ASCII字符,仍然只佔一個字節。而像中文及日語這樣的複雜字符就需要2個到3個字節來存儲。

看完上面兩個概念解釋,那麼解釋UTF-8和Unicode的關係就比較簡單了。Unicode就是上文中提到的編碼字符集,而UTF-8就是字符編碼,即Unicode規則字庫的一種實現形式。隨著網際網路的發展,對同一字庫集的要求越來越迫切,Unicode標準也就自然而然的出現。它幾乎涵蓋了各個國家語言可能出現的符號和文字,並將為他們編號。詳見:http://en.wikipedia.org/wiki/Unicode。Unicode的編號從 0000開始一直到 10FFFF共分為16個Plane,每個Plane中有65536個字符。而UTF-8則只實現了第一個Plane,可見UTF-8雖然是一個當今接受度最廣的字符集編碼,但是它並沒有涵蓋整個Unicode的字庫,這也造成了它在某些場景下對於特殊字符的處理困難(下文會有提到)。

為了更好的理解後面的實際應用,我們這裡簡單的介紹下UTF-8的編碼實現方法。即UTF-8的物理存儲和Unicode序號的轉換關係。

UTF-8編碼為變長編碼。最小編碼單位( code unit)為一個字節。一個字節的前1-3個bit為描述性部分,後面為實際序號部分。

如果一個字節的第一位為0,那麼代表當前字符為單字節字符,佔用一個字節的空間。0之後的所有部分(7個bit)代表在Unicode中的序號。

如果一個字節以110開頭,那麼代表當前字符為雙字節字符,佔用2個字節的空間。110之後的所有部分(7個bit)代表在Unicode中的序號。且第二個字節以10開頭

如果一個字節以1110開頭,那麼代表當前字符為三字節字符,佔用3個字節的空間。110之後的所有部分(7個bit)代表在Unicode中的序號。且第二、第三個字節以10開頭

如果一個字節以10開頭,那麼代表當前字節為多字節字符的第二個字節。10之後的所有部分(6個bit)代表在Unicode中的序號。

具體每個字節的特徵可見下表,其中 x代表序號部分,把各個字節中的所有 x部分拼接在一起就組成了在Unicode字庫中的序號

我們分別看三個從一個字節到三個字節的UTF-8編碼例子:

實際字符在Unicode字庫序號的十六進位在Unicode字庫序號的二進位UTF-8編碼後的二進位UTF-8編碼後的十六進位$0024010 01000010 010024¢00A2000 1010 00101100 0010 1010 0010C2 A2€20AC0010 0000 1010 11001110 0010 1000 0010 1010 1100E2 82 AC

不難從以上的簡單介紹中得出以下規律:

3個字節的UTF-8十六進位編碼一定是以 E開頭的

2個字節的UTF-8十六進位編碼一定是以 C或 D開頭的

1個字節的UTF-8十六進位編碼一定是以比 8小的數字開頭的

簡單的說亂碼的出現是因為:編碼和解碼時用了不同或者不兼容的字符集。對應到真實生活中,就好比是一個英國人為了表示祝福在紙上寫了bless(編碼過程)。而一個法國人拿到了這張紙,由於在法語中bless表示受傷的意思,所以認為他想表達的是受傷(解碼過程)。這個就是一個現實生活中的亂碼情況。在計算機科學中一樣,一個用UTF-8編碼後的字符,用GBK去解碼。由於兩個字符集的字庫表不一樣,同一個漢字在兩個字符表的位置也不同,最終就會出現亂碼。

來看一個例子:假設我們用UTF-8編碼存儲 很強兩個字,會有如下轉換:

字符UTF-8編碼後的十六進位很E5BE88強E5B18C

於是我們得到了 E5BE88E5B18C這麼一串數值。而顯示時我們用GBK解碼進行展示,通過查表我們獲得以下信息:

兩個字節的十六進位數值GBK解碼後對應的字符E5BE寰88E5堝B18C睂

解碼後我們就得到了 寰堝睂這麼一個錯誤的結果,更要命的是連字符個數都變了。

要從亂碼字符中反解出原來的正確文字需要對各個字符集編碼規則有較為深刻的掌握。但是原理很簡單,這裡用最常見的UTF-8被錯誤用GBK展示時的亂碼為例,來說明具體反解和識別過程。

第1步 編碼

假設我們在頁面上看到 寰堝睂這樣的亂碼,而又得知我們的瀏覽器當前使用GBK編碼。那麼第一步我們就能先通過GBK把亂碼編碼成二進位表達式。當然查表編碼效率很低,我們也可以用以下SQL語句直接通過MySQL客戶端來做編碼工作:

{% highlight mysql %}

{% raw %}

mysql [localhost] {msandbox} > select hex(convert('寰堝睂' using gbk));

+--+

| hex(convert('寰堝睂' using gbk)) |

+--+

| E5BE88E5B18C |

+--+

1 row in set (0.01 sec)

{% endraw %}

{% endhighlight %}

第2步 識別

現在我們得到了解碼後的二進位字符串 E5BE88E5B18C。然後我們將它按字節拆開。

然後套用之前UTF-8編碼介紹章節中總結出的規律,就不難發現這6個字節的數據符合UTF-8編碼規則。如果整個數據流都符合這個規則的話,我們就能大膽假設亂碼之前的編碼字符集是UTF-8

第3步 解碼

然後我們就能拿著 E5BE88E5B18C用UTF-8解碼,查看亂碼前的文字了。當然我們可以不查表直接通過SQL獲得結果:

{% highlight mysql %}

{% raw %}

mysql [localhost] {msandbox} ((none)) > select convert(0xE5BE88E5B18C using utf8);

+-+

| convert(0xE5BE88E5B18C using utf8) |

+-+

| 很強 |

+-+

1 row in set (0.00 sec)

{% endraw %}

{% endhighlight %}

所謂Emoji就是一種在Unicode位於 \u1F601- \u1F64F區段的字符。這個顯然超過了目前常用的UTF-8字符集的編碼範圍 \u0000- \uFFFF。Emoji表情隨著IOS的普及和微信的支持越來越常見。下面就是幾個常見的Emoji:

那麼Emoji字符表情會對我們平時的開發運維帶來什麼影響呢?最常見的問題就在於將他存入MySQL資料庫的時候。一般來說MySQL資料庫的默認字符集都會配置成UTF-8(三字節),而utf8mb4在5.5以後才被支持,也很少會有DBA主動將系統默認字符集改成utf8mb4。那麼問題就來了,當我們把一個需要4位元組UTF-8編碼才能表示的字符存入資料庫的時候就會報錯:ERROR 1366:Incorrectstringvalue:'\xF0\x9D\x8C\x86'forcolumn 。如果認真閱讀了上面的解釋,那麼這個報錯也就不難看懂了。我們試圖將一串Bytes插入到一列中,而這串Bytes的第一個字節是 \xF0意味著這是一個四字節的UTF-8編碼。但是當MySQL表和列字符集配置為UTF-8的時候是無法存儲這樣的字符的,所以報了錯。

那麼遇到這種情況我們如何解決呢?有兩種方式

若有錯誤或者不當之處,可在本公眾號內反饋,一起學習交流!

更多熱文在此:

更多 務實、能看懂、可復現的 技術文章、資源盡在公眾號 CodeSheep,歡迎掃碼訂閱,第一時間獲取更新 ⬇️⬇️⬇️

相關焦點

  • 淺入深出:一次提問引發的深思,從此再也不怕「亂碼」問題
    之前關於爬蟲亂碼有很多粉絲的各式各樣的問題,今天與大家一起總結下關於網絡爬蟲的亂碼處理。注意,這裡不僅是中文亂碼,還包括一些如日文、韓文 、俄文、藏文之類的亂碼處理,因為他們的解決方式 是一致的,故在此統一說明。一、亂碼問題的出現就以爬取51job網站舉例,講講為何會出現「亂碼」問題,如何解決它以及其背後的機制。
  • Content-Disposition 亂碼問題記錄
    最近家裡領導安排了一項任務,要從空中課堂網站上批量下載一些教學需要用的文件,做為一名比較懶惰的碼農,當然想使用些省力的方法,既能滿足領導的要求,又不用自己太費時費事,因此便想用 Python Requests 來實現。
  • iPhone郵件亂碼怎麼辦_iPhone郵件亂碼解決教程
    iPhone郵件亂碼怎麼辦_iPhone郵件亂碼解決教程 小編最近看到一些網友說蘋果產品自帶Mail發郵件對方收到是亂碼,這裡就發個自己在國外網站找到的方法吧。
  • 總結,四個常見html網頁亂碼問題及解決辦法
    1、HTML的字符編碼問題在網頁頭部中加上下面內容:<meta http-equiv="Content-Type" Content="text/html;charset=utf8"/>2、PHP的字符編碼問題在php文件頂部加上下面代碼:header(「Content-type:text/html;charset=utf8」);
  • 《王國風雲3》字體亂碼怎麼辦 字體亂碼解決辦法
    遊戲中不少玩家遇到了字體亂碼的情況不知道怎麼處理,這裡將為大家分享的是十字軍之王3字體亂碼解決方法介紹,供大家參考,希望對大家有所幫助。 十字軍之王3字體亂碼解決方法介... 王國風雲3字體亂碼怎麼辦?
  • 日文遊戲亂碼怎麼辦 亂碼轉換工具下載及使用
    ,那麼遇到這種日文遊戲亂碼情況該怎麼辦呢,這裡我們需要一個亂碼轉換工具,此類軟體有不少,比如applocale亂碼轉換工具,NTLEA等。這裡巴士小編給大家推薦一款名為Locale Emulator的亂碼轉換工具,並附上下載和使用說明。
  • 魔獸世界:六串神秘「字符」,玩了多年魔獸,還是沒搞懂啥意思
    第一串神秘字符:刀柄上的「AL」對於兄弟會之劍,許多玩家都知道,這把武器還有另外一個名字AL大劍!至於為什麼這把劍名字叫做AL大劍,是因為這把武器的刀柄上刻著「AL」這樣神秘的字符!隨後又有許多玩家開始尋找,暴雪為什麼要在這把武器上印刻「AL」這樣的字符,最後依舊是眾說紛紜,這串神秘的字符,就成為了這把武器最形象的代表!
  • 關於上期推送的亂碼文章
    上次推送中出現了一篇亂碼文章,其實那不是亂碼,而是摩斯電碼,高中生現有的知識存量應該很容易就想得到,後臺有評論說將摩斯電碼轉化之後是一串毫無規律的數字
  • CAD亂碼原因及解決方法,字體大全下載!
    所謂亂碼是指CAD中的文本顯示為雜亂符號的非正常狀態。三、亂碼原因電腦中沒有文件原作者所用的字體文件。選定亂碼文本→右鍵→「特性」→選擇「樣式1」並調整文字大小,大功告成!方法2複製原作者的字體文件到自己電腦中對應文件夾中,所有亂碼都可以正常顯示了!
  • 如何解決Matlab的中文亂碼?
    在使用Matlab時,雖然不能用中文來進行編程,但卻可以使用中文進行注釋,儘管不推薦這樣做(某些場合英文更易於傳播),但如果以前編寫的或別人編寫的代碼注釋中包含很多中文,而自己的電腦又恰恰是英文的Windows系統,那麼將很可能會出現如下亂碼的情況,這會給對代碼的理解帶來很多麻煩甚至困難。
  • 【亂碼變型記】這個男人憑什麼讓女人看硬?
    世界上那麼多專業複雜的時裝公號,它們真的能學到很多東西麼,也許你連讀完那篇文章都沒能夠就徹底歇菜了吧,而亂碼的目的只有一個:短平快地幫你找到適合自己的穿衣風格。(再次對比一下鞋的白邊兒)姐姐帶著剛上大學的弟弟去交學費路過亂碼,順便進來參觀參觀  當然社長也不是一直都這麼生無可戀,下面提供一張珍貴的歷史照片。
  • 【亂碼賤聞】普天同慶,丘咲愛米莉下馬了!
    周五了,相信這個點大家已經開始在外面到處浪了,然而有的人浪著浪著就上了【亂碼賤聞】……俗話說得好,常在河邊走哪有不溼鞋,今天的頭條,就讓我們來看看這些人是怎麼個浪法
  • U盤盤符及文件無故出現文件亂碼,找回文件並修復U盤.
    ,需要找回文件,是廣場舞曲,MP3格式的,裡面有25首歌曲,其中10首歌的圖標變成資料夾圖標,而且是亂碼,並且打不開,說明已損壞,歌曲是編輯好的,再搞很麻煩。我們下面講下找回的具體方法,再修復U盤亂碼的問題。」關注一下小編沒有壞處,可以給你免費的解決問題」+V遠程免費幫你解決
  • 亂碼放映室 | 這些扎心燒腦、道德淪喪的反烏託邦電影
    在豆瓣上看到,一位網友的精闢點評,emmmm,你們都懂。一句話概括本片那就是現代版《1984》,除了反烏託邦這一亮點之外,另一個亮點就是槍鬥術了,武術配上槍,讓教士們在槍戰中都能立於不敗之地。德國,2008年
  • 請不要把閩南語寫成亂碼
    只見那招牌上寫著:「泉州,阮哎裡!」「你麼起得T淘」「餅U,利底兜」「唔洗甘來阮厝泡Dei」「滴桃,來酒兄」「嘍吼,記勒Dua吼算」。  有人做了「閩南語正字」解:「泉州,阮愛汝」「汝卜去佗■迌」「朋友,汝佇佗」「有時間來阮厝泡茶」「豬頭,來照相」「落雨,記得帶雨傘」。其中第二句,解得更加莫名其妙。
  • 亂碼的讀者們太好看了!
    當一個你不討厭的人問這個問題時,你發哪張?如果朋友圈裡只能曬一張照片,你曬哪張?如果社交平臺需要一個吸引人的頭像?你用哪張?感謝各位投稿的碼友願意和大家分享這張照片,收到的每張照片和每段文字我們都認真看過了,但篇幅有限恕無法一一刊登。再次感嘆,亂碼讀者真是臥虎藏龍,這篇推文讓我找到了做公眾號的意義,也讓我十分愉悅,希望也能帶給你相同的感受。
  • 【亂碼直播】原來蔡依林的男友這麼長
    但是有個問題困擾了我很多年,錦榮身高188,官方顯示156的Jolin真的會和他差這麼多??(圖為狗仔偷拍)經紀人說他最近太累應該是睡著了。於是我選擇了默默離開。 準備就緒後,進入正式拍攝環節。(天后身高之謎在此揭開,左一168,右一172)你見過用腹肌頂起一塊盾牌的錦榮嗎……(說,亂碼的logo牌酷不酷)
  • 淺入深出 | 網站爬取時出現亂碼該怎麼辦
    作者:丁彥軍本文轉自公眾號:戀習Python(ID:sldata2017)近日,有位粉絲向我請教,在爬取某網站時,網頁的原始碼出現了中文亂碼問題。之前關於爬蟲亂碼有很多粉絲的各式各樣的問題,今天與大家一起總結下關於網絡爬蟲的亂碼處理。注意,這裡不僅是中文亂碼,還包括一些如日文、韓文 、俄文、藏文之類的亂碼處理,因為他們的解決方式 是一致的,故在此統一說明。一、亂碼問題的出現就以爬取51job網站舉例,講講為何會出現「亂碼」問題,如何解決它以及其背後的機制。
  • 搞懂懷孕中期問題(上) www.013578.com
    搞懂懷孕中期問題(上)  www.013578.com    摸著肚子裡的寶寶,妳是否也想知道他是怎麼長大的呢
  • 【亂碼賤聞】光棍節特輯:AV女優新聞播報!
    亂碼的兄弟姐妹們,光棍節快樂。作為全網第一家連續一周為讀者們送上光棍節特輯的公眾號,本周的【亂碼賤聞】自然也不例外。