1. 程式人生 > 實用技巧 >亂碼問題及字元編碼集(一)

亂碼問題及字元編碼集(一)

小白在剛開始學習程式設計,經常會遇到一些亂碼問題,導致程式無法編譯,在這裡,對此進行一個瞭解。

在聊編碼集之前,我們先來了解一些名詞解釋:

  • 字符集:所謂字元編碼就是一個系統支援的所有抽象字元的集合,也就是說我們平常使用的文字,標點符號,圖形符號等都是字符集。但計算機無法識別這些文字,只能識別二進位制的數字系統,所以需要一套規則,將字元轉換成為數字系統,這就是字元編碼。
  • 編碼: 按照某種字元編碼規則( GBK, UTF-8 等) 將字元以二進位制序列形式儲存在計算機中.
  • 解碼: 將儲存在計算機中的二進位制序列資料解析成對應的字元的過程.

上圖表示的是程式執行過程中對字元進行編碼和解碼的過程的. 請注意 unicode 的重要作用( java語言在程式碼裡宣告的每一個char、String型別的變數中字元在在JVM的常量池或磁碟檔案中,都是以unicode格式存在的.) 為什麼呢?這就要從字符集的發展歷史說起了.

編碼集發展歷史:
1. ASCII編碼:

美國國家標準協會ANSI制定了一個標準,規定了常用字元的集合以及每個字元對應的編號,這就是ASCII字符集(Character Set),也稱ASCII碼。這個編碼集如下,只是一個簡單的查表的過程。比如我要存 'a'字元,只要在ASCII表中查到它對應的編碼97(或二進位制 0110 0001 ),再以二進位制流寫入儲存裝置即可.

2。 OEM字符集

請注意: 此時,ASCII碼錶只用到了一個位元組(八位)中的前七位,第八位沒有用到(所以它只能表示128個字元,後面128個數字沒有用到).這樣混亂就產生,每個人都可以在 128-255這些位置存放到不同的字元,形成了各種不同的字符集,這些字符集統稱為OEM字符集

,用這些字符集生成的文件就不容易實現互換了.

3. ISO-8859-1 ( Latin-1 )

在這其中 ASCII最優秀的擴充套件方案就是 ISO-8859-1,通常稱為Latin-1的OEM字符集,它包括了足夠的附加字符集來寫基本的西歐,希臘語等語言。( 我們安裝mysql 5時,就可以注意到,它預設安裝的就是latin-1 的字符集 ).

4. ANSI標準

最後,這個人人蔘與的OEM終於以ANSI標準的形式形成檔案。在ANSI標準中,每個人都認同如何使用低端的128個編碼,這與ASCII相當一致。不過,根據所在國籍的不同,處理編碼128以上的字元有許多不同的方式。這些不同的系統稱為內碼表。

講到這裡,大家可以注意到,上面我們提到的字符集都是基於單位元組編碼,也就是說,一個位元組翻譯成一個字元。這對於拉丁語系國家來說可能沒有什麼問題,因為他們通過擴充套件第8個位元,就可以得到256個字元了,足夠用了。但是對於亞洲國家來說,256個字元是遠遠不夠用的。因此這些國家的人為了用上電腦,又要保持和ASCII字符集的相容,就發明了多位元組編碼方式,相應的字符集就稱為多位元組字符集。例如中國使用的就是雙位元組字符集編碼(DBCS,Double Byte CharacterSet)

5. 雙位元組字符集編碼(DBCS,Double Byte CharacterSet)

對於多位元組字符集,內碼表中通常會有很多碼錶。那麼程式怎麼知道該使用哪張碼錶去解碼二進位制流呢?答案是,根據第一個位元組來選擇不同的碼錶進行解析。 比如Windows系統採用936內碼表來實現對GBK字符集的編解碼。在解析位元組流的時候,如果遇到位元組的最高位是0的話,那麼就使用936內碼表中的第1張碼錶進行解碼,這就和單位元組字符集的編解碼方式一致了(如下圖)。

當位元組的高位是1的時候,確切的說,當第一個位元組位於0x81–0xFE之間時,根據第一個位元組不同找到內碼表中的相應的碼錶,例如當第一個位元組是0x81,那麼對應936中的下面這張碼錶:按照936內碼表的碼錶,當程式遇到連續位元組流0x81 0x40的時候,就會解碼為“丂”字元。

6. GB2312與GBK有什麼不同。

