面试之Java String 编码相关

  另有一篇我的字符编码本质入门的文章见这里: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 : ÖÐ
 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行,也是“乱码”了,ÖÐ, 但为什么又变成了两个不同的字符。。-_-|| 

  第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行,并不是乱码,Ö 和 Ð 确实是ISO8859字符集中包含的字符,对应的编码为[0xD6 0xD0],在GBK中为字符 “中” ,在 ISO8859中为两个字符 “Ö” 和 “Д,isoString内容为“ÖД

  第9行,取得isoString在utf8 编码集中对应 Ö 和 Ð 字符的编码数组, 即 [0xC3,0x96] =Ö  [0xC3,0x90] = Ð。

  第10行,取得isoString在GBK编码其中对应的Ö 和 Ð 字符的编码数组,因为GBK未包含这两个字符,于是被转换为“??”后取得编码 即 [0x3F 0x3F]

  第10行,取得isoString在ISO8859中对应的Ö 和 Ð 字符的编码数组,即为[0xD6 0xD0],因此不变。

  总结及推论:

  •   String实际存储的内容是不可见,也无需关心的,可以理解为它存储的是字符。你用Byte数组初始化一个字符串时,总会显示或者默认的指明数组的编码格式。String内部会据此将其对应的字符而非编码,以某种方法保存在其内部。如果你指定的字符集与提供的数组不一致,String会帮你映射为未知字符可能是“?”或“�”。
  •   String存储的不是初始化时提供的Byte数组,因此经过 Byte 到 String的转换后,可能会导致原始Byte数组的内容丢失,无法通过转换后的 String获得。所以乱码问题,要从源头解决,而不是在String上下功夫。
  •   ISO8859-1是一个0x00-0xFF的都有定义的单字符编码,因此该编码进行byte到String转换不会丢失信息,String可以以Iso8859取得Byte数组后,以其他字符集显示,因此很多地方仍然使用此种字符集。  

  另:字符是抽象的,具体存储肯定要定义编码,Java规范定义的是“外部”的编码的表现和工作方式,内部存储可以自行实现,目前实际使用似乎是UTF16.

posted @ 2022-03-30 16:22  锅叔  阅读(452)  评论(0编辑  收藏  举报