了解字符集曆史,解決亂碼問題

時間:2021-05-11 作者:管理員(yuán) 點擊:537

一(yī)個故事來理解爲什麽要編碼

爲什麽會亂碼

字符集的曆史

ASCII編碼的誕生(shēng)

IOS-8859編碼家族誕生(shēng)

GB2312和GBK等雙字節編碼誕生(shēng)

Unicode字符誕生(shēng)

UTF編碼家族誕生(shēng)

UTF-32編碼

UTF-16編碼

UTF-8編碼

爲什麽有時候亂碼都是?号

拓展知(zhī)識

代碼點和代碼單元

大(dà)端模式和小(xiǎo)端模式

BOM

總結

前言

在日常開(kāi)發中(zhōng),亂碼問題可以說曾經都困擾過我(wǒ)(wǒ)們,那麽爲什麽會有亂碼發生(shēng)呢?爲什麽全世界不統一(yī)使用一(yī)套編碼呢?本文将會從字符集的發展曆史來解答這兩個問題,看完本篇,相信大(dà)家對亂碼現象會有本質上的認識。

一(yī)個故事來理解爲什麽要編碼

現在有兩個人,張三和李四,張三隻會中(zhōng)文,李四隻會英文,那麽這時候他們怎麽溝通?解決辦法是他們可以找個翻譯,這個翻譯的過程就可以理解爲編碼,也就是說從中(zhōng)文到英文或者從英文到中(zhōng)文這就是一(yī)個編碼的過程,編碼的本質就是爲了讓對方能讀懂自己的語言。

人類的各種官方語言和方言數不勝數,所以在應用到在計算機時總不能兩兩互相編碼吧?而且最重要的是人類的語言并不适合計算機使用,所以就需要發明一(yī)種适合計算機的語言,這就是二進制。二進制就是當今世界計算機的語言,當然,曾經前蘇聯也發明過三進制計算機,但是沒有普及,這個感興趣的可以自己去(qù)了解下(xià)。

有了二進制這種計算機能讀懂的語言就好辦了,當我(wǒ)(wǒ)們想和計算機溝通的時候,先轉成二進制(編碼),計算機處理完成之後,再轉換回人類語言(解碼),這就是需要編碼的原因。

爲什麽會亂碼

但是爲什麽會亂碼呢?還是用上面的故事中(zhōng)張三李四來舉例,假如有一(yī)次張三說了一(yī)個生(shēng)僻詞,然後翻譯從來沒見過這個詞,這時候翻譯就不知(zhī)道怎麽翻譯了,沒有辦法,就直接翻譯成了??,也就是亂碼了。

在計算機的世界也是同理,比如我(wǒ)(wǒ)們想從一(yī)個程序A發送雙子孤狼四個字到另一(yī)個程序B,這時候計算機數據傳輸的時候會轉成二進制,傳輸過去(qù)之後,因爲二進制不适合人類閱讀,所以B就需要進行解碼,可是現在B并不知(zhī)道A用的是什麽語言進行的編碼,所以就胡亂用英文進行解碼,解碼出來的字符英文肯定是不存在的,也就是在英文字符集裏面找不到雙子孤狼這個單詞,這時候就會發生(shēng)亂碼。

所以亂碼的本質其實就是當前編碼無法解析接收到的二進制數據。

字符集的曆史

知(zhī)道了爲什麽要編碼以及亂碼的原因之後,不禁又(yòu)有另一(yī)個疑問了,如果說全世界都統一(yī)用一(yī)種編碼,那在正常情況下(xià)也就沒有亂碼問題了,可是現實情況卻是各種編碼猶如八仙過海各顯神通,整的我(wǒ)(wǒ)們程序員(yuán)頭暈腦脹,一(yī)不留神亂碼就出來了。不過要回答這個問題那麽就需要了解一(yī)下(xià)字符集的發展曆史了。

ASCII編碼的誕生(shēng)

計算機最開(kāi)始誕生(shēng)于美國,而且計算機隻能識别二進制,所以我(wǒ)(wǒ)們就需要把常用語言和二進制關聯起來。美國人把英文裏面常用的字符以及一(yī)些控制字符轉換成了二進制數據,比如我(wǒ)(wǒ)們耳熟能詳的小(xiǎo)寫字母a,對應的十進制是97,二進制就是01100001。而一(yī)個字節有8位,即最大(dà)能表示255個字符,但是英語的常用字符比較少,常用的字母以及一(yī)些常用符号列出來就是128個,所以美國人就占用了這0-127的位置,形成了一(yī)個編碼對應關系表,這就是ASCII(AmericanStandardCodeforInformationInterchange,美國标準信息交換碼)編碼,ASCII編碼表的對應關系如果大(dà)家想知(zhī)道的可以自己去(qù)查一(yī)下(xià),這裏就不列舉了。

