1、名詞解釋java
在聊編碼集以前,咱們先來了解一些名詞解釋:數組
字符集:所謂字符編碼就是一個系統支持的全部抽象字符的集合,也就是說咱們日常使用的文字,標點符號,圖形符號等都是字符集。ide
咱們知道,計算機沒法識別咱們平時說的文字,只能識別二進制的數字系統,那麼就須要一套規則,將咱們所說的字符轉換爲數字系統,那麼這種操做,就是字符編碼。官方解釋以下:編碼
字符編碼:將符號轉換爲計算機能夠接受的數字系統的規則。spa
理解了以上兩個概念,接下來的兩個概念就很容易理解了:
code
編碼:按照某種字符編碼將字符存儲在計算機中。blog
解碼:將存儲在計算機中的二進制數解析出來。utf-8
2、編碼集的發展史
ASCII編碼ci
ASCII使用8個bit表示一個字節,共有128個字符,編碼範圍是0-127。unicode
衆所周知,計算機最早使用是在美國,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倍的空間。
3、兩種亂碼狀況
說了這麼多,咱們只是爲了要避免一個問題,那就是亂碼。形成亂碼的緣由有許多種,在這裏只先向你們介紹兩種:
第一種:中文成了看不懂的字符
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進行解碼¹þ¹þ
運行結果顯示,中文變成了咱們看不懂的字符,那麼,這種狀況每每是因爲,編碼和解碼使用了兩種不兼容的編碼集,致使中文變成了其餘語言符號。
底層運行過程以下:
字符串轉爲字符數組,以後字符數組經過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沒法識別多字節,因此進行了過濾,將全部的中文都過濾成了「?」,底層執行原理以下: