最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

 更新時間:2017年12月14日 11:29:05   作者:Coder君  
這篇文章主要介紹了ANSI,Unicode,BMP,UTF等編碼概念實例講解,具有一定借鑒價值,需要的朋友可以參考下。

一、前言

其實從開始寫Java代碼以來,我遇到過無數(shù)次亂碼與轉(zhuǎn)碼問題,比如從文本文件讀入到String出現(xiàn)亂碼,Servlet中獲取HTTP請求參數(shù)出現(xiàn)亂碼,JDBC查詢到的數(shù)據(jù)亂碼等等,這些問題很常見,遇到的時候隨手搜一下都可以順利解決,所以沒有深入的去了解。

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

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

二、概念總結(jié)

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

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

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

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

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

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

此外,我發(fā)現(xiàn)Java在編譯的時候還不會處理大于0xFFFF的Unicode字面量,所以如果你敲不出某個非BMP字符來,但是你知道它的Unicode碼,得用一個比較笨的方法來讓String存儲它:手動計算出該字符的UTF-16編碼(四字節(jié)),把前兩個字節(jié)和后兩個字節(jié)各作為一個Unicode數(shù),然后賦值給String,示例代碼如下所示。

public static void main(String[] args) {
	//String str = "";    //我們想賦值這樣一個字符,假設(shè)我輸入法打不出來
	//但我知道它的Unicode是0x1D11E
	//String str = "\u1D11E"; //這樣寫不會識別
	//于是通過計算得到其UTF-16編碼 D834 DD1E
	String str = "\uD834\uDD1E";
	//然后這么寫
	System.out.println(str);
	//成功輸出了""
}

Windows系統(tǒng)自帶的記事本可以另存為Unicode編碼,實際上指的是UTF-16編碼。上面說了,主要使用的字符編碼都在BMP范圍內(nèi),而在BMP范圍內(nèi),每個字符的UTF-16編碼值與對應(yīng)的Unicode數(shù)值是相等的,這大概就是微軟把它稱為Unicode的原因吧。舉個例子,我在記事本中輸入了”好a“兩個字符,然后另存為Unicode big endian(高位優(yōu)先)編碼,用WinHex打開文件,內(nèi)容如下圖,文件開頭兩個字節(jié)被稱為Byte Order Mark(字節(jié)順序標記),(FE FF)標識字節(jié)序為高位優(yōu)先,然后(59 7D)正是”好“的Unicode碼,(00 61)正是”a“的Unicode碼。

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

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

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

理解了轉(zhuǎn)碼丟失問題的本質(zhì)后,我才突然明白JSP的框架為什么要以ISO-8859-1去解碼HTTP請求參數(shù),導(dǎo)致我們獲取中文參數(shù)的時候不得不寫這樣的語句:

Stringparam=newString(s.getBytes("iso-8859-1"),"UTF-8");

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

我對Unicode以及字符編碼的相關(guān)概念闡述完畢,接下來用Java實例來感受一下。

三、實例分析

1.轉(zhuǎn)換到Unicode——String構(gòu)造方法

String的構(gòu)造方法就是把各種編碼數(shù)據(jù)轉(zhuǎn)換到Unicode序列(以UTF-16編碼存儲),下面這段測試代碼,用來展示JavaString構(gòu)造方法的應(yīng)用,實例中都不涉及非BMP字符,所以就不用codePointAt那些方法了。

public class Test {
	public static void main(String[] args) throws IOException {
		//"你好"的GBK編碼數(shù)據(jù)
		byte[] gbkData = {(byte)0xc4, (byte)0xe3, (byte)0xba, (byte)0xc3
	}
	;
	//"你好"的BIG5編碼數(shù)據(jù)
	byte[] big5Data = {(byte)0xa7, (byte)0x41, (byte)0xa6, (byte)0x6e
}
;
//構(gòu)造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();
}
}

運行結(jié)果如下圖

可以發(fā)現(xiàn),由于String掌握了Unicode碼,要轉(zhuǎn)換到其它編碼soeasy!

3.以Unicode為橋梁,實現(xiàn)編碼互轉(zhuǎn)

有了上面兩部分的基礎(chǔ),要實現(xiàn)編碼互轉(zhuǎn)就很簡單了,只需要把他們聯(lián)合使用就可以了。先newString把原編碼數(shù)據(jù)轉(zhuǎn)換為Unicode序列,再調(diào)用getBytes轉(zhuǎn)到指定的編碼就OK。

比如一個很簡單的GBK到Big5的轉(zhuǎn)換代碼如下

public static void main(String[] args) throws UnsupportedEncodingException {
	//假設(shè)這是以字節(jié)流方式從文件中讀取到的數(shù)據(jù)(GBK編碼)
	byte[] gbkData = {(byte) 0xc4, (byte) 0xe3, (byte) 0xba, (byte) 0xc3
}
;
//轉(zhuǎn)換到Unicode
String tmp = new String(gbkData, "GBK");
//從Unicode轉(zhuǎn)換到Big5編碼
byte[] big5Data = tmp.getBytes("Big5");
//后續(xù)操作……
}

4.編碼丟失問題

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