IOS-8859編碼家族誕生(shēng)

随着計算機的普及,計算機傳到了歐洲,這時候發現歐洲的常用字符也需要進行編碼,于是國際标準化組織(ISO)及國際電(diàn)工(gōng)委員(yuán)會(IEC)決定聯合制定另一(yī)套字符集标準。于是ISO-8859-1字符集就誕生(shēng)了。

因爲ASCII隻用到了0-127個位置,另外(wài)128-255的位置并沒有被占用(也就是一(yī)個字節的最高位并沒有被使用),于是歐洲人就把第8位利用了起來,從此這128-255就被西歐常用字符占用了,ISO-8859-1字符也叫做Latin1編碼。

慢(màn)慢(màn)的,随着時間的推移,歐洲越來越多國家的字符需要編碼,所以就衍生(shēng)了一(yī)系列的字符集,從ISO-8859-1到ISO-8859-16經過了一(yī)系列的微調,但是這些都屬于ISO-8859标準。

需要注意的是,ISO-8859标準是向下(xià)兼容ASCII字符集的,所以平常我(wǒ)(wǒ)們見到的許多場景下(xià)默認都是用的ISO-8859-1編碼比較多,而不會直接使用ASCII編碼。

GB2312和GBK等雙字節編碼誕生(shēng)

慢(màn)慢(màn)的,随着時間的推移,計算機傳到了亞洲,傳到了中(zhōng)國以及其他國家,這時候許多國家都針對自己國家的常用文字制定了自己國家的編碼,中(zhōng)國也不例外(wài)。

但是這個時候卻發現,一(yī)個字節的8位已經全部被占用了,于是隻能再擴展一(yī)個字節,也就是用2個字節來存儲。但是兩個字節來存儲又(yòu)有一(yī)個問題,那就是比如我(wǒ)(wǒ)讀取了兩個字節出來,這兩個字節到底是表示兩個單字節字符還是表示的是雙字節的中(zhōng)文呢?

于是我(wǒ)(wǒ)們偉大(dà)的中(zhōng)國人民就決定制定一(yī)套中(zhōng)文編碼,用來兼容ASCII,因爲ASCII編碼中(zhōng)的單字節字符一(yī)定是小(xiǎo)于128的,所以最後我(wǒ)(wǒ)們就決定,中(zhōng)文的雙字節字符都從128之後開(kāi)始,也就是當發現字符連續兩位都大(dà)于128時,就說明這是一(yī)個中(zhōng)文,指定了之後我(wǒ)(wǒ)們就把這種編碼方式稱之爲GB2312編碼。

需要注意的是GB2312并不兼容ISO-8859-n編碼集,但是兼容ASCII編碼。

GB2312編碼收錄了常用的漢字6763個和非漢字圖形字符682(包括拉丁字母、希臘字母、日文平假名及片假名字母、俄語西裏爾字母在内的全角字符)個。

随着計算機的更進一(yī)步普及,GB2312也暴露出了問題,那就是GB2312中(zhōng)收錄的中(zhōng)文漢字都是簡體(tǐ)字和常用字,對于一(yī)些生(shēng)僻字以及繁體(tǐ)字沒有收錄,于是乎GBK出現了。

GB2312編碼因爲兩個字節采用的都是高位,就算全部對應上,最大(dà)也隻能存儲16384個漢字,而我(wǒ)(wǒ)國漢字如果加上繁體(tǐ)字和生(shēng)僻字是遠遠不夠的,于是GBK的做法就是隻要求第一(yī)位是大(dà)于128,第二位可以小(xiǎo)于128,這就是說隻要發現一(yī)個字節大(dà)于128,那麽緊随其後的一(yī)個字節就是和其作爲一(yī)個整體(tǐ)作爲中(zhōng)文字符,這樣最多就能存儲32640個漢字了。當然,GBK并沒有全部用完,GBK共收入21886個漢字和圖形符号,其中(zhōng)漢字(包括部首和構件)21003個,圖形符号883個。

