1. 程式人生 > >Java String 和 byte[]中間的轉換

Java String 和 byte[]中間的轉換

轉自 http://blog.csdn.net/llwan/article/details/7567906

做JAVA經常會碰到中文亂碼問題,還有各種編碼的問題,特別是String類的內容需要重新編碼的問題。要解決這些問題,必須瞭解清楚JAVA對於字串是怎麼處理的。 

 

1. “字元”是由數字來表示的

先來重新瞭解一下計算機是如何處理“字元”的,這個原理是大家必須記住的,特別是在用JAVA寫程式的時候,萬萬不可模糊。我們知道,計算機把任何東西都用數字來表示,“字元”也不例外。比如我們要顯示一個阿拉伯數字“3”,在我們的PC裡,其實並不是僅僅用一個數字3來代表我們要寫的“3”,而是以十六進位制的0x33來代表,包括放在記憶體或者是寫到檔案裡,其實都是寫著0x33的,不信你可以編輯一個文字檔案,寫一個“3”,然後用ultraEdit看他的原始碼。 

 

2. 一切“字元”都必定用數字+編碼表表示。 
這時候,有一個問題:為什麼一定要用0x33來代表“3”呢?而不用0x43來代表呢?或者是直接用0x03來代替?其實用什麼來代表都可以,只不過大家都習慣了用ASCII編碼表(是美國國家資訊交換表)來確定各字元應該是用什麼數字代表的。同樣,為了表示中國字,我國也指定了中文的編碼表,其中最廣泛使用的是GB2312。比如中文的“當”字,就是用0xB5, 0xB1這兩個八位的數字來表示的。所以如果顯示字元的程式不知道一列數字到底是按什麼編碼表編碼的,他也無法去判斷到底這些是什麼文字。如果隨便用一個不對的編碼表來處理這些數字,處理出來的字元很可能完全是錯的。比如在英文系統上,沒有GB2312編碼表,送給他一個0xB5,0xB1,他就傻傻的當作ASCII來處理(作業系統通常都有自己預設的編碼表),結果顯示出來就是兩個奇怪的符號,因為這兩個字在ASCII表裡就是那兩個符號。同樣在繁體中文系統裡,他的編碼表是BIG5,顯示出來也是一個奇怪的中文,不是“當”字。 

 

3. UNICODE讓全世界都說一種語言 
看完上面的文字,是否覺得,世界有那麼多語言,每個都有自己的一套編碼表,很麻煩呢?就算是中文,也有兩套流行的編碼表,一個是GB2312,一個是BIG5。要使用不同中文的編碼的字元時,還要轉來轉去,的確很麻煩。不光這個,如果想要寫一篇包含很多過國文字的文章,就麻煩了,必須要讓處理這個文章的程式知道,哪個字是什麼編碼標準的。如果你想要在文章裡找一個字,也必須指定你要找的是哪種編碼的哪個字。否則,你要找一個0xB5,0xB1的中文“當”字,很可能把同樣數字表示的日文、波蘭文這些不相干的字一起給你找出來,夠麻煩的吧! 
所以人們想,不如大家都用同一個編碼標準吧,各種文字都在編碼表裡有一席之地,處理文字的程式只需要都按這個編碼表來處理就可以了。不過要一個編碼表裡包含所有的文字,這張表就大了,本來英文字+數字一共只有128個以內。但加上中文後,忽然就多了數萬個,所以存放一個字元需要的大小也大了很多。現在UNICODE規定了一個字元必須由2個8位數字來表示,想想,8x8x8x8x = 65536 ,是多大的一個數字啊!所以全世界的文字才能都包含進去。當然拉,也有人說中國字可能都不止6萬個拉,還要包括別的文字,但人家外國人覺得你們中國人常用的也沒那麼多,所以就這麼定了,我們也沒辦法。需要注意的是GB2312和UNICODE雖然都是用兩個8位數來代表一箇中文字,但具體的規格可不一樣,比如0xB5,0xB1在UNICODE裡面可不是“當”字,而是另外一國的文字來的。

 

4. C是如何簡潔的處理字元的 

