1. 程式人生 > >淺談編碼集

淺談編碼集

type 完全 繁體字 橫線 官方 兼容 進制數 裏的 說我

一、名詞解釋

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

字符集:所謂字符編碼就是一個系統支持的所有抽象字符的集合,也就是說我們平常使用的文字,標點符號,圖形符號等都是字符集。

我們知道,計算機無法識別我們平時說的文字,只能識別二進制的數字系統,那麽就需要一套規則,將我們所說的字符轉換為數字系統,那麽這種操作,就是字符編碼。官方解釋如下:

字符編碼:將符號轉換為計算機可以接受的數字系統的規則。

理解了以上兩個概念,接下來的兩個概念就很容易理解了:

編碼:按照某種字符編碼將字符存儲在計算機中

解碼:將存儲在計算機中的二進制數解析出來

二、編碼集的發展史
ASCII編碼

ASCII使用8個bit表示一個字節,共有128個字符,

編碼範圍是0-127

眾所周知,計算機最先使用是在美國,ASCII編碼作為美國“元老級編碼”,被稱作“美國信息交換標準代碼”。

ASCII字符集主要包括兩部分,分別是:控制字符可顯示字符

控制字符:主要包括回車鍵、退格鍵、換行鍵等

可顯示字符:主要包括英文大小寫、阿拉伯數字和西文符號等

ISO-8859-1

在ASCII推出後,計算機也在迅猛發展,當西歐等國家也開始使用計算機時,發現,ASCII雖然在顯示英文方面做的很好,但是西歐國家的一些語言是無法顯示的,所以,他們就對ASCII進行拓展。他們將新的符號填入128-255,將編碼範圍從0-127拓展成為0-255,共256個字符。相較於ASCII,ISO-8859-1加入了西歐語言,同時還加入了橫線、豎線、交叉等符號。

GB系列的誕生

隨著計算機的普及,我們國家也開始使用計算機,但是如何顯示中文就成了一個大難題。

使用ASCII對中文進行編碼和解碼(以JAVA代碼為例):

String chineseStr = "哈哈";
//編碼
byte[] ascii = chineseStr.getBytes("ASCII");
//解碼
String asciiStr = new String(ascii,"ASCII");
System.out.println("使用ASCII編碼顯示中文"+asciiStr);

運行結果不出所料,中文變成了“??”,可見,

ASCII無法滿足我們對中文的需要,可是已經沒有可用的字節給我們用了,這可難不倒我們中國的勞動人民。那我們是怎麽做的呢?

我們把127號之後的所有符號(即ASCIIM拓展碼)取消掉,並且規定:一個小於127的字符,意義與原來相同,兩個大於127的字符連在一起表示一個漢字

就這樣,我們組合出7000多個常用簡體漢字,還包括數字符號、羅馬希臘字母、日文假名們。這就是我們常說的GB2312編碼

但是,中國的漢字實在是太多了,還有繁體字也沒有編進GB2312中,同時,在GB2312推出後又增加了許多簡體字,這些漢字還是無法顯示。

還好,GB2312並沒有把所有的碼位都用完。但是當我們把剩下的碼位填滿之後,發現還是有很多漢字無法編入,我們天朝的專家又說了,不再要求第二個字節也是127號以後的字符,只要第一個字節大於127就表示一個漢字的開始

這次的編碼,增加了進20000個漢字(包括繁體字)和符號,我們把這中編碼成為GBK編碼集。GBK編碼包括了GB2312的所有內容。

那麽問題來了,之前使用ASCII編碼的軟件可以在GB系列環境下繼續運行嗎?

使用ASCII編碼,使用GBK解碼:

String englishStr = "hello world";
//編碼
byte[] ascii = englishStr.getBytes("ASCII");
//解碼
String gbkStr = new String(ascii,"GBK");
System.out.println("使用ASCII編碼,使用GBK解碼:"+gbkStr);

運行結果:沒有發生亂碼

使用ASCII編碼,使用GBK解碼:hello world

可見,GB系列解決了顯示中文的問題,同時還保證了ASCII遺留軟件還可以繼續運行

unicode編碼

在中國推出自己的GB系列編碼的時候,其他國家也都推出了屬於自己語言的編碼集,各個國家的軟件無法做到互通,因為當編碼集不同時,亂碼問題就會出現。

終於有一個叫ISO的國際組織實在是看不下去了,他們推出了一個新的編碼:unicode編碼

unicode編碼是一個很大的編碼,它廢除了所有地區性的編碼方案,重新規定了編碼方式,包括了地球上所有文化、所有字母和符號。

它要求:

ASCII裏的字符保持原編碼不變,只是將其長度由原來的8位拓展成了16位;

其他文化語言的字符則全部統一重新編碼,同樣也是16位

我們來看一下unicode顯示各國語言的效果如何:

String testStr = "abc哈哈??あはは";
//編碼
byte[] unicode = testStr.getBytes("unicode");
//解碼
String unicodeStr = new String(unicode,"unicode");
System.out.println("使用UNICODE編碼和解碼:"+unicodeStr);

運行結果:未發生亂碼

使用UNICODE編碼和解碼:abc哈哈??あはは

我們剛才說到,unicode在處理英文時,將其長度拓展為16位,那麽也就是說,在處理同等長度的純英文字符串時,Unicode要使用兩倍的空間來存儲,為了能更直觀地表達意思,使用代碼來向大家說明:

String englishStr = "helloworld" ;
//使用ASCII編碼
byte[] ascii = englishStr.getBytes("ASCII");
//使用UNICODE編碼
byte[] unicode = englishStr.getBytes("unicode");
System.out.println("使用ASCII編碼後字節數組長度:"+ascii.length);
System.out.println("使用UNICODE編碼後字節數組長度:"+unicode.length);

運行結果:

使用ASCII編碼後字節數組長度:10
使用UNICODE編碼後字節數組長度:22

果然UNICODE的字節數組長度確實是ASCII的兩倍。

在這邊稍作解釋:為什麽UNICODE的字節數組的長度是22而不是20.

這是因為,UNICODE屬於多字節編碼,在存儲多字節時,cpu有兩種存儲方式,分別是大端模式和小端模式。例如,“漢”字的UNICODE編碼為6c49,在存儲6c49時,CPU要判斷是要將6c存在前面還是把49存在前面,如果是大端模式,那麽將先存6c,再存49,如果是小端模式,則先存49再存6c。unicode多出的兩位其實就是指定使用哪種存儲模式,但是在編碼過程中是毫無意義的,也就是說Unicode確實要比ASCII花費兩倍的空間來存儲純英文文本。

現在我們來總結一下unicode的優缺點:

首先unicode在避免亂碼上面功不可沒,java底層的編碼集就是使用的UNICODE,但同時,它在存儲純英文文本時要比ASCII花費兩倍的存儲空間,而且,它不與任何一種編碼集兼容

UTF-8

在很長一段時間內,unicode一直沒有得到廣泛應用,直到互聯網的出現,為了解決Unicode的傳輸問題,出現了一系列的新的編碼:UTF系列

其中utf-8編碼是在互聯網上使用最廣的一種UNICODE的實現方式。

utf-8最大的特點就是:它是一種可變長度的編碼

技術分享圖片

utf-8對於不同範圍的unicode編碼,都有不同的長度來表示,分別使用1-4個字節表示一個符號

對於單字節的符號,字節的第一位設為0,後面7位為這個符號的unicode碼。因此對於英語字母,UTF-8編碼和ASCII碼是相同的

對於N字節的符號,第一個字節前N位都設為1,第N+1位設為0,後面的兩個字節一律設為10,剩下沒有提及的二進制全部為這個符號的Unicode碼。

這樣說是有點抽象,我們來舉一個栗子詳細說明:

以漢字“嚴”為例,嚴的unicode碼為4E25,轉成二進制就是100111000100101,4E25處於00000800-0000FFFF中,所以嚴的utf-8的編碼應為3個字節。接下來,將嚴的unicode二進制表示從最後一位開始依次從後向前填入格式中的X,多出的位補0,最終得到嚴的utf-8的表示:11100100 10111000 10100101,轉為十六進制就是E4B8A5。

最後再來總結一下utf-8的優缺點:

優點:對於單字節編碼,utf-8與ASCII是一樣的,可以說utf-8就是ASCII的超集,所以大量只支持AASCII的遺留軟件可以在UTF-8環境下繼續工作;

對於純英文文本,相較於UNICODE,utf-8節約了一半的存儲空間

缺點:比如剛才的栗子,“嚴”的unicode只需要兩個字節,但是utf-8卻需要3個字節,可見,在存儲中文時,要花費1.5-2倍的空間

三、兩種亂碼情況

說了這麽多,我們只是為了要避免一個問題,那就是亂碼。造成亂碼的原因有許多種,在這裏只先向大家介紹兩種:

第一種:中文成了看不懂的字符

String chineseStr = "哈哈";
//編碼
byte[] gbks = chineseStr.getBytes("GBK");
//解碼
String gbksStr = new String(gbks,"ISO-8859-1");
System.out.println("使用GBK進行編碼,使用ISO-8859-1進行解碼"+gbksStr );

運行結果:

使用GBK進行編碼,使用ISO-8859-1進行解碼1t1t

運行結果顯示,中文變成了我們看不懂的字符,那麽,這種情況往往是由於,編碼和解碼使用了兩種不兼容的編碼集,導致中文變成了其他語言符號。

底層運行過程如下:

技術分享圖片

字符串轉為字符數組,之後字符數組通過GBK轉為字節數組,由於ISO和GBK的編碼方式完全不同,所以將字節數組轉為字符數組時“曲解”了語意。

第二種:中文變成了“?”

String chineseStr = "哈哈";
//編碼
byte[] iso = chineseStr.getBytes("ISO-8859-1");
//解碼
String isoStr = new String(iso,"ISO-8859-1");
System.out.println("使用ISO-8859-1進行編碼,使用ISO-8859-1進行解碼"+isoStr);

運行結果:

使用ISO-8859-1進行編碼,使用ISO-8859-1進行解碼??

運行結果顯示,中文統一變成了“?”。那這又是為什麽呢?編碼和解碼的過程中使用的相同的編碼集怎麽還會亂碼呢?那是因為中文需要多字節編碼,而ASCII是單字節編碼,由於ASCII無法識別多字節,所以進行了過濾,將所有的中文都過濾成了“?”,底層執行原理如下:

技術分享圖片

淺談編碼集