• 面试之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.

  • 相关阅读:
    Java 进阶集合Set、Map(二)
    基于24位Δ-ΣADC和FPGA的高精度数据采集系统开发
    Java八股文纯享版——篇②:并发编程
    广州华锐互动:VR互动教学平台如何赋能职业院校?
    怎样处理专利申请优先权呢?
    Mysql 单行处理函数打字练习—— 让你熟悉必要的函数,提高查询效率
    多模态及图像安全的探索与思考
    【论文笔记】MiniSeg: An Extremely Minimum Network for Efficient COVID-19 Segmentation
    javaScrip入门
    高可用状态决策方式
  • 原文地址:https://www.cnblogs.com/uncleguo/p/16076173.html