我們來談談C的字串。C語言誕生在JAVA之前,C語言的基本資料型別是沒有字串這個型別的,它只有char[]。也就是C把字元順序放入一個位元組陣列就完了。而且C也不管放在數組裡的是什麼文字,也不管那些字是按什麼編碼標準的。而且他的char的大小也不一定是8位數字,有時候是16位也可能,這要看具體的機器和作業系統。所以寫程式的人必須要知道正在處理的char[]的內容到底是按什麼編碼表表示的字串,要知道如果比較兩國文字是否相同,可是沒任何意義的哦! 

 

5. JAVA是是如何處理字元的。 
世界總會進步的,JAVA就是一個例子。JAVA終於有了String類了,它是解決字元問題的最好工具。在JAVA裡,一個基本的要點是:String類物件是不需要指定編碼表的!為什麼它會自己知道一堆數字各代表什麼字元呢?就是因為String裡的字元資訊是用UNICODE編碼存放的。而JAVA為了表示字元(注意是單個字元),也有char這個資料型別,而且他的大小是固定2個8位16進位制數字長度,也就是0~65535羅。為的就是對應UNICODE裡面的一個字元。大家如果想取一個String裡的按UNICODE數字,可以用getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) 方法取得一個char[],這個char[]裡就是表示String字元的,按UNICODE編碼表編碼的數字。 
可惜現在絕大多數的系統和程式都不是按UNICODE來處理字元,而JAVA程式總是要和別的程式和系統交換資料的,所以在接收一個字元,或者是傳送一個字元的時候,就必須要留意當前系統和UNICODE的關係了。比如你從網路或者檔案接受到一數字:0xB5,0xB1,JAVA程式並不知道這兩個字到底是中文呢?還是日文,或者英文。你如果不指明這個兩個數字的編碼表,JAVA就會按當前系統預設的編碼表來處理。如果這兩個數字是從中文WIN98發出去的,JAVA程式又是在英文LINUX上執行的,那就出現了所謂的亂碼問題了。也就是JAVA按英文的編碼表ASCII來處理這兩個數字,當通過new String({0xB5,0xB1})得到的String的時候,這個String代表的已經不是中文的“當”字,而是兩個英文的奇怪字元了。不過如果你知道這兩個數字一定是中文的話,就可以指定用new String({0xB5,0xB1},"GB2312")來處理,這時候新建立的String才真的是一個“當”字。當然拉,如果你要把一個“當”字的JAVA的String顯示在中文WIN98上,必須把這個字輸出成兩個8位數字:0xB5,0xB1,不管是寫成檔案還是輸出到瀏覽器上,都必須是0xB5,0xB1。如何把“當”字用GB2312輸出?String.getBytes("GB2312")就可以拉!所以有一點要記住:和外界交換任何資訊都是以byte[]來進行的!。你可以留意一下JAVA大多數的I/O類,都有以byte[]作為引數和返回值的方法。不過,也有很多寫的比較糊塗的程式,沒有提供byte[]交換資訊的方法,害的不同文字平臺的程式設計師很頭疼。Servlet的HttpRequest.getParameter()就是這樣。好在有的JSP/SERVLET容易還提供先指定編碼表的方法,才能比較簡單的解決這個問題。 

 

6. 網上關於JAVA中文問題的一些錯誤處理方法。 
一個是最常見的,不管什麼內容,都用new String(...,"ISO-8859-1")來建立字串,然後使用的時候按預設的編碼格式(通常在伺服器上都是英文系統)輸出字串。這樣其實你使用的String並不是按UNICODE來代表真正的字元,而是強行把BYTE陣列複製到String的char[]裡,一旦你的執行環境改變,你就被迫要修改一大堆的程式碼。而且也無法在同一個字串裡處理幾種不同編碼的文字。 
另一個是把一種編碼格式的字串,比如是GB2312,轉換成另一種格式的字串,比如UTF-8,然後不指明是UTF-8編碼,而直接用new String(...)來建立String,這樣放在String裡面的字元也是無法確定的,它在不同的系統上代表不同的字元。如果要求別人用“UTF-8格式”的String來交換資訊的時候,其實已經破壞了JAVA為了相容各種語言所做的規定。這種錯誤的本質思想是還按寫C語言的方式,把字串純粹當作可以自己自由編碼的儲存器使用,而忽略了JAVA字串只有一種編碼格式。如果真的想自由編碼,用byte[]或者char[]就完全瞭解決問題的了。 

