一、亂碼
首先,所有信息在計算機上都是以二進制形式存儲。而當出現(xiàn)亂碼的時候往往是將這些信息以字符的形式表現(xiàn)之后。這是因為用的編碼和解碼的方式不一樣。
舉個例子:
當我們將“祝福”輸入電腦時。默認是以GB2312作為字符編碼進行編碼,將漢字編碼成二進制存儲在電腦中,當我們再次讀取時,若采用UTF-8字符編碼來解碼輸出,就會造成亂碼。
就像:
當英國人將“祝?!睂懺诩埳蠒r。默認是以英文來編碼,將“祝?!钡囊馑季幋a成bless。當一個法國人讀取時,會通過法語來解碼這個單詞的意思,在法語中,bless就是“受傷”的意思。就會造成理解錯誤,而當如果法語如果沒有這個單詞,就會翻譯出錯,出現(xiàn)亂碼。
二、字符集
字符集是一個規(guī)則的集合。就比如上述的英語,漢語,法語。
對于一個字符集來說,正確編碼轉(zhuǎn)碼一個字符需要三個關(guān)鍵元素:字庫表(character repertoire)、編碼字符集(coded character set)、字符編碼(character encoding form)。
其中字庫表相當于一個所有字符的數(shù)據(jù)庫。編碼字符集(編碼用的字符集)用來表示一個字符在字庫中的位置。字符編碼(字符的編碼)表示將編碼字符集轉(zhuǎn)化為實際存儲的數(shù)值。
一般來說,會直接將編碼字符集的值作為編碼后的值直接存儲。例如ASCLL中A的位置是65位,編碼后的A的數(shù)值是0100 0001,即十進制的65轉(zhuǎn)化為二進制。
</br>
看到這里,可能有人會疑惑:既然每個字符都有自己的編號(編碼字符集),那直接存儲就好了啊,為什么還要字符編碼呢?
其實原因也比較好理解,unicode的出現(xiàn)是為了統(tǒng)一字庫表,能夠涵蓋世界上所有的字符,但實際使用過程中會發(fā)現(xiàn)真正用的上的字符相對整個字庫表來說比例非常低。例如中文地區(qū)的程序幾乎不會需要日語字符,而一些英語國家甚至簡單的ASCII字庫表就能滿足基本需求。而如果把每個字符都用字庫表中的序號來存儲的話,每個字符就需要3個字節(jié)(這里以Unicode字庫為例),這樣對于原本用僅占一個字符的ASCII編碼的英語地區(qū)國家顯然是一個額外成本(存儲體積是原來的三倍)。算的直接一些,同樣一塊硬盤,用ASCII可以存1500篇文章,而用3字節(jié)Unicode序號存儲只能存500篇。于是就出現(xiàn)了UTF-8這樣的變長編碼。在UTF-8編碼中原本只需要一個字節(jié)的ASCII字符,仍然只占一個字節(jié)。而像中文及日語這樣的復(fù)雜字符就需要2個到3個字節(jié)來存儲。
UTF-8和Unicode的關(guān)系
看完上面的解釋,那么對于UTF-8和Unicode的關(guān)系就比較好理解了。unicode就是上面的編碼字符集,而utf-8就是字符編碼。也可以理解為unicode是字符在字庫里的位置,或者unicode代表整個字庫。
unicode幾乎包括了所有國家的可能出現(xiàn)的所有字符。Unicode的編號從0000開始一直到10FFFF共分為16個Plane,每個Plane中有65536個字符。而UTF-8則只實現(xiàn)了第一個Plane,可見UTF-8雖然是一個當今接受度最廣的字符集編碼,但是它并沒有涵蓋整個Unicode的字庫,這也造成了它在某些場景下對于特殊字符的處理困難。
UTF-8編碼簡介
為了更好的理解后面的實際應(yīng)用,我們這里簡單的介紹下UTF-8的編碼實現(xiàn)方法。即UTF-8的物理存儲和Unicode序號的轉(zhuǎn)換關(guān)系。
UTF-8編碼為變長編碼。最小編碼單位(code unit)為一個字節(jié)。一個字節(jié)的前1-3個bit為描述性部分,后面為實際序號部分。
1、如果一個字節(jié)的第一位為0,那么代表當前字符為單字節(jié)字符,占用一個字節(jié)的空間。0之后的所有部分(7個bit)代表在Unicode中的序號。
2、如果一個字節(jié)以110開頭,那么代表當前字符為雙字節(jié)字符,占用2個字節(jié)的空間。110之后的所有部分(7個bit)代表在Unicode中的序號。且第二個字節(jié)以10開頭
3、如果一個字節(jié)以1110開頭,那么代表當前字符為三字節(jié)字符,占用2個字節(jié)的空間。110之后的所有部分(7個bit)代表在Unicode中的序號。且第二、第三個字節(jié)以10開頭
4、如果一個字節(jié)以10開頭,那么代表當前字節(jié)為多字節(jié)字符的第二個字節(jié)。10之后的所有部分(6個bit)代表在Unicode中的序號。
具體每個字節(jié)的特征可見下表,其中x代表序號部分,把各個字節(jié)中的所有x部分拼接在一起就組成了在Unicode字庫中的序號:

例如“漢”字的Unicode編碼是6C49。6C49在0800-FFFF之間,所以肯定要用3字節(jié)模板了:1110xxxx 10xxxxxx 10xxxxxx。將6C49寫成二進制是:0110 110001 001001,用這個比特流依次代替模板中的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解碼中文也會亂碼),那我們怎么解決呢?
其實只要分析每個需要解碼的過程,一一分析就可以知道了。以web應(yīng)用為例:
首先在jsp上面有一行不可或缺的代碼<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
pageEncoding是jsp文件本身的編碼
contentType的charset是指服務(wù)器發(fā)送給客戶端時的內(nèi)容編碼
JSP要經(jīng)過兩次的“編碼”,第一階段會用pageEncoding,第二階段會用utf-8至utf-8,第三階段就是由Tomcat出來的網(wǎng)頁, 用的是contentType。
第一階段是jsp編譯成.java,它會根據(jù)pageEncoding的設(shè)定讀取jsp,結(jié)果是由指定的編碼方案翻譯成統(tǒng)一的UTF-8 JAVA源碼(即.java),如果pageEncoding設(shè)定錯了,或沒有設(shè)定,出來的就是中文亂碼。
第二階段是由JAVAC的JAVA源碼至java byteCode的編譯,不論JSP編寫時候用的是什么編碼方案,經(jīng)過這個階段的結(jié)果全部是UTF-8的encoding的java源碼。
JAVAC用UTF-8的encoding讀取java源碼,編譯成UTF-8 encoding的二進制碼(即.class),這是JVM對常數(shù)字串在二進制碼(java encoding)內(nèi)表達的規(guī)范。
第三階段是Tomcat(或其的application container)載入和執(zhí)行階段二的來的JAVA二進制碼,輸出的結(jié)果,也就是在客戶端見到的,這時隱藏在階段一和階段二的參數(shù)contentType就發(fā)揮了功效
參考文獻:
Notepad++的多種編碼支持
編譯.java文件時的編碼問題
【筆面試】字符流和字節(jié)流的區(qū)別以及如何解決亂碼問題
jsp中的contentType與pageEncoding的區(qū)別和作用
漢字編碼轉(zhuǎn)換原理及方法
四、Notepad++
而像Notepad++等軟件,有兩種功能

以XXX格式編碼是改變編碼字符集,意味著在電腦中存儲的值是不變的
轉(zhuǎn)為XXX格式編碼是改變編碼格式,意味著都是同一個字符集,改變的是編碼方式,比如UTF-8轉(zhuǎn)UTF-16
utf8mb4可以放表情,4個字節(jié)
utf8_bin將字符串中的每一個字符用二進制數(shù)據(jù)存儲,區(qū)分大小寫。
utf8_genera_ci不區(qū)分大小寫,ci為case insensitive的縮寫,即大小寫不敏感。
utf8_general_cs區(qū)分大小寫,cs為case sensitive的縮寫,即大小寫敏感。