後面随着計算機的再進一(yī)步普及,我(wǒ)(wǒ)們也慢(màn)慢(màn)擴展了其他的中(zhōng)文字符集,比如GB18030等,但是這些都屬于雙字節字符。

到這裏希望大(dà)家明白(bái),爲什麽英文是一(yī)個字符,中(zhōng)文是兩個甚至更多字符了。一(yī)個原因就是低位被用了,另一(yī)個就是常用中(zhōng)文字符太多了,一(yī)個字節是遠遠存不完的。

Unicode字符誕生(shēng)

其實計算機在發展過程中(zhōng),不單單是美國,歐洲和中(zhōng)國,其他許多國家都有自己的字符,比如日本,韓國等都有自己的字符集,可以說很混亂,于是有關部門看不下(xià)去(qù)了,決定結束這種世界大(dà)戰的混亂局面,重新制定另一(yī)套字符标準,這就是Unicode。

從一(yī)出生(shēng)開(kāi)始,Unicode就覺得除了自己,其他各位都是渣渣。所以它壓根就沒準備兼容其他編碼,直接另起爐竈來了一(yī)套标準。Unicode字符最開(kāi)始采用的是UCS-2标準,UCS-2标準規定一(yī)個字符至少使用2個字節來表示。當然,2個字節即使全被利用也隻能存儲65536個字符,這肯定容納不了世界上所有的語言和符号以及控制字符,所以後面又(yòu)有了UCS-4标準,可以用4個字節來存儲一(yī)個字符,四個字節來存儲全世界所有語言文字和控制字符是基本沒有問題了。

需要注意的是:Unicode編碼隻是定義了字符集,對于字符集具體(tǐ)應該如何存儲并沒有做要求。站在我(wǒ)(wǒ)們開(kāi)發的角度,相當于Unicode隻定義了接口,但是沒有具體(tǐ)的實現。

UTF編碼家族誕生(shēng)

UTF系列編碼就是對Unicode字符集的實現,隻不過實現的方式有所區别,其中(zhōng)主要有:UTF-8,UTF-16,UTF-32等類型。

UTF-32編碼

UTF-32編碼基本按照Unicode字符集标準來實現,任何一(yī)個符号都占用4個字節。可以想象,這會浪費(fèi)多大(dà)空間,對英文而言,空間擴大(dà)了四倍,中(zhōng)文也擴大(dà)了兩倍,所以這種編碼方式也導緻了Unicode在最初并沒有被大(dà)家廣泛的接受。

UTF-16編碼

UTF-16編碼相比較UTF-32做了一(yī)點改進,其采用2個字節或者4個字節來存儲。大(dà)部分(fēn)情況下(xià)UTF-16編碼都是采用2個字節來存儲,而當2個字節存儲時,UTF-16編碼會将Unicode字符直接轉成二進制進行存儲,對于另外(wài)一(yī)些生(shēng)僻字或者使用較少的符号,UTF-16編碼會采用4個字節來存儲,但是采用四個字節存儲時需要做一(yī)次編碼轉換。

下(xià)表就是UTF-16編碼的存儲格式:

Unicode編碼範圍(16進制) UTF-16編碼的二進制存儲格式
0x00000000-0x0000FFFF xxxxxxxxxxxxxxxx
0x00010000-0x0010FFFF 110110xxxxxxxxxx110111xxxxxxxxxx
這個表先不解釋,後面解釋UTF-8編碼時會一(yī)起說明。

UTF-8編碼

UTF-8是一(yī)種變長的編碼,兼容了ASCII編碼,爲了實現變長這個特性,那麽就必須要有一(yī)個規範來規定存儲格式,這樣當程序讀了2個或者多個字節時能解析出這到底是表示多個單字節字符還是一(yī)個多字節字符。

UTF-8編碼的存儲規範如下(xià)表所示:

Unicode編碼範圍(16進制) UTF-8編碼的二進制存儲格式
0x00000000-0x0000007F 0xxxxxxx
0x00000080-0x000007FF 110xxxxx10xxxxxx
0x00000800-0x0000FFFF 1110xxxx10xxxxxx10xxxxxx
0x00010000-0x0010FFFF 11110xxx10xxxxxx10xxxxxx10xxxxxx
接下(xià)來我(wǒ)(wǒ)們以雙字爲例來進行說明:

雙:對應的Unicode編碼爲\u53cc,轉成二進制就是:101001111001100,這時候表格中(zhōng)的第一(yī)行隻有7位存不下(xià)去(qù),第二列也隻有11位,也不夠存儲,所以隻能存儲到第三列,第三列有16位,從後往前依次填補x的位置,填完之後還有一(yī)位空餘,直接補0,最終得到:111001011000111110001100,所以雙字就占用了3個字節,當然,有些生(shēng)僻字會占用到四個字節。

所以上面的UTF-16編碼也是同理,如果當前字符采用的是兩字節存儲,那麽直接轉成二進制存儲即可,位數不足直接補0即可,而當采用4個字節存儲時,則需要和UTF-8一(yī)樣進行一(yī)次轉換,也就是說隻能将其填充到x的位置,x之外(wài)的是固定格式。

需要注意的是:在UTF-16編碼中(zhōng),2個字節也可能出現4字節中(zhōng)110110xx或者110111xx開(kāi)頭的格式,這兩部分(fēn)對應的區間分(fēn)别是:D800~DBFF和DC00~DFFF,所以爲了避免這種歧義的發生(shēng),這兩部分(fēn)區間是是專門空出來的,沒有進行編碼。

爲什麽有時候亂碼都是?号

在Java開(kāi)發中(zhōng),經常會碰到亂碼顯示爲?号,比如下(xià)面這個例子:

Stringname="雙子孤狼";byte[]bytes=name.getBytes(StandardCharsets.ISO_8859_1);System.out.println(newString(bytes));//輸出:????

這個輸出結果的原因是中(zhōng)文無法用ISO_8859_1編碼進行存儲,而示例中(zhōng)卻強制用ISO_8859_1編碼進行解碼。

在Java中(zhōng)提供了一(yī)個ISO_8859_1類用來解碼,解碼時當發現當前字符轉成十進制之後大(dà)于255時就會直接不進行解碼,轉而直接賦一(yī)個默認值63,所以上面的示例中(zhōng)的byte數組結果就是63636363,而63在ASCII中(zhōng)就恰好就對應了?号。

所以一(yī)般我(wǒ)(wǒ)們看到編碼出現?基本就說明當前是采用ISO_8859_1進行的解碼,而當前的字符又(yòu)大(dà)于255。

拓展知(zhī)識

了解了編碼發展曆史之後,接下(xià)來就讓我(wǒ)(wǒ)們一(yī)起了解下(xià)其他和編碼相關的題外(wài)話(huà)。

代碼點和代碼單元

在Java中(zhōng)的字符串是由char序列組成,而char又(yòu)是采用UTF-16表示的Unicode代碼點的代碼單元。這句話(huà)裏面涉及到了代碼點和代碼單元,初次接觸的朋友可能會有點迷惑,但是了解了Unicode字符集标準和UTF-16的編碼方式之後就比較好理解。

代碼點:一(yī)個代碼點等同于一(yī)個Unicode字符。

代碼單元:在UTF-16中(zhōng),兩個字節表示一(yī)個代碼單元,代碼單元是最小(xiǎo)的不可拆分(fēn)的部分(fēn),所以如果在UTF-8中(zhōng),一(yī)個代碼單元就是一(yī)個字節,因爲UTF-8中(zhōng)可以用一(yī)個字節表示一(yī)個字符。

平常我(wǒ)(wǒ)們調用字符串的length方法,返回的就是代碼單元數量,而不是代碼點數量,所有如果碰到一(yī)些需要用4個字節來表示的繁體(tǐ)字,那麽代碼單元數就會小(xiǎo)于代碼點數,而想要獲取代碼點數量,可以通過其他方法獲取,獲取方式如下(xià):

Stringname="䭢";//\uD852\uDF62System.out.println(name.length);//代碼單元數,輸出2System.out.println(name.codePointCount(0,name.length));//代碼點數,輸出1

大(dà)端模式和小(xiǎo)端模式

在計算機中(zhōng),數據的存儲是以字節爲單位的,那麽當一(yī)個字符需要使用多個字節來表示的時候,就會産生(shēng)一(yī)個問題,那就是多字節字符應該從前往後組合還是從後往前組合。

