一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|JAVA教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|JavaScript|易語言|

服務器之家 - 編程語言 - JAVA教程 - ANSI,Unicode,BMP,UTF等編碼概念實例講解

ANSI,Unicode,BMP,UTF等編碼概念實例講解

2021-03-05 14:34Coder君 JAVA教程

這篇文章主要介紹了ANSI,Unicode,BMP,UTF等編碼概念實例講解,具有一定借鑒價值,需要的朋友可以參考下。

一、前言

其實從開始寫java代碼以來,我遇到過無數次亂碼與轉碼問題,比如從文本文件讀入到string出現亂碼,servlet中獲取http請求參數出現亂碼,jdbc查詢到的數據亂碼等等,這些問題很常見,遇到的時候隨手搜一下都可以順利解決,所以沒有深入的去了解。

直到前兩天同學與我談起一個java源文件的編碼問題(這問題在最后一個實例分析),從這個問題入手拉扯出了一連串的問題,然后我們一邊查資料一邊討論,直到深夜,終于在一篇博客中找到了關鍵性線索,解決了所有的疑惑,以前沒有理解的語句都能解釋清楚了。因此我決定用這篇隨筆,記錄我對一些編碼問題的理解以及實驗的結果。

下面有些概念是我自己結合實際的理解,如果有誤,請一定不吝指正。

二、概念總結

早期,互聯網還沒有發展起來,計算機僅用于處理一些本地的資料,所以很多國家和地區針對本土的語言設計了編碼方案,這種與區域相關的編碼統稱為ansi編碼(因為都是對ansi-ascii碼的擴展)。但是他們沒有事先商量好怎么相互兼容,而是自己搞自己的,這樣就埋下了編碼沖突的禍根,比如大陸使用的gb2312編碼與臺灣使用的big5編碼就有沖突,同樣的兩個字節,在兩種編碼方案里表示的是不同的字符,隨著互聯網的興起,一個文檔里經常會包含多種語言,計算機在顯示的時候就遇到麻煩了,因為它不知道這兩個字節到底屬于哪種編碼。

這樣的問題在世界上普遍存在,因此重新定義一個通用的字符集,為世界上所有字符進行統一編號的呼聲不斷高漲。

由此unicode碼應運而生,它為世界上所有字符進行了統一編號,由于它可以唯一標識一個字符,所以字體也只需要針對unicode碼進行設計就行了。但unicode標準定義的是一個字符集,而沒有規定編碼方案,也就是說它僅僅定義了一個個抽象的數字與其對應的字符,而沒有規定具體怎么存儲一串unicode數字,真正規定怎么存儲的是utf-8、utf-16、utf-32等方案,所以帶有utf開頭的編碼,都是可以直接通過計算和unicode數值(codepoint,代碼點)進行轉換的。顧名思義,utf-8就是8位長度為基本單位編碼,它是變長編碼,用1~6個字節來編碼一個字符(因為受unicode范圍的約束,所以實際最大只有4字節);utf-16是16位為基本單位編碼,也是變長編碼,要么2個字節要么4個字節;utf-32則是定長的,固定4字節存儲一個unicode數。

其實我以前一直對unicode有點誤解,在我的印象中unicode碼最大只能到0xffff,也就是最多只能表示2^16個字符,在仔細看了維基百科之后才明白,早期的ucs-2編碼方案確實是這樣,ucs-2固定使用兩個字節來編碼一個字符,因此它只能編碼bmp(基本多語言平面,即0x0000-0xffff,包含了世界上最常用的字符)范圍內的字符。為了要編碼unicode大于0xffff的字符,人們對ucs-2編碼進行了拓展,創造了utf-16編碼,它是變長的,在bmp范圍內,utf-16與ucs-2完全一致,而bmp之外utf-16則使用4個字節來存儲。

為了方便下面的描述,先交代一下代碼單元(codeunit)的概念,某種編碼的基本組成單位就叫代碼單元,比如utf-8的代碼單元為1個字節,utf-16的代碼單元為2個字節,不好解釋,但是很好理解。