以上,除了是解決JAVA中文問題的基礎知識外,也是多年前應該掌握的計算機基礎知識。溫故而知新,以期共勉。

 

1. “字元”是由數字來表示的

先來重新瞭解一下計算機是如何處理“字元”的,這個原理是大家必須記住的,特別是在用JAVA寫程式的時候,萬萬不可模糊。我們知道,計算機把任何東西都用數字來表示,“字元”也不例外。比如我們要顯示一個阿拉伯數字“3”,在我們的PC裡,其實並不是僅僅用一個數字3來代表我們要寫的“3”,而是以十六進位制的0x33來代表,包括放在記憶體或者是寫到檔案裡,其實都是寫著0x33的,不信你可以編輯一個文字檔案,寫一個“3”,然後用ultraEdit看他的原始碼。 

 

2. 一切“字元”都必定用數字+編碼表表示。 
這時候,有一個問題:為什麼一定要用0x33來代表“3”呢?而不用0x43來代表呢?或者是直接用0x03來代替?其實用什麼來代表都可以,只不過大家都習慣了用ASCII編碼表(是美國國家資訊交換表)來確定各字元應該是用什麼數字代表的。同樣,為了表示中國字,我國也指定了中文的編碼表,其中最廣泛使用的是GB2312。比如中文的“當”字,就是用0xB5, 0xB1這兩個八位的數字來表示的。所以如果顯示字元的程式不知道一列數字到底是按什麼編碼表編碼的,他也無法去判斷到底這些是什麼文字。如果隨便用一個不對的編碼表來處理這些數字,處理出來的字元很可能完全是錯的。比如在英文系統上,沒有GB2312編碼表,送給他一個0xB5,0xB1,他就傻傻的當作ASCII來處理(作業系統通常都有自己預設的編碼表),結果顯示出來就是兩個奇怪的符號,因為這兩個字在ASCII表裡就是那兩個符號。同樣在繁體中文系統裡,他的編碼表是BIG5,顯示出來也是一個奇怪的中文,不是“當”字。 

 

3. UNICODE讓全世界都說一種語言 
看完上面的文字,是否覺得,世界有那麼多語言,每個都有自己的一套編碼表,很麻煩呢?就算是中文,也有兩套流行的編碼表,一個是GB2312,一個是BIG5。要使用不同中文的編碼的字元時,還要轉來轉去,的確很麻煩。不光這個,如果想要寫一篇包含很多過國文字的文章,就麻煩了,必須要讓處理這個文章的程式知道,哪個字是什麼編碼標準的。如果你想要在文章裡找一個字,也必須指定你要找的是哪種編碼的哪個字。否則,你要找一個0xB5,0xB1的中文“當”字,很可能把同樣數字表示的日文、波蘭文這些不相干的字一起給你找出來,夠麻煩的吧! 
所以人們想,不如大家都用同一個編碼標準吧,各種文字都在編碼表裡有一席之地,處理文字的程式只需要都按這個編碼表來處理就可以了。不過要一個編碼表裡包含所有的文字,這張表就大了,本來英文字+數字一共只有128個以內。但加上中文後,忽然就多了數萬個,所以存放一個字元需要的大小也大了很多。現在UNICODE規定了一個字元必須由2個8位數字來表示,想想,8x8x8x8x = 65536 ,是多大的一個數字啊!所以全世界的文字才能都包含進去。當然拉,也有人說中國字可能都不止6萬個拉,還要包括別的文字,但人家外國人覺得你們中國人常用的也沒那麼多,所以就這麼定了,我們也沒辦法。需要注意的是GB2312和UNICODE雖然都是用兩個8位數來代表一箇中文字,但具體的規格可不一樣,比如0xB5,0xB1在UNICODE裡面可不是“當”字,而是另外一國的文字來的。

 

4. C是如何簡潔的處理字元的 

我們來談談C的字串。C語言誕生在JAVA之前,C語言的基本資料型別是沒有字串這個型別的,它只有char[]。也就是C把字元順序放入一個位元組陣列就完了。而且C也不管放在數組裡的是什麼文字,也不管那些字是按什麼編碼標準的。而且他的char的大小也不一定是8位數字,有時候是16位也可能,這要看具體的機器和作業系統。所以寫程式的人必須要知道正在處理的char[]的內容到底是按什麼編碼表表示的字串,要知道如果比較兩國文字是否相同,可是沒任何意義的哦! 

 

