告別亂碼,針對GBK、UTF-8兩種編碼的智能URL解碼器的java實現(轉)

效果圖html


 

 

字符java


字符是早於計算機而存在,從人類有文明那時起,人們就用一個個符號表明世間萬象。如ABC,如「1、2、三」。正則表達式

字符集算法


字符集是全部字符的集合。工具

 

XXX字符集網站


給字符集中的每個字符套上一個序號後的字符集。常見的XXX字符集有ASCLL字符集、Unicode字符集等等,不一樣種字符集爲每一個字符編的序號不一樣,包含的字符數量也不一樣。編碼

GBK、UTF-8spa


  GBK、UTF-8是一種編碼編碼格式。固然,你也能夠說unicode是一種編碼格式,由於它的的確確爲每一個字符編了一個碼,沒錯,但是unicode的編碼徹底沒有規律,最多隻能把其當映射表用。.net

  咱們知道,計算機只能識別1和0,假如計算機存儲中文字符「字」在硬盤,確定是存儲一串二進制串。3d

  那麼問題來了,中文字符【字】在unicode字符集中的序號是23383,那麼直接把23383轉化成2進製爲101101101010111,而後存儲在計算機裏面,等須要的時候把101101101010111串拿出來,轉成23383,再根據unicode映射表,找到中文字符【字】不就好嗎?

  答案是否認的,若是是這樣的話,那計算機怎麼知道多少個一、0才表明一個字符呢?因此咱們須要一種編碼格式,把23383編碼成有規律的一、0串,以便計算機讀取。

  而GBK和UTF-8即是兩種不一樣的有規則的編碼格式。

  例如:以UTF-8爲例子,假如咱們所在的環境使用的是unicode字符集,那麼「字」在unicode字符集中的序號是23383,轉成二進制是101101101010111,使用UTF-8爲其編碼,以一種特定的算法(下面會具體講這種算法),把101101101010111轉化成11100101 10101101 10010111三個字節的二進制串,再存儲到硬盤中,計算機在讀取的時候,假如咱們指定了讓計算機以UTF-8編碼格式讀取並解碼,計算機就會把這三個字節拿出來,倒着轉回去,就能獲得【字】這個中文字符了。

亂碼的根源:

假如咱們存儲的時候,使用GBK編碼格式編碼,存儲到硬盤,而從硬盤讀取出來後,在「倒着轉回去」這個步驟卻使用UTF-8編碼格式轉回去,算法不一樣,那麼就可能出現亂碼。

如何避免亂碼:

以什麼編碼格式存儲,就用什麼編碼格式解

可是,假如用戶A使用GBK編碼對「字」進行編碼,而用戶B並不知情,也沒A的聯繫方式,跟A約定不了,沒法得知硬盤中的數據是以什麼編碼格式編碼的,怎麼辦呢?

解決亂碼的思路:

一、隨意使用一種編碼格式解碼,看解碼後的字符串是否亂碼,若是是亂碼,就用另外一種編碼格式解碼。但該方法可能誤判。

二、UTF-8編碼格式有必定的規律,咱們能夠經過正則表達式來驗證是不是通過UTF-8編碼後的。

 

JAVA自帶檢測亂碼


 

1  boolean b = java.nio.charset.Charset.forName("GBK").newEncoder().canEncode(str);

 

當開始接觸這種方法時,原覺得java能幫咱們判斷亂碼,就能夠高枕無憂了,後來發現,該方法的成功率並不高。

但咱們能夠先用此方法作第一步檢測,若是判斷不出來,再使用第2種方法。

 

UTF-8的編碼規律


UTF-8形式的二進制,當一個字節時,兩個字節時,3、4、5、六個字節時,都有必定的格式:

1字節 0xxxxxxx
2字節 110xxxxx 10xxxxxx
3字節 1110xxxx 10xxxxxx 10xxxxxx
4字節 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
5字節 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
6字節 111111x0 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

很明顯,字節數不同的話,第一個字節是不一樣的,因此第一個字節可用用來表示該字符究竟佔用了多少個字節。

當計算機讀取到以0xxxxxxx開頭的字節,那麼就表明這個字節獨自就已經表示某個字符了,計算機將把這個字節單獨拿出來解碼。