為了兼容各種語言以及更好的跨平臺,javastring保存的就是字符的unicode碼。它以前使用的是ucs-2編碼方案來存儲unicode,后來發現bmp范圍內的字符不夠用了,但是出于內存消耗和兼容性的考慮,并沒有升到ucs-4(即utf-32,固定4字節編碼),而是采用了上面所說的utf-16,char類型可看作其代碼單元。這個做法導致了一些麻煩,如果所有字符都在bmp范圍內還沒事,若有bmp外的字符,就不再是一個代碼單元對應一個字符了,length方法返回的是代碼單元的個數,而不是字符的個數,charat方法返回的自然也是一個代碼單元而不是一個字符,遍歷起來也變得麻煩,雖然提供了一些新的操作方法,總歸還是不方便,而且還不能隨機訪問。

此外,我發現java在編譯的時候還不會處理大于0xffff的unicode字面量,所以如果你敲不出某個非bmp字符來,但是你知道它的unicode碼,得用一個比較笨的方法來讓string存儲它:手動計算出該字符的utf-16編碼(四字節),把前兩個字節和后兩個字節各作為一個unicode數,然后賦值給string,示例代碼如下所示。

?
1
2
3
4
5
6
7
8
9
10
public static void main(string[] args) {
    //string str = "";    //我們想賦值這樣一個字符,假設我輸入法打不出來
    //但我知道它的unicode是0x1d11e
    //string str = "\u1d11e"; //這樣寫不會識別
    //于是通過計算得到其utf-16編碼 d834 dd1e
    string str = "\ud834\udd1e";
    //然后這么寫
    system.out.println(str);
    //成功輸出了""
}

windows系統自帶的記事本可以另存為unicode編碼,實際上指的是utf-16編碼。上面說了,主要使用的字符編碼都在bmp范圍內,而在bmp范圍內,每個字符的utf-16編碼值與對應的unicode數值是相等的,這大概就是微軟把它稱為unicode的原因吧。舉個例子,我在記事本中輸入了”好a“兩個字符,然后另存為unicode big endian(高位優先)編碼,用winhex打開文件,內容如下圖,文件開頭兩個字節被稱為byte order mark(字節順序標記),(fe ff)標識字節序為高位優先,然后(59 7d)正是”好“的unicode碼,(00 61)正是”a“的unicode碼。

ANSI,Unicode,BMP,UTF等編碼概念實例講解

有了unicode碼,也還不能立即解決問題,因為首先世界上已經存在了大量的非unicode標準的編碼數據,我們不可能丟棄它們,其次unicode的編碼往往比ansi編碼更占空間,所以從節約資源的角度來說,ansi編碼還是有存在的必要的。所以需要建立一個轉換機制,使得ansi編碼可以轉換到unicode進行統一處理,也可以把unicode轉換到ansi編碼以適應平臺的要求。

轉換方法說起來比較容易,對于utf系列或者是iso-8859-1這種被兼容的編碼,可以通過計算和unicode數值直接進行轉換(實際可能也是查表),而對于系統遺留下來的ansi編碼,則只能通過查表的方式進行,微軟把這種映射表稱為codepage(代碼頁),并按編碼進行分類編號,比如我們常見的cp936就是gbk的代碼頁,cp65001就是utf-8的代碼頁。下圖是微軟官網查到的gbk->unicode映射表(目測不全),同理還應有反向的unicode->gbk映射表。

ANSI,Unicode,BMP,UTF等編碼概念實例講解

有了代碼頁,就可以很方便的進行各種編碼轉換了,比如從gbk轉換到utf-8,只需要先按照gbk的編碼規則對數據按字符劃分,用每個字符的編碼數據去查gbk代碼頁,得到其unicode數值,再用該unicode去查utf-8的代碼頁(或直接計算),就可以得到對應的utf-8編碼。反過來同理。注意:utf-8是unicode的標準實現,它的代碼頁中包含了所有的unicode取值,所以任意編碼轉換到utf-8,再轉換回去都不會有任何丟失。至此,我們可以得出一個結論就是,要完成編碼轉換工作,最重要的是第一步要成功的轉換到unicode,所以正確選擇字符集(代碼頁)是關鍵。

理解了轉碼丟失問題的本質后,我才突然明白jsp的框架為什么要以iso-8859-1去解碼http請求參數,導致我們獲取中文參數的時候不得不寫這樣的語句:

stringparam=newstring(s.getbytes("iso-8859-1"),"utf-8");

