菜单 学习猿地 - LMONKEY

 java字符串“我是谁”的转码问题

java字符串“我是谁”的转码问题

vency profile image vency ・1 min read

3.jpg

微信公众号:爱问CTO
专业编程问答社区
www.askcto.com

问题出场

public static void main(String[] args) throws Exception {
                 String a = "我是谁";
                 String b=new String(a.getBytes("utf-8"),"gbk");
                 System.out.println(b);
                 String c=new String(b.getBytes("gbk"),"utf-8");
                 System.out.println(c);
            }

输出的结果:

鎴戞槸璋�
我是�?

问题:字符串从utf-8转到gbk再转回utf-8为什么会出现部分乱码?

ps:如果换成偶数个数的字符串,比如“我是谁啊”往回转就没问题的。详情见本文补充

utf-8编码

回答上面的这个问题,我们先回顾一下基础的编码知识,说到 UTF 必须要提到 Unicode(Universal Code 统一码)。开始有了UTF-16,UTF-16 用两个字节来表示 Unicode 转化格式,这个是定长的,不论什么字符都可以用两个字节表示,两个字节是 16 个 bit,所以叫 UTF-16。

试想一下,不管什么字符都要用两个字节表示,是不是有点浪费空间呢。比如有很多单个字符的如英文字母完全可以用一个字节表示的。这个时候UTF-8就出场了,它采用了一种变长技术,每个编码区域有不同的字码长度。对汉字采用三个字节表示。不同类型的字符可以是由 1~6 个字节组成。让我想到了数据库的char与Varchar2的区别。

ps:顺便再说一下,UTF-16还存在一个问题。UTF-16 采用顺序编码,不能对单个字符的编码值进行校验,如果中间的一个字符码值损坏,后面的所有码值都将受影响。而 UTF-8 这些问题都不存在。每当一个问题出现的时候,总有人想法设法去解决它。

GBK编码

全称叫《汉字内码扩展规范》,是国家技术监督局为 windows95 所制定的新的汉字内码规范,它的出现是为了扩展 GB2312,加入更多的汉字,(GB2313总包含 6763 个汉字)。它的编码范围是 8140~FEFE(去掉 XX7F)总共有 23940 个码位,它能表示 21003 个汉字,它的编码是和 GB2312 兼容的,也就是说用 GB2312 编码的汉字可以用 GBK 来解码,并且不会有乱码。

GBK 字符集有一个 char 到 byte 的码表,不同的字符编码就是查这个码表找到与每个字符的对应的字节,然后拼装成 byte 数组。而汉字被编码成双字节。

解答问题

有了上面的基础,我们在看文章开始提出的问题。

首先字符串“我是谁”经过getBytes("utf-8")转为utf-8编码的字节。utf编码的汉字占用3个字节。一共也就是9个字节,然后再经过(a.getBytes("utf-8"),"gbk")转回gbk编码的字符串,而gbk编码对应的汉字是双字节。经过utf-8编码后的9个字节,在转GBK就是4个字,但是还余剩下个字节,这个时候,它会帮你在补充一个字节。就是5个字了-鎴戞槸璋�,

最后一个字很奇怪,就是最后2个字节组合的时候,在GBK码表中找不到对应的字,没有对应的怎么办,找一个比较接近的代替。

继续往下看代码,(b.getBytes("gbk"),"utf-8"),这是拿到gbk编码的字节,也就是10个字节。然后转回utf-8编码的字符串。utf编码的汉字占用3个字节。10个字节。前三个被翻译为了我,接着三个翻译为了是,在接着三个就开始乱码了,因为这个你拿的字节是-鎴戞槸璋�,这5个字的第4个字的2个字节和第5个字的第一个字节,而你刚才在转GBK的时候,最后两个字节组合在GBK码表中找不到对应的字,他找了一个比较接近的替代,字变了,那对应的字节数组也发生了改变。所以这里在翻译回去就出了问题。

最后还剩一个字节,但是utf-8需要三个字节才能被翻译,它又补上了两个,就翻译出来了一个?

从代码中认识

将转换的字节打印出来。

    public static void main(String[] args) throws Exception {
                 String a = "我是谁";
                 byte[] byte1 = a.getBytes("utf-8");
                 String b=new String(byte1,"gbk");
                 System.out.println(b);
                 byte[] byte2 = b.getBytes("gbk");
                 String c=new String(byte2,"utf-8");
                 System.out.println(c);
            }

字符串“我是谁”,转成utf-8的字节对应的数据:

[-26, -120, -111, -26, -104, -81, -24, -80, -127]

字符串“鎴戞槸璋�”,转成gbk的字节数组

[-26, -120, -111, -26, -104, -81, -24, -80, 63]

这个时候最后一个字节已经发生了变化,肯定翻译的会出现部分乱码了。

补充一点

字符串是偶数个数就可以转过去

public static void main(String[] args) throws Exception {
                 String a = "我是谁啊";
                 String b=new String(a.getBytes("utf-8"),"gbk");
                 System.out.println(b);
                 String c=new String(b.getBytes("gbk"),"utf-8");
                 System.out.println(c);
            }

运行的结果:

鎴戞槸璋佸晩
我是谁啊

仔细想一下,因为这样,转gbk编码的时候就不需要它自己去补位了。原来的字节数组也就不会发生改变。

评论 (0)