當計算機讀取到以110xxxxx開頭的字節,那麼就表明兩個字節才能表示某個字符,計算機就把這個字節以及它後面的一個字節拿出來,表明一個字符進行解碼。

……

而除了第一個字節外,後面的字節都是統一的10xxxxxx格式。

有了上面的有規則的格式,按到理咱們就可使用正則表達式來檢測一個二進制串是不是UTF-8編碼後的串,但代碼中操做二進制並不方便,結合URL爲16進制的特色,咱們能夠用正則表達式判斷16進制的串。

 

如何構造正則表達式


 

咱們先看看這種編碼格式前一個字節的範圍:

  二進制 十六進制
1字節 00000000~01111111 00~7f  
2字節 11000000~11011111 c0~df  
3字節 11100000~11101111   e0~df
4字節 11110000~11110111 f0~f7
5字節 11111000~11111011 f8~fb
6字節 11111100~11111101 fc~fd

以上的範圍可用計算機自行驗證:

後面格式相同的字節10xxxxxx的範圍:

10000000~10111111   80~bf

按照這種格式,UTF-8編碼格式最多可用用來表示一個1+5*6=31位的二進制串,共使用6個字節。

按照這種規律,咱們先練一下手,嘗試把「字」轉化爲UTF-8的十六進制:

java使用的字符集是unicode的,因此咱們以unicode爲例子。

一、找出「字」在unicdoe字符集中的序號:

?
1
2
3
public static void main(String[] args) {
System.out.println(( int ) '字' );
}

結果爲:23383

2、把23383轉化二進制:  

23383   101101101010111

可用看出,二進制共15位,按照UTF-8的編碼格式,得用3個字節來表示。

咱們把101101101010111從後往前分紅三組:101,101101,010111

填充到3字節的UTF-8編碼格式中爲:

1110xxxx 10xxxxxx 10xxxxxx

11100101 10101101 10010111

3、使用計算器把二進制轉化爲16進製爲:

OxE5 OxAD Ox97

4、使用網上的工具驗證一下,結果吻合,說明這種規律是正確的。

 

 

上面已經介紹了UTF-8的規律,那麼咱們藉助強大的正則表達式,就能夠判斷一個URL串是通過什麼編碼格式編碼的了。

先把上面的表複製下來容易觀察:

  二進制 十六進制
1字節 00000000~01111111 00~7f  
2字節 11000000~11011111 c0~df  
3字節 11100000~11101111   e0~df
4字節 11110000~11110111 f0~f7
5字節 11111000~11111011 f8~fb
6字節 11111100~11111101 fc~fd

1字節時:[\\x00-\\x7f]---------------------------------1

2字節時:[\\xc0-\\xdf][\\x80-\\xbf]-------------------2

3字節時:[\\xe0-\\xef][\\x80-\\xbf]{2}--------------3

4字節時:[\\xf0-\\xf7][\\x80-\\xbf]{3}--------------4

5字節時:[\\xf8-\\xfb][\\x80-\\xbf]{4}--------------5

6字節時:[\\xfc-\\xfd][\\x80-\\xbf]{5}--------------6

使用或組合在一塊兒就是:^([\\x00-\\x7f]|[\\xc0-\\xdf][\\x80-\\xbf]|[\\xe0-\\xef][\\x80-\\xbf]{2}|[\\xf0-\\xf7][\\x80-\\xbf]{3}|[\\xf8-\\xfb][\\x80-\\xbf]{4}|[\\xfc-\\xfd][\\x80-\\xbf]{5})+$

判斷過程是這樣子的:例如【字】通過UTF-8編碼後,爲:%e5 %ad %97,共3個字節,符合第3字節的狀況,第一個字節e5在[\\xe0-\\xef]範圍內,後兩個字節ad和97都在[\\x80-\\xbf]範圍內。

因此咱們能夠說這個字符是通過UTF-8編碼的。咱們就可使用UTF-8編碼格式對其進行解碼了。

java代碼以下:

