实话说,作为一个多年Java老年程序员,直到近来,在没有决心花时间搞清楚Java String的编码相关问题之前, 自己也都还是似懂非懂,一脸懵逼的。设想如果在面试中,有同学能够条理清晰的回答下面的问题,那必是非常了得之人,论智慧武功应该均在本人之上:-)。

  问:请预测下面程序的输出,并解释原因。printHexBinary方法为16进制打印Byte

  1. 1 String str = "中";
  2. 2
  3. 3 byte[] bufferGBK = str.getBytes("GBK");
  4. 4 System.out.println("bufferGBK = "+printHexBinary(bufferGBK)) ;
  5. 5
  6. 6 String gbkString =new String(bufferGBK,"GBK");
  7. 7 System.out.println("gbkString = new String bufferGBK GBK : "+gbkString);
  8. 8
  9. 9 String utf8String =new String(bufferGBK,"utf-8");
  10. 10 System.out.println("utf8String = new String bufferGBK utf8 : "+utf8String);
  11. 11
  12. 12 byte[] utfFromStr = utf8String.getBytes("utf-8");
  13. 13 System.out.println("utf8String getBytes utf-8 : "+printHexBinary(utfFromStr));
  14. 14
  15. 15 byte[] gbkFromStr = utf8String.getBytes("GBK");
  16. 16 System.out.println("utf8String getBytes GBK : "+printHexBinary(gbkFromStr));
  17. 17
  18. 18 byte[] isoFromStr = utf8String.getBytes("ISO-8859-1");
  19. 19 System.out.println("utf8String getBytes ISO-8859-1 : "+printHexBinary(isoFromStr));
  20. 20
  21. 21 String isoString =new String(bufferGBK,"ISO-8859-1");
  22. 22 System.out.println("isoString = new String bufferGBK ISO-8859-1 : "+isoString);
  23. 23
  24. 24 utfFromStr = isoString.getBytes("utf-8");
  25. 25 System.out.println("isoString getBytes utf-8 : "+printHexBinary(utfFromStr));
  26. 26
  27. 27 gbkFromStr = isoString.getBytes("GBK");
  28. 28 System.out.println("isoString getBytes GBK : "+printHexBinary(gbkFromStr));
  29. 29
  30. 30 isoFromStr = isoString.getBytes("ISO-8859-1");
  31. 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. 1 ========================================
  2. 2 bufferGBK = 0xD6,0xD0
  3. 3 gbkString = new String bufferGBK GBK :
  4. 4 utf8String = new String bufferGBK utf8 : ��
  5. 5 utf8String getBytes utf-8 : 0xEF,0xBF,0xBD,0xEF,0xBF,0xBD
  6. 6 utf8String getBytes GBK : 0x3F,0x3F
  7. 7 utf8String getBytes ISO-8859-1 : 0x3F,0x3F
  8. 8 isoString = new String bufferGBK ISO-8859-1 : ÖÐ
  9. 9 isoString getBytes utf-8 : 0xC3,0x96,0xC3,0x90
  10. 10 isoString getBytes GBK : 0x3F,0x3F
  11. 11 isoString getBytes ISO-8859-1 : 0xD6,0xD0
  12. 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 String 编码相关的更多相关文章

  1. Java String的相关性质分析

    引言 String可以说是在Java开发中必不可缺的一种类,String容易忽略的细节也很多,对String的了解程度也反映了一个Java程序员的基本功.下面就由一个面试题来引出对String的剖析. ...

  2. java String编码转换

    /** * Get XML String of utf-8 * * @return XML-Formed string */ public static String getUTF8XMLString ...

  3. Java String类相关知识梳理(含字符串常量池(String Pool)知识)

    目录 1. String类是什么 1.1 定义 1.2 类结构 1.3 所在的包 2. String类的底层数据结构 3. 关于 intern() 方法(重点) 3.1 作用 3.2 字符串常量池(S ...

  4. 面试话痨(二)C:JAVA String,别以为你穿个马甲我就不认识你了

    面试话痨系列是从技术广度的角度去回答面试官提的问题,适合萌新观看!   面试官,别再问我火箭怎么造了,我知道螺丝的四种拧法,你想听吗? String相关的题目,是面试中经常考察的点,当面试中遇到了St ...

  5. 从Java String实例来理解ANSI、Unicode、BMP、UTF等编码概念

    转(http://www.codeceo.com/article/java-string-ansi-unicode-bmp-utf.html#0-tsina-1-10971-397232819ff9a ...

  6. 手写代码 - java.lang.String/StringBuilder 相关

    语言:Java 9-截取某个区间的string /** * Returns a string that is a substring of this string. The * substring b ...

  7. java 中String编码和byte 解码总结——字节流和字符流

    1.InputStreamReader 是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符 InputStreamReader(InputStream in, Strin ...

  8. Java总结篇系列:Java String

    String作为Java中最常用的引用类型,相对来说基本上都比较熟悉,无论在平时的编码过程中还是在笔试面试中,String都很受到青睐,然而,在使用String过程中,又有较多需要注意的细节之处. 1 ...

  9. 通过反编译深入理解Java String及intern(转)

    通过反编译深入理解Java String及intern 原文传送门:http://www.cnblogs.com/paddix/p/5326863.html 一.字符串问题 字符串在我们平时的编码工作 ...

随机推荐

  1. mysql数据库怎么存入emoji表情

    第一步 设置数据库字符编码为utf8mb4_general_ci  设置相应表字段字符编码为utf8mb4_general_ci 第二步 设置my.cnf增加以下配置信息 [client] defau ...

  2. 浅谈归并排序:合并 K 个升序链表的归并解法

    在面试中遇到了这道题:如何实现多个升序链表的合并.这是 LeetCode 上的一道原题,题目具体如下: 用归并实现合并 K 个升序链表 LeetCode 23. 合并K个升序链表 给你一个链表数组,每 ...

  3. CPU、进程、线程原理

    巨人的肩膀 看完这篇还不懂高并发中的线程与线程池你来打我 (qq.com)

  4. 详细介绍Windows下也能够使用osw性能升级历史

    1.Windows系统历史性能分析困难背景 在Linux/Unix上.要追朔历史性能,一般採用部署nmon进行性能监控採集与存储的方式实现.可是却没有在Windows上的版本号. Windows系统假 ...

  5. 扫盲贴:2021 CSS 最冷门特性都是些啥?

    最近几年 CSS 界的大事之一是每年年底的 <State Of CSS>,也就是 CSS 现状调查,去年年底发布了<State Of CSS 2021>.其中关于特性这一章,会 ...

  6. HTML表格总结

    知识小记: 表格的主要目的:用于HTML展示数据,不适用于布局. 表格由行的单元格组成,没有列,常识上的"列"的个数取决于行中单元格的个数. 表格本来就很丑,颜色线条美化交给css ...

  7. Wireshark教程之数据包操作

    实验目的 1.工具介绍 2.主要应用 实验原理 1.网络管理员用来解决网络问题 2.网络安全工程师用来检测安全隐患 3.开发人员用来测试执行情况 4.学习网络协议 实验内容 1.工具基本使用 2.快速 ...

  8. 第一次接触数据库(SQLite)

    第一次接触,学了创建列表 + 行的删除 + 内容的更改 + 删除列表 第一次接触要知道一些基本知识 NULL(SQL) = Nnoe(python)  #空值 INTEGER = int  #整数 R ...

  9. Python 的垃圾回收

    垃圾回收 首先介绍两个画图的工具:objgraph 包和在线绘图网站 draw.io.具体的使用以后再写. 1.引用计数 Python 中,每个对象都有存有指向该对象的引用总数,即:引用计数(refe ...

  10. IDEA 配置安卓(Android)开发环境

    今天用idea配了一下环境,安装了SDK和Gradle.找了一些学习的资源,明天正式开始学习,配置环境的(3条消息) 用IntelliJ IDEA 配置安卓(Android)开发环境(一条龙服务,新手 ...