一、亂碼
首先腔寡,所有信息在計(jì)算機(jī)上都是以二進(jìn)制形式存儲(chǔ)洪添。而當(dāng)出現(xiàn)亂碼的時(shí)候往往是將這些信息以字符的形式表現(xiàn)之后垦页。這是因?yàn)橛玫木幋a和解碼的方式不一樣。
舉個(gè)例子:
當(dāng)我們將“祝皋弊椋”輸入電腦時(shí)外臂。默認(rèn)是以GB2312作為字符編碼進(jìn)行編碼,將漢字編碼成二進(jìn)制存儲(chǔ)在電腦中律胀,當(dāng)我們?cè)俅巫x取時(shí)宋光,若采用UTF-8字符編碼來解碼輸出,就會(huì)造成亂碼炭菌。
就像:
當(dāng)英國(guó)人將“祝缸锛眩”寫在紙上時(shí)。默認(rèn)是以英文來編碼黑低,將“祝缸秆蓿”的意思編碼成bless酌毡。當(dāng)一個(gè)法國(guó)人讀取時(shí),會(huì)通過法語來解碼這個(gè)單詞的意思蕾管,在法語中枷踏,bless就是“受傷”的意思。就會(huì)造成理解錯(cuò)誤掰曾,而當(dāng)如果法語如果沒有這個(gè)單詞旭蠕,就會(huì)翻譯出錯(cuò),出現(xiàn)亂碼旷坦。
二掏熬、字符集
字符集是一個(gè)規(guī)則的集合。就比如上述的英語秒梅,漢語旗芬,法語。
對(duì)于一個(gè)字符集來說捆蜀,正確編碼轉(zhuǎn)碼一個(gè)字符需要三個(gè)關(guān)鍵元素:字庫(kù)表(character repertoire)疮丛、編碼字符集(coded character set)、字符編碼(character encoding form)漱办。
其中字庫(kù)表相當(dāng)于一個(gè)所有字符的數(shù)據(jù)庫(kù)这刷。編碼字符集(編碼用的字符集)用來表示一個(gè)字符在字庫(kù)中的位置。字符編碼(字符的編碼)表示將編碼字符集轉(zhuǎn)化為實(shí)際存儲(chǔ)的數(shù)值娩井。
一般來說暇屋,會(huì)直接將編碼字符集的值作為編碼后的值直接存儲(chǔ)。例如ASCLL中A的位置是65位洞辣,編碼后的A的數(shù)值是0100 0001
咐刨,即十進(jìn)制的65轉(zhuǎn)化為二進(jìn)制。
</br>
看到這里扬霜,可能有人會(huì)疑惑:既然每個(gè)字符都有自己的編號(hào)(編碼字符集)定鸟,那直接存儲(chǔ)就好了啊,為什么還要字符編碼呢著瓶?
其實(shí)原因也比較好理解联予,unicode的出現(xiàn)是為了統(tǒng)一字庫(kù)表,能夠涵蓋世界上所有的字符材原,但實(shí)際使用過程中會(huì)發(fā)現(xiàn)真正用的上的字符相對(duì)整個(gè)字庫(kù)表來說比例非常低沸久。例如中文地區(qū)的程序幾乎不會(huì)需要日語字符,而一些英語國(guó)家甚至簡(jiǎn)單的ASCII字庫(kù)表就能滿足基本需求余蟹。而如果把每個(gè)字符都用字庫(kù)表中的序號(hào)來存儲(chǔ)的話卷胯,每個(gè)字符就需要3個(gè)字節(jié)(這里以Unicode字庫(kù)為例),這樣對(duì)于原本用僅占一個(gè)字符的ASCII編碼的英語地區(qū)國(guó)家顯然是一個(gè)額外成本(存儲(chǔ)體積是原來的三倍)威酒。算的直接一些窑睁,同樣一塊硬盤挺峡,用ASCII可以存1500篇文章,而用3字節(jié)Unicode序號(hào)存儲(chǔ)只能存500篇担钮。于是就出現(xiàn)了UTF-8這樣的變長(zhǎng)編碼橱赠。在UTF-8編碼中原本只需要一個(gè)字節(jié)的ASCII字符,仍然只占一個(gè)字節(jié)裳朋。而像中文及日語這樣的復(fù)雜字符就需要2個(gè)到3個(gè)字節(jié)來存儲(chǔ)病线。
UTF-8和Unicode的關(guān)系
看完上面的解釋,那么對(duì)于UTF-8和Unicode的關(guān)系就比較好理解了鲤嫡。unicode就是上面的編碼字符集,而utf-8就是字符編碼绑莺。也可以理解為unicode是字符在字庫(kù)里的位置暖眼,或者unicode代表整個(gè)字庫(kù)。
unicode幾乎包括了所有國(guó)家的可能出現(xiàn)的所有字符纺裁。Unicode的編號(hào)從0000開始一直到10FFFF共分為16個(gè)Plane诫肠,每個(gè)Plane中有65536個(gè)字符。而UTF-8則只實(shí)現(xiàn)了第一個(gè)Plane欺缘,可見UTF-8雖然是一個(gè)當(dāng)今接受度最廣的字符集編碼栋豫,但是它并沒有涵蓋整個(gè)Unicode的字庫(kù),這也造成了它在某些場(chǎng)景下對(duì)于特殊字符的處理困難谚殊。
UTF-8編碼簡(jiǎn)介
為了更好的理解后面的實(shí)際應(yīng)用丧鸯,我們這里簡(jiǎn)單的介紹下UTF-8的編碼實(shí)現(xiàn)方法。即UTF-8的物理存儲(chǔ)和Unicode序號(hào)的轉(zhuǎn)換關(guān)系嫩絮。
UTF-8編碼為變長(zhǎng)編碼丛肢。最小編碼單位(code unit)為一個(gè)字節(jié)。一個(gè)字節(jié)的前1-3個(gè)bit為描述性部分剿干,后面為實(shí)際序號(hào)部分蜂怎。
1、如果一個(gè)字節(jié)的第一位為0置尔,那么代表當(dāng)前字符為單字節(jié)字符杠步,占用一個(gè)字節(jié)的空間。0之后的所有部分(7個(gè)bit)代表在Unicode中的序號(hào)榜轿。
2幽歼、如果一個(gè)字節(jié)以110開頭,那么代表當(dāng)前字符為雙字節(jié)字符差导,占用2個(gè)字節(jié)的空間试躏。110之后的所有部分(7個(gè)bit)代表在Unicode中的序號(hào)。且第二個(gè)字節(jié)以10開頭
3设褐、如果一個(gè)字節(jié)以1110開頭颠蕴,那么代表當(dāng)前字符為三字節(jié)字符泣刹,占用2個(gè)字節(jié)的空間。110之后的所有部分(7個(gè)bit)代表在Unicode中的序號(hào)犀被。且第二椅您、第三個(gè)字節(jié)以10開頭
4、如果一個(gè)字節(jié)以10開頭寡键,那么代表當(dāng)前字節(jié)為多字節(jié)字符的第二個(gè)字節(jié)掀泳。10之后的所有部分(6個(gè)bit)代表在Unicode中的序號(hào)。
具體每個(gè)字節(jié)的特征可見下表西轩,其中x代表序號(hào)部分员舵,把各個(gè)字節(jié)中的所有x部分拼接在一起就組成了在Unicode字庫(kù)中的序號(hào):
例如“漢”字的Unicode編碼是6C49。6C49在0800-FFFF之間藕畔,所以肯定要用3字節(jié)模板了:1110xxxx 10xxxxxx 10xxxxxx马僻。將6C49寫成二進(jìn)制是:0110 110001 001001,用這個(gè)比特流依次代替模板中的x注服,得到:11100110 10110001 10001001韭邓,即E6 B1 89。
其中iso 8859-1,gb2312,gbk,gb18030,big5,unicode等都是編碼字符集和字符編碼一致的字符集溶弟,其中女淑,unicode還有好幾種字符編碼,比如UTF-8辜御,UTF-16等等鸭你。
<br />
三、亂碼解決
根據(jù)上訴內(nèi)容我抠,可以知道大部分的亂碼都是由解碼編碼不統(tǒng)一引起的(iso8859-1解碼中文也會(huì)亂碼)苇本,那我們?cè)趺唇鉀Q呢?
其實(shí)只要分析每個(gè)需要解碼的過程菜拓,一一分析就可以知道了瓣窄。以web應(yīng)用為例:
首先在jsp上面有一行不可或缺的代碼<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
pageEncoding是jsp文件本身的編碼
contentType的charset是指服務(wù)器發(fā)送給客戶端時(shí)的內(nèi)容編碼
JSP要經(jīng)過兩次的“編碼”,第一階段會(huì)用pageEncoding纳鼎,第二階段會(huì)用utf-8至utf-8俺夕,第三階段就是由Tomcat出來的網(wǎng)頁(yè), 用的是contentType贱鄙。
第一階段是jsp編譯成.java劝贸,它會(huì)根據(jù)pageEncoding的設(shè)定讀取jsp,結(jié)果是由指定的編碼方案翻譯成統(tǒng)一的UTF-8 JAVA源碼(即.java)逗宁,如果pageEncoding設(shè)定錯(cuò)了映九,或沒有設(shè)定,出來的就是中文亂碼瞎颗。
第二階段是由JAVAC的JAVA源碼至java byteCode的編譯件甥,不論JSP編寫時(shí)候用的是什么編碼方案捌议,經(jīng)過這個(gè)階段的結(jié)果全部是UTF-8的encoding的java源碼。
JAVAC用UTF-8的encoding讀取java源碼引有,編譯成UTF-8 encoding的二進(jìn)制碼(即.class)瓣颅,這是JVM對(duì)常數(shù)字串在二進(jìn)制碼(java encoding)內(nèi)表達(dá)的規(guī)范。
第三階段是Tomcat(或其的application container)載入和執(zhí)行階段二的來的JAVA二進(jìn)制碼譬正,輸出的結(jié)果宫补,也就是在客戶端見到的,這時(shí)隱藏在階段一和階段二的參數(shù)contentType就發(fā)揮了功效
參考文獻(xiàn):
Notepad++的多種編碼支持
編譯.java文件時(shí)的編碼問題
【筆面試】字符流和字節(jié)流的區(qū)別以及如何解決亂碼問題
jsp中的contentType與pageEncoding的區(qū)別和作用
漢字編碼轉(zhuǎn)換原理及方法
四曾我、Notepad++
而像Notepad++等軟件粉怕,有兩種功能
以XXX格式編碼是改變編碼字符集,意味著在電腦中存儲(chǔ)的值是不變的
轉(zhuǎn)為XXX格式編碼是改變編碼格式抒巢,意味著都是同一個(gè)字符集斋荞,改變的是編碼方式,比如UTF-8轉(zhuǎn)UTF-16
utf8mb4可以放表情虐秦,4個(gè)字節(jié)
utf8_bin將字符串中的每一個(gè)字符用二進(jìn)制數(shù)據(jù)存儲(chǔ),區(qū)分大小寫凤优。
utf8_genera_ci不區(qū)分大小寫悦陋,ci為case insensitive的縮寫,即大小寫不敏感筑辨。
utf8_general_cs區(qū)分大小寫俺驶,cs為case sensitive的縮寫,即大小寫敏感棍辕。