複製代碼
1     protected static final Pattern utf8Pattern = Pattern.compile("^([\\x00-\\x7f]|[\\xc0-\\xdf][\\x80-\\xbf]|[\\xe0-\\xef][\\x80-\\xbf]{2}|[\\xf0-\\xf7][\\x80-\\xbf]{3}|[\\xf8-\\xfb][\\x80-\\xbf]{4}|[\\xfc-\\xfd][\\x80-\\xbf]{5})+$");
2                 Matcher matcher = utf8Pattern.matcher(pureValue);
3                 if (matcher.matches()) {
4                     return "UTF-8";
5                 } else {
6                     return "GBK";
7                 }
複製代碼

 

缺陷


  使用上面的方法,貌似沒什麼問題,不過GBK編碼後是以兩個兩個字節呈現的,而UTF-8也有兩個字節的狀況,因此當一個字符經GBK編碼後,轉化爲16進制,而恰好這個16進制的範圍落入UTF-8的兩個字節的範圍,那麼就會被誤判成UTF-8,從而致使解碼錯誤。那真的有可能會出現這種狀況嗎?

答案是會的,咱們查看下GBK簡體中文編碼表

發現有一部分範圍落入了UTF-8的二進制範圍了。

從:

一直到:

即UTF-8十六進制中兩個字節的範圍[\\xc0-\\xdf][\\x80-\\xbf],GBK都有。

例如上面表的第二個中文【愧】,愧的GBK十六進制是C0 A0,那麼徹底符合UTF-8正則表達式中二字節的[\\xc0-\\xdf][\\x80-\\xbf]這個判斷,因此會被誤認爲是UTF-8編碼。

注:該缺陷第一次看,是在下方「參考"的第一篇博客裏,嘗試了一下,的確有缺陷。

 

嘗試修復缺陷


根據下面"參考"的第一篇博客,修復的思路是把重複的區域都認爲是GBK編碼。

咱們截取正則表達式的前兩種狀況(一字節、二字節的狀況)來排除:^([\\x01-\\x7f]|[\\xc0-\\xdf][\\x80-\\xbf])+$

假如某個16進制串match該正則表達式,就認爲是GBK編碼的。

修改後的代碼爲:

複製代碼
 1     protected static final Pattern utf8Pattern = Pattern.compile("^([\\x01-\\x7f]|[\\xc0-\\xdf][\\x80-\\xbf]|[\\xe0-\\xef][\\x80-\\xbf]{2}|[\\xf0-\\xf7][\\x80-\\xbf]{3}|[\\xf8-\\xfb][\\x80-\\xbf]{4}|[\\xfc-\\xfd][\\x80-\\xbf]{5})+$");
 2     protected static final Pattern publicPattern = Pattern.compile("^([\\x01-\\x7f]|[\\xc0-\\xdf][\\x80-\\xbf])+$");
 3 Matcher publicMatcher = publicPattern.matcher(str);
 4                 if(publicMatcher.matches()) {
 5                     return "GBK";
 6                 }
 7                 
 8                 Matcher matcher = utf8Pattern.matcher(str);
 9                 if (matcher.matches()) {
10                     return "UTF-8";
11                 } else {
12                     return "GBK";
13                 }
複製代碼

 

又一缺陷


但這樣一來,本來是一個字節或兩字節,且是UTF-8編碼的,就會被誤判爲GBK。。。

可是,這總比被誤判成UTF-8好,由於咱們查看Unicode編碼表

能夠發現,第一個中文是「一」,轉化爲UTF-8的話已經排到3個字節去了,因此2個字節內不會出現中文。

可是GBK中,中文是兩個字節的。

因此,採用上面的修復缺陷的方法,能夠保證中文不會亂碼。對於某些網站,只需保證中文不會亂碼便可,好比說國內的各類中文購物網站。這些網站中商品的標題通常都是中文的,用戶通常以中文搜索,咱們儘量保證中文不亂碼便可。

因此,該技術仍是有必定用處的。

 

參考


一、http://www.cnblogs.com/chengmo/archive/2011/02/19/1958657.html

二、http://www.cnblogs.com/chengmo/archive/2010/10/30/1864004.html

三、unicode編碼表

四、GBK簡體中文表

http://www.cnblogs.com/xiaoMzjm/p/4648175.html

相關文章
相關標籤/搜索