微信公众号: 爱问CTO
专业编程问答社区
www.askcto.compublic 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 必须要提到 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 这些问题都不存在。每当一个问题出现的时候,总有人想法设法去解决它。
全称叫《汉字内码扩展规范》,是国家技术监督局为 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编码的时候就不需要它自己去补位了。原来的字节数组也就不会发生改变。