5. JAVA是是如何處理字元的。 
世界總會進步的,JAVA就是一個例子。JAVA終於有了String類了,它是解決字元問題的最好工具。在JAVA裡,一個基本的要點是:String類物件是不需要指定編碼表的!為什麼它會自己知道一堆數字各代表什麼字元呢?就是因為String裡的字元資訊是用UNICODE編碼存放的。而JAVA為了表示字元(注意是單個字元),也有char這個資料型別,而且他的大小是固定2個8位16進位制數字長度,也就是0~65535羅。為的就是對應UNICODE裡面的一個字元。大家如果想取一個String裡的按UNICODE數字,可以用getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) 方法取得一個char[],這個char[]裡就是表示String字元的,按UNICODE編碼表編碼的數字。 
可惜現在絕大多數的系統和程式都不是按UNICODE來處理字元,而JAVA程式總是要和別的程式和系統交換資料的,所以在接收一個字元,或者是傳送一個字元的時候,就必須要留意當前系統和UNICODE的關係了。比如你從網路或者檔案接受到一數字:0xB5,0xB1,JAVA程式並不知道這兩個字到底是中文呢?還是日文,或者英文。你如果不指明這個兩個數字的編碼表,JAVA就會按當前系統預設的編碼表來處理。如果這兩個數字是從中文WIN98發出去的,JAVA程式又是在英文LINUX上執行的,那就出現了所謂的亂碼問題了。也就是JAVA按英文的編碼表ASCII來處理這兩個數字,當通過new String({0xB5,0xB1})得到的String的時候,這個String代表的已經不是中文的“當”字,而是兩個英文的奇怪字元了。不過如果你知道這兩個數字一定是中文的話,就可以指定用new String({0xB5,0xB1},"GB2312")來處理,這時候新建立的String才真的是一個“當”字。當然拉,如果你要把一個“當”字的JAVA的String顯示在中文WIN98上,必須把這個字輸出成兩個8位數字:0xB5,0xB1,不管是寫成檔案還是輸出到瀏覽器上,都必須是0xB5,0xB1。如何把“當”字用GB2312輸出?String.getBytes("GB2312")就可以拉!所以有一點要記住:和外界交換任何資訊都是以byte[]來進行的!。你可以留意一下JAVA大多數的I/O類,都有以byte[]作為引數和返回值的方法。不過,也有很多寫的比較糊塗的程式,沒有提供byte[]交換資訊的方法,害的不同文字平臺的程式設計師很頭疼。Servlet的HttpRequest.getParameter()就是這樣。好在有的JSP/SERVLET容易還提供先指定編碼表的方法,才能比較簡單的解決這個問題。 

 

6. 網上關於JAVA中文問題的一些錯誤處理方法。 
一個是最常見的,不管什麼內容,都用new String(...,"ISO-8859-1")來建立字串,然後使用的時候按預設的編碼格式(通常在伺服器上都是英文系統)輸出字串。這樣其實你使用的String並不是按UNICODE來代表真正的字元,而是強行把BYTE陣列複製到String的char[]裡,一旦你的執行環境改變,你就被迫要修改一大堆的程式碼。而且也無法在同一個字串裡處理幾種不同編碼的文字。 
另一個是把一種編碼格式的字串,比如是GB2312,轉換成另一種格式的字串,比如UTF-8,然後不指明是UTF-8編碼,而直接用new String(...)來建立String,這樣放在String裡面的字元也是無法確定的,它在不同的系統上代表不同的字元。如果要求別人用“UTF-8格式”的String來交換資訊的時候,其實已經破壞了JAVA為了相容各種語言所做的規定。這種錯誤的本質思想是還按寫C語言的方式,把字串純粹當作可以自己自由編碼的儲存器使用,而忽略了JAVA字串只有一種編碼格式。如果真的想自由編碼,用byte[]或者char[]就完全瞭解決問題的了。 

以上,除了是解決JAVA中文問題的基礎知識外,也是多年前應該掌握的計算機基礎知識。溫故而知新,以期共勉。