因為jsp框架接收到的是參數編碼的二進制字節流,它不知道這究竟是什么編碼(或者不關心),也就不知道該查哪個代碼頁去轉換到unicode。然后它就選擇了一種絕對不會產生丟失的方案,它假設這是iso-8859-1編碼的數據,然后查iso-8859-1的代碼頁,得到unicode序列,因為iso-8859-1是按字節編碼的,而且不同于ascii的是,它對0~255空間的每一位都進行了編碼,所以任意一個字節都能在它的代碼頁中找到對應的unicode,若再從unicode轉回原始字節流的話也就不會有任何丟失。它這樣做,對于不考慮其他語言的歐美程序員來說,可以直接用jsp框架解碼好的string,而要兼容其他語言的話也只需要轉回原始字節流,再以實際的代碼頁去解碼一下就好。

我對unicode以及字符編碼的相關概念闡述完畢,接下來用java實例來感受一下。

三、實例分析

1.轉換到unicode——string構造方法

string的構造方法就是把各種編碼數據轉換到unicode序列(以utf-16編碼存儲),下面這段測試代碼,用來展示javastring構造方法的應用,實例中都不涉及非bmp字符,所以就不用codepointat那些方法了。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class test {
    public static void main(string[] args) throws ioexception {
        //"你好"的gbk編碼數據
        byte[] gbkdata = {(byte)0xc4, (byte)0xe3, (byte)0xba, (byte)0xc3
    }
    ;
    //"你好"的big5編碼數據
    byte[] big5data = {(byte)0xa7, (byte)0x41, (byte)0xa6, (byte)0x6e
}
;
//構造string,解碼為unicode
string strfromgbk = new string(gbkdata, "gbk");
string strfrombig5 = new string(big5data, "big5");
//分別輸出unicode序列
showunicode(strfromgbk);
showunicode(strfrombig5);
}
public static void showunicode(string str) {
for (int i = 0; i < str.length(); i++) {
    system.out.printf("\\u%x", (int)str.charat(i));
}
system.out.println();
}
}

運行結果如下圖

ANSI,Unicode,BMP,UTF等編碼概念實例講解

可以發現,由于string掌握了unicode碼,要轉換到其它編碼soeasy!

3.以unicode為橋梁,實現編碼互轉

有了上面兩部分的基礎,要實現編碼互轉就很簡單了,只需要把他們聯合使用就可以了。先newstring把原編碼數據轉換為unicode序列,再調用getbytes轉到指定的編碼就ok。

比如一個很簡單的gbk到big5的轉換代碼如下

?
1
2
3
4
5
6
7
8
9
10
11
public static void main(string[] args) throws unsupportedencodingexception {
    //假設這是以字節流方式從文件中讀取到的數據(gbk編碼)
    byte[] gbkdata = {(byte) 0xc4, (byte) 0xe3, (byte) 0xba, (byte) 0xc3
}
;
//轉換到unicode
string tmp = new string(gbkdata, "gbk");
//從unicode轉換到big5編碼
byte[] big5data = tmp.getbytes("big5");
//后續操作……
}

4.編碼丟失問題

上面已經解釋了,jsp框架采用iso-8859-1字符集來解碼的原因。先用一個例子來模擬這個還原過程,代碼如下

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class test {
    public static void main(string[] args) throws unsupportedencodingexception {
        //jsp框架收到6個字節的數據
        byte[] data = {(byte) 0xe4, (byte) 0xbd, (byte) 0xa0, (byte) 0xe5, (byte) 0xa5, (byte) 0xbd
    }
    ;
    //打印原始數據
    showbytes(data);
    //jsp框架假設它是iso-8859-1的編碼,生成一個string對象
    string tmp = new string(data, "iso-8859-1");
    //**************jsp框架部分結束********************
    //開發者拿到后打印它發現是6個歐洲字符,而不是預期的"你好"
    system.out.println(" iso解碼的結果:" + tmp);
    //因此首先要得到原始的6個字節的數據(反查iso-8859-1的代碼頁)
    byte[] utfdata = tmp.getbytes("iso-8859-1");
    //打印還原的數據
    showbytes(utfdata);
    //開發者知道它是utf-8編碼的,因此用utf-8的代碼頁,重新構造string對象
    string result = new string(utfdata, "utf-8");
    //再打印,正確了!
    system.out.println(" utf-8解碼的結果:" + result);
}
public static void showbytes(byte[] data) {
    for (byte b : data)
          system.out.printf("0x%x ", b);
    system.out.println();
}
}

運行結果如下,第一次輸出是不正確的,因為解碼規則不對,也查錯了代碼頁,得到的是錯誤的unicode。然后發現通過錯誤的unicode反查iso-8859-1代碼頁還能完美的還原數據。

