另有一篇我的字符編碼本質入門的文章見這里:https://www.cnblogs.com/uncleguo/p/16008551.html
實話說,作為一個多年Java老年程式員,直到近來,在沒有決心花時間搞清楚Java String的編碼相關問題之前, 自己也都還是似懂非懂,一臉懵逼的,設想如果在面試中,有同學能夠條理清晰的回答下面的問題,那必是非常了得之人,論智慧武功應該均在本人之上:-),
問:請預測下面程式的輸出,并解釋原因,printHexBinary方法為16進制列印Byte
1 String str = "中"; 2 3 byte[] bufferGBK = str.getBytes("GBK"); 4 System.out.println("bufferGBK = "+printHexBinary(bufferGBK)) ; 5 6 String gbkString =new String(bufferGBK,"GBK"); 7 System.out.println("gbkString = new String bufferGBK GBK : "+gbkString); 8 9 String utf8String =new String(bufferGBK,"utf-8"); 10 System.out.println("utf8String = new String bufferGBK utf8 : "+utf8String); 11 12 byte[] utfFromStr = utf8String.getBytes("utf-8"); 13 System.out.println("utf8String getBytes utf-8 : "+printHexBinary(utfFromStr)); 14 15 byte[] gbkFromStr = utf8String.getBytes("GBK"); 16 System.out.println("utf8String getBytes GBK : "+printHexBinary(gbkFromStr)); 17 18 byte[] isoFromStr = utf8String.getBytes("ISO-8859-1"); 19 System.out.println("utf8String getBytes ISO-8859-1 : "+printHexBinary(isoFromStr)); 20 21 String isoString =new String(bufferGBK,"ISO-8859-1"); 22 System.out.println("isoString = new String bufferGBK ISO-8859-1 : "+isoString); 23 24 utfFromStr = isoString.getBytes("utf-8"); 25 System.out.println("isoString getBytes utf-8 : "+printHexBinary(utfFromStr)); 26 27 gbkFromStr = isoString.getBytes("GBK"); 28 System.out.println("isoString getBytes GBK : "+printHexBinary(gbkFromStr)); 29 30 isoFromStr = isoString.getBytes("ISO-8859-1"); 31 System.out.println("isoString getBytes ISO-8859-1 : "+printHexBinary(isoFromStr));
按我之前的認識,先簡單推理下,
第4行的Print輸出的應該是“中”的GBK編碼(中的GBK編碼是0xD6 0xD0),
第7行用[0xD6 0xD0]以GBK字符集new一個String,列印這個String,那應該是“中”
第10行用[0xD6 0xD0]以UTF8字符集new一個String,列印這個String,這里可能會亂碼,具體會顯示什么字符,要看0xD6 0xD0對應的Utf8 字符,
× 第13行從上面new的String中按UTF8取得Byte陣列,因為上面New 的是Utf8 String,這里取出的應該還是[0xD6 0xD0]
× 第16行從上面new的String中按GBK取得Byte陣列, 這……不太確定,可能還是[0xD6 0xD0]?記憶體存盤的編碼應該是不變的?
× 第19行從上面new的String中按ISO8859取得Byte陣列, 這……同上吧? 但似乎有點兒問題,應該是不對,邏輯上如果getBytes都一樣,那為啥要引數指定字符集呢?
第22行用[0xD6 0xD0]以ISO8859字符集new一個String,列印這個String,這里可能會亂碼, 要看[0xD6 0xD0]ISO8859中對應的字符,
× 第25,28行,這……
第30行從上面new的String中按ISO8859取得Byte陣列,這應該不會變,還是[0xD6 0xD0]
我只能回答成這樣了,自我感覺比較風流倜儻,瀟灑惆悵的可以先自己琢磨下, 實際的程式輸出在這里↓
1 ======================================== 2 bufferGBK = 0xD6,0xD0 3 gbkString = new String bufferGBK GBK : 中 4 utf8String = new String bufferGBK utf8 : ?? 5 utf8String getBytes utf-8 : 0xEF,0xBF,0xBD,0xEF,0xBF,0xBD 6 utf8String getBytes GBK : 0x3F,0x3F 7 utf8String getBytes ISO-8859-1 : 0x3F,0x3F 8 isoString = new String bufferGBK ISO-8859-1 : ?D 9 isoString getBytes utf-8 : 0xC3,0x96,0xC3,0x90 10 isoString getBytes GBK : 0x3F,0x3F 11 isoString getBytes ISO-8859-1 : 0xD6,0xD0 12 ========================================答案點這里
然后對著輸出結果來理解下,
答案中的2,3行輸出跟預期一樣
第4行確實是“亂碼”了,但為什么[0xD6 0xD0]會變成兩個一樣的字符??
第5行,byte陣列不是之前的2個,而是6個元素,與0xD6 0xD0完全不同,是何原因?
第6,7行,byte陣列是[0x3F 0x3F],為啥?
第8行,也是“亂碼”了,?D, 但為什么又變成了兩個不同的字符,,-_-||
第9行 byte陣列4個元素,看起來不同,
第10行 byte陣列[0x3f 0x3f]
第11行 確實還是[0xD6 0xD0]
實踐檢驗真理,上面的實驗表明,String在記憶體存盤的實際內容與getBytes取得的內容,可能是存在轉換關系的,某些字符集的情況下是不變的(ISO8859),而有些經過Byte 到 String 到 Byte 的轉換后會發生變化,與創建時的byte陣列不同,
經過一番上下求索之后,下面是我認為比較合理的解釋,
答案中的2,3行輸出跟預期一樣
第4行,亂碼因為[0xD6 0xD0]不是兩個有效的Utf8字符集字符, Java將其轉換處理為兩個?,即utf8String中的內容即為“??”
第5行此時取得Byte陣列為對應Utf8 中兩個?字符的字符編碼,即在UTF8 字符集中? 的編碼為[0xEF,0xBF,0xBD]
第6行取得的Byte陣列為,字符?對應在GBK字符集中的字符編碼,該字符應該未包含,被轉換為 0x3F 即 ? 字符
第7行,同上
第8行,并不是亂碼,? 和 D 確實是ISO8859字符集中包含的字符,對應的編碼為[0xD6 0xD0],在GBK中為字符 “中” ,在 ISO8859中為兩個字符 “?” 和 “D”,isoString內容為“?D”
第9行,取得isoString在utf8 編碼集中對應 ? 和 D 字符的編碼陣列, 即 [0xC3,0x96] =? [0xC3,0x90] = D,
第10行,取得isoString在GBK編碼其中對應的? 和 D 字符的編碼陣列,因為GBK未包含這兩個字符,于是被轉換為“??”后取得編碼 即 [0x3F 0x3F]
第10行,取得isoString在ISO8859中對應的? 和 D 字符的編碼陣列,即為[0xD6 0xD0],因此不變,
總結及推論:
- String實際存盤的內容是不可見,也無需關心的,可以理解為它存盤的是字符,你用Byte陣列初始化一個字串時,總會顯示或者默認的指明陣列的編碼格式,String內部會據此將其對應的字符而非編碼,以某種方法保存在其內部,如果你指定的字符集與提供的陣列不一致,String會幫你映射為未知字符可能是“?”或“?”,
- String存盤的不是初始化時提供的Byte陣列,因此經過 Byte 到 String的轉換后,可能會導致原始Byte陣列的內容丟失,無法通過轉換后的 String獲得,所以亂碼問題,要從源頭解決,而不是在String上下功夫,
- ISO8859-1是一個0x00-0xFF的都有定義的單字符編碼,因此該編碼進行byte到String轉換不會丟失資訊,String可以以Iso8859取得Byte陣列后,以其他字符集顯示,因此很多地方仍然使用此種字符集,
另:字符是抽象的,具體存盤肯定要定義編碼,Java規范定義的是“外部”的編碼的表現和作業方式,內部存盤可以自行實作,目前實際使用似乎是UTF16.
本文來自博客園,作者:鍋叔
轉載請注明原文鏈接:https://www.cnblogs.com/uncleguo/p/16076173.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/452824.html
標籤:Java
下一篇:IO(遞回)