它們都佔兩個位元組,但GB2312編碼組合出7000多個常用簡體漢字,還包括數字符號、羅馬希臘字母、日文假名等. 但GB2312並沒有把所有的碼位都用完.

而GBK是GB2312的擴充套件(K就是擴充套件的意思).GBK包括了GB2312的所有內容,同時增加了近20000個新的漢字(包括繁體)和符號 。只要求高位大於0x7F,低位可以小於0x7F,認為是中文。

7. Unicode ( 定長 , 通常使用2個位元組,有的是4個位元組, 具體如何以幾個位元組編碼由字元編碼(utf-8, utf-16決定).

雖然通過使用不同字符集,我們可以在一臺機器上查閱不同語言的文件,但是我們仍然無法解決一個問題:在一份文件中顯示所有字元( 這就是亂碼的由來,世界上存在著多種編碼方式,同一個二進位制數字可以被解釋成不同的符號。因此,要想開啟一個文字檔案,就必須知道它的編碼方式,否則用錯誤的編碼方式解讀,就會出現亂碼。)。為了解決這個問題,我們需要一個全人類達成共識的巨大的字符集,這就是Unicode字符集。

Unicode字符集將所有字元按照使用上的頻繁度劃分為17個層面(Plane),每個層面上有216=65536個字元碼空間。

其中第0個層面BMP,基本涵蓋了當今世界用到的所有字元。其他的層面要麼是用來表示一些遠古時期的文字,要麼是留作擴充套件。我們平常用到的Unicode字元,一般都是位於BMP層面上的。目前Unicode字符集中尚有大量字元空間未使用。

前面談到的ASCII,GB2312,GBK字符集都限定了最多2個位元組來編碼所有字元,並且規定了位元組序。這樣的編碼系統通常用簡單的查表,也就是通過內碼表就可以直接將字元對映為儲存裝置上的位元組流了. 即這些編碼的字符集與字元編碼方案(碼錶)是合在一起了,限制了它的擴充套件能力。

而unicode在設計上考慮到了這一點,將字符集和字元編碼方案分離開

注意左邊表示每個字元在unicode字元中都能找到唯一確定的編號(unicode編碼),但最終如何以位元組流編碼和反編碼的是具體的字元編碼( 如 UTF-8, UTF-16等) .例如同樣是對Unicode字元“A”進行編碼,UTF-8字元編碼得到的位元組流是0x41,而UTF-16(大端模式)得到的是0x00 0x41.

概念上要區分開,以前的GB2312,ASCII即是字元編碼方式(這決定如何存,如何讀取解析),也是字符集( 表示字元與編號的對映關係),而從unicode開始,它只定了字元的集合和編號,具體如何編碼由字元編碼方案決定(即 utf-8,utf-16等).

8. utf-8 :變長,所以可以節省空間.

utf-8的特點是它是變長儲存,所以適用於儲存和網路傳輸,是具體的字元編碼方案.

  • 國際標準組織(ISO)制定英文字元使用1個位元組,沿用原來的ASCII碼
  • 使用1~4個位元組表示一個符號,中文儲存使用3個位元組(ascii碼中的內容用1個位元組儲存\歐洲的字元, 用2個位元組儲存\東亞的字元用3個位元組儲存\特殊符號用4個位元組)
  • Unicode是記憶體編碼表示方案(規範),而utf-8是如何儲存和傳輸Unicode的方案(實現)

UTF-8的編碼規則:

1)對於單位元組的UTF-8編碼,該位元組的最高位為0,其餘7位用來對字元進行編碼(等同於ASCII碼)。

2)對於多位元組的UTF-8編碼,如果編碼包含 n 個位元組,那麼第一個位元組的前 n 位為1,第一個位元組的第 n+1 位為0,該位元組的剩餘各位用來對字元進行編碼。在第一個位元組之後的所有的位元組,都是最高兩位為"10",其餘6位用來對字元進行編碼。

> 優點:雖然記憶體彙總的資料都是Unicode,但當資料儲存到磁碟或者用於網路傳輸時,使用utf-8會節省更
多的流量和硬碟空間。UTF8編碼後的大小是不一定,例如一個英文字母"a" 和一個漢字 "好",編碼後佔用的空間大小就不樣了,前者是一個位元組,後者是三個位元組!

總結: 通過上面的講解,現在在回過頭去,體會一下最前面的那張程式編解碼執行圖,大家能看懂它的意思了嗎.