public class Test {
	public static void main(String[] args) throws UnsupportedEncodingException {
		//JSP框架收到6個字節(jié)的數(shù)據(jù)
		byte[] data = {(byte) 0xe4, (byte) 0xbd, (byte) 0xa0, (byte) 0xe5, (byte) 0xa5, (byte) 0xbd
	}
	;
	//打印原始數(shù)據(jù)
	showBytes(data);
	//JSP框架假設(shè)它是ISO-8859-1的編碼,生成一個String對象
	String tmp = new String(data, "ISO-8859-1");
	//**************JSP框架部分結(jié)束********************
	//開發(fā)者拿到后打印它發(fā)現(xiàn)是6個歐洲字符,而不是預(yù)期的"你好"
	System.out.println(" ISO解碼的結(jié)果:" + tmp);
	//因此首先要得到原始的6個字節(jié)的數(shù)據(jù)(反查ISO-8859-1的代碼頁)
	byte[] utfData = tmp.getBytes("ISO-8859-1");
	//打印還原的數(shù)據(jù)
	showBytes(utfData);
	//開發(fā)者知道它是UTF-8編碼的,因此用UTF-8的代碼頁,重新構(gòu)造String對象
	String result = new String(utfData, "UTF-8");
	//再打印,正確了!
	System.out.println(" UTF-8解碼的結(jié)果:" + result);
}
public static void showBytes(byte[] data) {
	for (byte b : data)
	      System.out.printf("0x%x ", b);
	System.out.println();
}
}

運行結(jié)果如下,第一次輸出是不正確的,因為解碼規(guī)則不對,也查錯了代碼頁,得到的是錯誤的Unicode。然后發(fā)現(xiàn)通過錯誤的Unicode反查ISO-8859-1代碼頁還能完美的還原數(shù)據(jù)。

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

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

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

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

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

四、總結(jié)

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

以上就是本文關(guān)于ANSI,Unicode,BMP,UTF等編碼概念實例講解的全部內(nèi)容,希望對大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站其他相關(guān)專題,如有不足之處,歡迎留言指出。感謝朋友們對本站的支持!

相關(guān)文章

  • Java 實戰(zhàn)項目錘煉之在線購書商城系統(tǒng)的實現(xiàn)流程

    Java 實戰(zhàn)項目錘煉之在線購書商城系統(tǒng)的實現(xiàn)流程

    讀萬卷書不如行萬里路,只學(xué)書上的理論是遠遠不夠的,只有在實戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+jsp+mysql+servlet+ajax實現(xiàn)一個在線購書商城系統(tǒng),大家可以在過程中查缺補漏,提升水平
    2021-11-11
  • 淺談java中的訪問修飾符

    淺談java中的訪問修飾符

    這篇文章介紹了java中的訪問修飾符,有需要的朋友可以參考一下
    2013-10-10
  • idea中使用maven?archetype新建項目時卡住問題解決方案

    idea中使用maven?archetype新建項目時卡住問題解決方案

    這篇文章主要介紹了idea中使用maven?archetype新建項目時卡住,解決本問題的方法,就是在maven的runner加上參數(shù)-DarchetypeCatalog=local就可以了,不需要下載xml文件再放到指定目錄,需要的朋友可以參考下
    2023-08-08
  • Java注解實現(xiàn)動態(tài)數(shù)據(jù)源切換的實例代碼

    Java注解實現(xiàn)動態(tài)數(shù)據(jù)源切換的實例代碼

    本篇文章主要介紹了Java注解實現(xiàn)動態(tài)數(shù)據(jù)源切換的實例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-06-06
  • SpringBoot與Dubbo整合的方式詳解

    SpringBoot與Dubbo整合的方式詳解

    這篇文章主要介紹了SpringBoot與Dubbo整合的方式詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-09-09
  • 一篇文章帶你了解Java中ThreadPool線程池

    一篇文章帶你了解Java中ThreadPool線程池

    線程池可以控制運行的線程數(shù)量,本文就線程池做了詳細的介紹,需要了解的小伙伴可以參考一下
    2021-08-08
  • Java語言ReadWriteLock特性實例測試

    Java語言ReadWriteLock特性實例測試

    這篇文章主要介紹了Java語言ReadWriteLock特性實例測試,分享了相關(guān)代碼示例,小編覺得還是挺不錯的,具有一定借鑒價值,需要的朋友可以參考下
    2018-02-02
  • Java管道流實現(xiàn)線程間通信過程解析

    Java管道流實現(xiàn)線程間通信過程解析

    這篇文章主要介紹了Java管道流實現(xiàn)線程間通信過程解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-03-03
  • MyBatis-Plus allEq()的用法詳解

    MyBatis-Plus allEq()的用法詳解

    這篇文章主要介紹了MyBatis-Plus allEq()的用法詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-12-12
  • Java多線程中的ThreadPoolExecutor使用解析

    Java多線程中的ThreadPoolExecutor使用解析

    這篇文章主要介紹了Java多線程中的ThreadPoolExecutor使用解析,作為線程池的緩沖,當新增線程超過maximumPoolSize時,會將新增線程暫時存放到該隊列中,需要的朋友可以參考下
    2023-12-12

最新評論

定边县| 新平| 会理县| 随州市| 渭源县| 潞城市| 福清市| 长垣县| 长宁区| 大关县| 盐山县| 赤峰市| 江都市| 鸡泽县| 含山县| 景德镇市| 大余县| 屏南县| 通河县| 海门市| 五莲县| 德惠市| 陇南市| 金乡县| 漳平市| 麻阳| 韶山市| 吉林市| 西林县| 厦门市| 安西县| 武陟县| 民乐县| 宁津县| 固阳县| 清丰县| 岢岚县| 灵璧县| 开平市| 伊吾县| 孟州市|