還是以雙字爲例,轉成二進制爲:0101001111001100,以一(yī)個字節爲單位,就可以拆分(fēn)成:01010011和11001100,其中(zhōng)第一(yī)部分(fēn)就稱之爲高位字節,第二部分(fēn)就稱之爲低位字節,将這兩部分(fēn)順序互換存儲就産生(shēng)了大(dà)端模式和小(xiǎo)端模式。

大(dà)端模式(Big-endian):顧名思義就是以高位字節結尾,低位在前(左),高位在後(右)。如雙字就會存儲爲:1100110001010011。

小(xiǎo)端模式(Little-endian):顧名思義就是以低位字節結尾,高位在前(左),低位在後(右)。如雙字就會存儲爲:0101001111001100(和我(wǒ)(wǒ)們平常計算二進制的邏輯一(yī)緻,從右到左依次從2的0次方開(kāi)始)。

注:在Java中(zhōng)默認采用的是大(dà)端模式,雖然底層的處理器可能會采用不同的模式存儲字節,但是因爲有JVM的存在,這些細節已經被屏蔽,所以平常大(dà)家可能也沒有很關注這些。

BOM

既然底層存儲分(fēn)爲了大(dà)端和小(xiǎo)端兩種模式,那麽假如我(wǒ)(wǒ)們現在有一(yī)個文件,計算機又(yòu)是怎麽知(zhī)道當前是采用的大(dà)端模式還是小(xiǎo)端模式呢?

BOM即byteordermark(字節順序标記),出現在文本文件頭部。BOM就是用來标記當前文件采用的是大(dà)端模式還是小(xiǎo)端模式存儲。我(wǒ)(wǒ)想這個大(dà)家應該都見過,平常在使用記事本保存文檔的時候,需要選擇采用的是大(dà)端還是小(xiǎo)端:


在UCS編碼中(zhōng)有一(yī)個叫做ZeroWidthNo-BreakSpace(零寬無間斷間隔)的字符,對應的編碼是FEFF。FEFF是不存在的字符,正常來說不應該出現在實際數據傳輸中(zhōng)。

但是爲了區分(fēn)大(dà)端模式和小(xiǎo)端模式,UCS規範建議在傳輸字節流前,先傳輸字符ZeroWidthNo-BreakSpace。而且根據這個字符的順序來區分(fēn)大(dà)端模式和小(xiǎo)端模式。

下(xià)表就是不同編碼的BOM:

編碼 16進制BOM
UTF-8 EFBBBF
UTF-16(大(dà)端模式) FEFF
UTF-16(小(xiǎo)端模式) FFFE
UTF-32(大(dà)端模式) 0000FEFF
UTF-32(小(xiǎo)端模式) FFFE0000

有了這個規範,解析文件的時候就可以知(zhī)道當前編碼以及其存儲模式了。注意這裏UTF-8編碼比較特殊,因爲本身UTF-8編碼有特殊的順序格式規定,所以UTF-8本身并沒有什麽大(dà)端模式和小(xiǎo)端模式的區别.

根據UTF-8本身的特殊編碼格式,在沒有BOM的情況下(xià)也能被推斷出來,但是因爲微軟是建議都加上BOM,所以目前存在了帶BOM的UTF-8文件和不帶BOM的UTF-8文件,這兩種格式在某些場景可能會出現不兼容的問題,所以在平常使用中(zhōng)也可以稍微留意這個問題。

總結

本文主要從編碼的曆史開(kāi)始,講述了編碼的存儲規則并且分(fēn)析了産生(shēng)亂碼的本質原因,同時也分(fēn)析了字節的兩種存儲模型以及BOM相關問題,通過本文相信對于項目中(zhōng)出現的亂碼問題,大(dà)家會有一(yī)個清晰的思路來分(fēn)析問題。
返回列表
在線溝通

Are you interested in ?

感興趣嗎(ma)?

有關我(wǒ)(wǒ)們服務的更多信息,請聯系

136 7365 2363(同微信) 13140187702

鄭州網站建設鄭州網站設計鄭州網站制作鄭州建站公司鄭州網站優化--聯系索騰

與我(wǒ)(wǒ)們合作

鄭州網站建設鄭州網站設計鄭州網站制作鄭州建站公司鄭州網站優化--與索騰合作,您将會得到更成熟、專業的網絡建設服務。我(wǒ)(wǒ)們以客戶至上,同時也相互挑戰,力求呈現最好的品牌建設成果。

業務咨詢熱線:

136 7365 2363

TOP

QQ客服

在線留言