1. 程式人生 > >Java字節數組和16進制字符串的互相轉化

Java字節數組和16進制字符串的互相轉化

格式 這樣的 req 而且 har 想想 .net string類 pan

背景基礎知識:

1、字符編碼的相關知識(轉自http://blog.csdn.net/llwan/article/details/7567906)

1.1、 “字符”是由數字來表示的

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

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

1.3、 UNICODE讓全世界都說一種語言
看完上面的文字,是否覺得,世界有那麽多語言,每個都有自己的一套編碼表,很麻煩呢?就算是中文,也有兩套流行的編碼表,一個是GB2312,一個是BIG5。要使用不同中文的編碼的字符時,還要轉來轉去,的確很麻煩。不光這個,如果想要寫一篇包含很多過國文字的文章,就麻煩了,必須要讓處理這個文章的程序知道,哪個字是什麽編碼標準的。如果你想要在文章裏找一個字,也必須指定你要找的是哪種編碼的哪個字。否則,你要找一個0xB5,0xB1的中文“當”字,很可能把同樣數字表示的日文、波蘭文這些不相幹的字一起給你找出來,夠麻煩的吧!
所以人們想,不如大家都用同一個編碼標準吧,各種文字都在編碼表裏有一席之地,處理文字的程序只需要都按這個編碼表來處理就可以了。不過要一個編碼表裏包含所有的文字,這張表就大了,本來英文字+數字一共只有128個以內。但加上中文後,忽然就多了數萬個,所以存放一個字符需要的大小也大了很多。現在UNICODE規定了一個字符必須由2個8位數字來表示(即用一個十六進制數表示)

,想想,8x8x8x8x = 65536 ,是多大的一個數字啊!所以全世界的文字才能都包含進去。當然拉,也有人說中國字可能都不止6萬個拉,還要包括別的文字,但人家外國人覺得你們中國人常用的也沒那麽多,所以就這麽定了,我們也沒辦法。需要註意的是GB2312和UNICODE雖然都是用兩個8位數來代表一個中文字,但具體的規格可不一樣,比如0xB5,0xB1在UNICODE裏面可不是“當”字,而是另外一國的文字來的。

1.4、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容易還提供先指定編碼表的方法,才能比較簡單的解決這個問題。

1.5、網上關於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[]就完全了解決問題的了。

1.6、小結(個人總結,非轉載)

1.6.1、對字符編碼的理解

  字符編碼就是一種將二進制數據解析為我們日常使用的語言字符的東西,例如0100 0001(二進制)在ASCII的規則下表示A這個字符,但是在Unicode的規則下並不表示這個字符

1.6.2、出現亂碼情況的原因

  1.6.2.1、2進制不對(getBytes(str,"GB2312/UNICODE..."))getBytes(str)使用系統默認的編碼方式

  假如在你將UNICODE編碼的漢字"我"變成二進制表示的時候,卻使用了GB2312的編碼規則,這個時候得到的二進制肯定不是你希望的,等到你再使用這個錯的二進制按照Unicode編碼規則得到的就不會是"我"了,出現亂碼

  1.6.2.2、編碼方式不對(New String(bytes,"GB2312/UNICODE..."))new String(bytes)使用系統默認的編碼方式

  假如用new String({0xB5,0xB1}),這個時候j因為你沒有指定編碼方式,java會使用當前系統默認的編碼方式進行處理,這個時候因為0xB5,0xB1在你希望的編碼方式下是正確的字符,但是如果默認的編碼方式下則不一定是你希望的結果,所以出現亂碼

1.6.3、一些測試

  1.6.3.1Java String由char組成,從String中的函數CharAt(int index)便可知

  1.6.3.2.String類中存放的是UNICODE編碼,每個Char是16位,一個Char可能是傳統的ASCII字符,也可能是一個漢字,在內存中都 是占據兩個字節

  1.6.3.3.String類和Byte[]之間的關系

String s = new String("霜之哀傷");
  byte[] array1 = s.getBytes("utf-8");
  byte[] array2 = s.getBytes("gbk");
  byte[] array3 = s.getBytes("unicode");
  byte[] array4 = s.getBytes();// 默認是GBK


  printBytes(array1);
  printBytes(array2);
  printBytes(array3);
  printBytes(array4);

  程序輸出:

  0xE9 0x9C 0x9C 0xE4 0xB9 0x8B 0xE5 0x93 0x80 0xE4 0xBC 0xA4
  0xCB 0xAA 0xD6 0xAE 0xB0 0xA7 0xC9 0xCB
  0xFE 0xFF 0x97 0x1C 0x4E 0x4B 0x54 0xC0 0x4F 0x24
  0xCB 0xAA 0xD6 0xAE 0xB0 0xA7 0xC9 0xCB

2、十六進制(參考 https://my.oschina.net/xinxingegeya/blog/287476)

十六進制(簡寫為hex或下標16)在數學中是一種逢16進1的進位制,一般用數字0到9和字母A到F表示(其中:A~F即10~15)。

例如十進制數57,在二進制寫作111001,在16進制寫作39。

像java,c這樣的語言為了區分十六進制和十進制數值,會在十六進制數的前面加上 0x,比如0x20是十進制的32,而不是十進制的20

十六進制字符用4個二進制位來表示

技術分享

3、java中byte[]和十六進制字符串相互轉換

Java中byte用二進制表示占用8位,而我們知道16進制的每個字符需要用4位二進制位來表示。

所以我們就可以把每個byte轉換成兩個相應的16進制字符,即把byte的高4位和低4位分別轉換成相應的16進制字符H和L,並組合起來得到byte轉換到16進制字符串的結果

new String(H) + new String(L)。

同理,相反的轉換也是將兩個16進制字符轉換成一個byte,原理同上。

根據以上原理,我們就可以將byte[] 數組轉換為16進制字符串了,當然也可以將16進制字符串轉換為byte[]數組了。

/* *
 * Convert byte[] to hex string.這裏我們可以將byte轉換成int,然後利用Integer.toHexString(int)
 *來轉換成16進制字符串。  
 * @param src byte[] data  
 * @return hex string  
 */     
public static String bytesToHexString(byte[] src){  
    StringBuilder stringBuilder = new StringBuilder("");  
    if (src == null || src.length <= 0) {  
        return null;  
    }  
    for (int i = 0; i < src.length; i++) {  
        int v = src[i] & 0xFF;  
        String hv = Integer.toHexString(v);  
        if (hv.length() < 2) {  
            stringBuilder.append(0);  
        }  
        stringBuilder.append(hv);  
    }  
    return stringBuilder.toString();  
}  

  /** 
     * 十六進制的字符串轉換為byte數組 
     * **/  
    public static byte[] conver16HexToByte(String hex16Str)  
    {  
        char [] c = hex16Str.toCharArray();  
        byte [] b = new byte[c.length/2];  
        for(int i = 0;i<b.length;i++)  
        {  
            int pos = i * 2;  
            b[i] = (byte)("0123456789ABCDEF".indexOf(c[pos]) << 4 | "0123456789ABCDEF".indexOf(c[pos+1]));  
        }  
        return b;  
    }  

Java字節數組和16進制字符串的互相轉化