ANSI,Unicode,BMP,UTF等編碼概念實例講解

這不是重點,重點如果把“中”換成“中國”,編譯就會成功,運行結果如下圖。另外進一步可發現,中文字符個數為奇數時編譯失敗,偶數時通過。這是為什么呢?下面詳細分析一下。

ANSI,Unicode,BMP,UTF等編碼概念實例講解

因為javastring內部使用的是unicode,所以在編譯的時候,編譯器就會對我們的字符串字面量進行轉碼,從源文件的編碼轉換到unicode(維基百科說用的是與utf-8稍微有點不同的編碼)。編譯的時候我們沒有指定encoding參數,所以編譯器會默認以gbk方式去解碼,對utf-8和gbk有點了解的應該會知道,一般一個中文字符使用utf-8編碼需要3個字節,而gbk只需要2個字節,這就能解釋為什么字符數的奇偶性會影響結果,因為如果2個字符,utf-8編碼占6個字節,以gbk方式來解碼恰好能解碼為3個字符,而如果是1個字符,就會多出一個無法映射的字節,就是圖中問號的地方。

再具體一點的話,源文件中“中國”二字的utf-8編碼是e4b8ade59bbd,編譯器以gbk方式解碼,3個字節對分別查cp936得到3個unicode值,分別是6d93e15e6d57,對應結果圖中的三個奇怪字符。如下圖所示,編譯后這3個unicode在.class文件中實際以類utf-8編碼存儲,運行的時候,jvm中存儲的就是unicode,然而最終輸出時,還是會編碼之后傳遞給終端,這次約定的編碼就是系統區域設置的編碼,所以如果終端編碼設置改了,還是會亂碼。我們這里的e15e在unicode標準中并沒有定義相應的字符,所以在不同平臺不同字體下顯示會有所不同。

ANSI,Unicode,BMP,UTF等編碼概念實例講解

可以想象,如果反過來,源文件以gbk編碼存儲,然后騙編譯器說是utf-8,那基本上是無論輸入多少個中文字符都無法編譯通過了,因為utf-8的編碼很有規律性,隨意組合的字節是不會符合utf-8編碼規則的。

當然,要使編譯器能正確的把編碼轉換到unicode,最直接的方法還是老老實實告訴編譯器源文件的編碼是什么。

四、總結

經過這次收集整理和實驗,了解了很多與編碼相關的概念,也熟悉了編碼轉換的具體過程,這些思想可以推廣到各種編程語言去,實現原理都類似,所以我想以后再遇到這類問題,應該不會再不知所以然了。

以上就是本文關于ansi,unicode,bmp,utf等編碼概念實例講解的全部內容,希望對大家有所幫助。感興趣的朋友可以繼續參閱本站其他相關專題,如有不足之處,歡迎留言指出。感謝朋友們對本站的支持!

原文鏈接:http://www.cnblogs.com/coderjun/p/5117590.html

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 精品欧美一区二区精品久久 | 欧美综合亚洲图片综合区 | 红色一片在线影视 | 国产精品久久久久久久久久久久 | 91东航翘臀女神在线播放 | 国产精品区牛牛影院 | 国产成人无精品久久久久国语 | 精品湿 | 日韩ab| 成人中文字幕在线高清 | 精品欧美男同同性videos | 色婷婷综合久久久 | 91制片| 超级碰碰青草免费视频92 | 99热er| 午夜久久久久久网站 | 日韩一级片在线观看 | 欧美腐剧mm在线观看 | 高清在线免费 | 俺去俺来也www色官网免费的 | 亚洲第五色综合网啪啪 | 四虎comwww最新地址 | 私人影院在线免费观看 | 国产精品成人一区二区 | 成人依依网 | 521色香蕉网在线观看免费 | 欧美激烈精交gif动态图18p | 日本免费一区二区三区a区 日本免费三片在线观看 | 国产福利视频一区二区微拍 | 精品免费久久久久久影院 | 出轨同学会2在线观看 | 日韩高清一区二区 | 成人久久18免费网站入口 | 猫咪av| 国内精品久久久久影院男同志 | 网www天堂资源在线 王淑兰与铁柱全文免费阅读 | 国内精品久久久久影院嫩草 | 美女扒开尿口让男生添 漫画 | 欧美最新在线 | 天堂avav| 日本在线观看www免费 |