這個着重第一個說,是由於要想了解一個知識就應該去了解他的歷史,每一個階段發生了什麼問題,以及如何解決,和出現的目的。php
編碼的發展大概分爲三個階段,出生(ASCII),編碼本地化(如GBK,BIG5),國際化(UNICODE)html
ASCII是基於拉丁字母的一套電腦編碼系統,主要用於顯示現代英語和其餘西歐語言。山姆大叔於50年代後期(967年定案)搞出來的西文字符編碼標準,使用指定的7 位或8 位二進制數組合來表示128 或256 種可能的字符。其中使用7 位二進制數來表示全部的大寫和小寫字母,數字0 到九、標點符號, 以及在美式英語中使用的特殊控制字符。記得哦,重點是使用了7位二進制來表示,也就是說用了128個字符,只能支持英語java
山姆大叔搞出來的編碼字符集只能支持英語,但是世界上還有其餘各類語言,好比漢語,法語,日韓等,更要命的是,ASCII是一個字節來標識的,最多能表示256個字符,而其餘語言遠不止這些,怎麼辦,以後,各國根據ASCII來本地化本身的字符集,好比咱們常見的,GBK(簡體中文),BIG5(繁體中文),iso8895系列(包含1-16)等,此時很好的解決本地化問題,讓計算機可以顯示本地文字。但這個問題是各自爲政,玩本身的。程序員
本地化以後,世界上存在了各類編碼,此時延伸出來了另一個問題,好比遊戲,日本作的遊戲本國玩沒問題,一到了中國就變成一堆亂碼了,這還好說,大不了寫個程序作轉碼,可是郵件,各國之間發送和接受都是一堆亂碼,這些只能本國使用沒法世界流通了,咱們現在的互聯網就沒法聯通世界了。數據庫
此時國際標準組織建議你們來搞一套萬國碼,不管到哪裏都能使用的編碼字符集,因而Unicode就誕生了,國際組織制定了 UNICODE 字符集,爲各類語言中的每個字符設定了統一而且惟一的數字編號,以知足跨語言、跨平臺進行文本轉換、處理的要求。具體的unicode介紹本身wiki腦補下,不作具體介紹了,以後的互聯網,更是讓UTF-8一會兒就火起來,爲什麼吶,他既省空間又靈活多變,讓程序員門愛不釋手。網絡
上面介紹的編碼歷史,裏面所說的編碼主要是字符集,沒有着重強調字符編碼,切記這兩個是有着重區別的,如今網上一堆介紹編碼的連這個基本的概念都搞混了,讓人誤解頗深。編輯器
字庫表決定了整個字符集可以展示表示的全部字符的範圍,你能夠這麼理解,字庫就是那些存在數據庫的二進制數據,他只爲計算機顯示錶現用,具體要用那個字,是字符集說了算。字體
字符集 雖然叫字符集,但並不存真是的字體數據,是一個抽象的概念,就是全部字符的集合,他相似是每一個字符的位置索引,方便調用方快速定位這個數據。編碼
常見字符集有:Unicode字符集、ASCII字符集、ISO 8859字符集、GB2312字符集、BIG5字符集、GB18030字符集等url
Unicode 是爲了解決傳統的字符編碼方案的侷限而產生的, 也稱爲萬國碼,是一個很大的字符集合,如今的規模能夠容納100多萬個符號,區間0x0000 至 0x10FFFF,目前字節長度爲2或者4個字節
字符編碼(encoding)和字符集不一樣,字符編碼是將編碼字符集和實際存儲數值之間的轉換關係,同一個字符集能夠有多種編碼方式,好比如Unicode可依不一樣須要以UTF-八、UTF-1六、UTF-32等方式編碼,也就是說UTF-八、UTF-1六、UTF-32是字符編碼,他們歸屬於Unicode字符集。爲什麼要對字符集再作一次編碼,目的是節省空間。好比unicode,一個字符佔用2或者4個字節,但是ascii一個字符一個字節就夠了,不必也佔用2個字節,用UTF-8的話,能夠節省了一半以上的空間,這對於傳送速度或者磁盤空間方面是比較節省方案了。
咱們經常使用的UTF-8是字符編碼,歸屬於Unicode字符集,是Unicode的一種編碼方式。
GBK即「國標」,是在GB2312-80標準基礎上的內碼擴展規範,使用了雙字節編碼方案,其編碼範圍從8140至FEFE(剔除xx7F),共23940個碼位,共收錄了21003個漢字,關鍵是每一個字符都是雙字節,包括英文字符。
UTF-8是當今接受度最廣的字符集編碼,是一種針對Unicode的可變長度字符編碼,主要特色是一種可變長度的編碼方式,不在是unicode那種刻板的一個字符佔用固定空間,目前字節是1-6個(現在最多用4個)。好比文件裏有ascii字符的話,一個字節空間,有中文的話就3個空間。
UTF-8編碼最小編碼單位爲一個字節。一個字節的前1-3個bit爲描述性部分,後面爲實際序號部分。對於單字節的符號,字節的第一位設爲0,後面7位爲這個符號的unicode碼。對於n字節的符號(n>1),第一個字節的前n位都設爲1,第n+1位設爲0,後面字節的前兩位一概設爲10。
好比,從Unicode到UTF-8的編碼方式以下:
Unicode編碼(十六進制) |
UTF-8 字節流(二進制) |
00000000 - 0000007F |
0xxxxxxx |
00000080 - 000007FF |
110xxxxx 10xxxxxx |
00000800 - 0000FFFF |
1110xxxx 10xxxxxx 10xxxxxx |
00010000 - 001FFFFF |
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
00200000 - 03FFFFFF | 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
04000000 - 7FFFFFFF | 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
經過上面的編碼中咱們能夠看到:
也就是說若是一個字節的第一位是0,則這個字節單獨就是一個字符,所以對於英語字母,UTF-8編碼和ASCII碼是相同的;若是第一位是1,則連續有多少個1,就表示當前字符佔用多少個字節。
亂碼的造成有常見有兩種緣由,第一種是是編碼和解碼時用了不一樣或者不兼容的字符集而形成的;第二種是字節序的存儲方式不一樣而造成 ,好比PowerPC系列採用big endian方式存儲數據,而x86系列則採用little endian方式存儲數據,兩種機器文件交互就會出現亂碼。咱們分開來說,通常來講第一種出現的更多些。
字符 |
UTF-8編碼後16進制 |
漢 |
E6B189 |
字 |
E5AD97 |
GBK(16進制數值) | 字符 |
E6B1 | 奼 |
89E5 | 夊 |
AD97 | 瓧 |
解決亂碼主要是要從亂碼字符中反解出原來的正確文字,咱們分三步來來反推,解碼,識別,編碼。
byte[] encodingBytes = "奼夊瓧".getBytes("gbk");
public static final String byte2hex(byte b[]) { if (b == null) { throw new IllegalArgumentException( "Argument b ( byte array ) is null! "); } String hs = ""; String stmp = ""; for (int n = 0; n < b.length; n++) { stmp = Integer.toHexString(b[n] & 0xff); if (stmp.length() == 1) { hs = hs + "0" + stmp; } else { hs = hs + stmp; } } return hs.toUpperCase(); }
咱們把數據打印出來看下對應的16進制是什麼,E6B189E5AD97,E開頭的,經過以前的瞭解咱們大概知道發送方是採用UTF-8編碼,咱們嘗試下。
String codingData = new String(encodingBytes, Charset.forName("utf8")); System.out.println(codingData);
知道了編碼方式,咱們採用utf8進行編碼,看下原始數據,發現是「漢字」。
以上的是通常反推亂碼的方式和原理,其實自己很簡單,程序編解碼(2行代碼左右),或者拿着亂碼數據用編輯器uedit來切換編碼格式,都會很快反推出來。
引用:
http://blog.163.com/wangxuefan1220@126/blog/static/8821147201231331838952/
http://www.joelonsoftware.com/articles/Unicode.html
http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
http://blog.jobbole.com/84903/