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

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编码的时候就不需要它自己去补位了。原来的字节数组也就不会发生改变。

Image placeholder
loftyet
未设置
  47人点赞

没有讨论,发表一下自己的看法吧

推荐文章
“我是技术总监,你干嘛总问我技术细节?”

题图:fromZoommy每个周末的午后,把儿子送进EF读书,随后找个环境幽静的咖啡馆坐一会,这便是我一周中最放松的时光。在咖啡厅的气氛和环境这两点上,我似乎有强迫症,比如装修主色调的运用,地上装饰是

Bash技巧:使用参数扩展获取变量的子字符串和字符串长度

在bash中,通常使用${parameter}表达式来获取parameter变量的值,这是一种参数扩展(parameterexpansion)。Bash还提供了其他形式的参数扩展,可以对变量值做一些处

GitLab,是谁给了你歧视中国程序员的勇气?

GitLab安全漏洞不断,收集用户行为数据,今天又找到新的作死方法了。由谷歌投资的全球第二大开源代码托管平台GitLab在其官网上发布了一项声明,称他们决定为有权访问客户数据的团队成员启用“工作家庭国

近50年来最具影响力的10种编程语言,都是谁发明的?

大数据文摘出品编译:洪颖菲、武帅前不久文摘菌曾报道过4分钟看尽Top15编程语言15年来的沉浮史,评论中就有小伙伴留言了为什么Ruby、Lisp这些语言在榜上寂寂无名?软件世界中有各种各样的编程语言,

jquery如何判断字符串是否包含指定字符?

方法一:使用indexOf()和lastIndexOf()方法案例:varCts="bblText"; if(Cts.indexOf("Text")>=0){ alert('Cts中包含Text字符串

阿里云视频点播转码

阿里云点播SDK下载安装地址:https://helpcdn.aliyun.com/document_detail... 阿里云点播上传SDK下载地址:https://helpcdn.aliyun.c

“我怎么就被一张照片出卖了?”

上篇文章《21岁日本女星惨遭猥亵,只因自拍瞳孔倒影暴露住址? |一张照片是怎么出卖你的!》发出去之后,发现大家对这个事情比较感兴趣,决定再和大家多聊聊这个领域。还有读者朋友们给我说,为什么我拿到的图片

那个“炫酷狂拽”的数据可视化利器AntV 11.22版全新发布啦

导读AntV是一个数据可视化项目,也是一个团队,蚂蚁金服数据可视化团队,一群有爱有梦的人,怀揣「让人们在数据世界里获得视觉化思考能力」的梦想前行,希望成就智能时代全球领先的数据可视化解决方案,满足与日

当年“你说什么,我都能实现”的软件公司,后来都是怎么死的?

在 #“我,80后,曾经靠副业的收入买车买房”# 的评论区里,有读者问,十几年前,圈内有不少软件公司,规模大小不一,遍布各个行业,但这几年似乎都没动静了,他们还活着吗?我说,撇开纯做“劳工”输出的外包

那些“中漂”的欧洲科学家们

大数据文摘出品来源:Sciencemag编译:武帅、刘俊寰就近几年的科技发展水平来看,中国已逐步向科技大国迈进,这在提升中国在全球的科研影响力之余,也在一定程度上改变了全球科学人才流动背后的格局。比如

实现人工智能落地 你还差一个“数据分析流水线”的距离

在智慧生产场景,生产制造商可以在生产线上利用深度学习,尤其是图像识别,将产品的质量检测自动化。比如自动检测产品表面有没有划伤、有没有零部件的缺失、有没有标签的错位。研究表明,相比人工检测,智慧检测可以

ERP云端相竞,QAD “中国云”的差异化竞争

提起海外知名ERP厂商可能很多人会想到SAP、Oracle,但在制造业尤其是汽车领域大家对QAD也许会更为熟悉。专注于制造业40年的QAD今年也迎来了在华的20岁生日,在6月13日举行的QAD2019

JavaScript中对“this”的简单理解

1.this的奥秘很多时候,JS中的this对于咱们的初学者很容易产生困惑不解。this的功能很强大,但需要一定付出才能慢慢理解它。对Java、PHP或其他标准语言来看,this表示类方法中当前对象的

一份关于机器学习“模型再训练”的终极指南

机器学习模型的训练,通常是通过学习某一组输入特征与输出目标之间的映射来进行的。一般来说,对于映射的学习是通过优化某些成本函数,来使预测的误差最小化。在训练出最佳模型之后,将其正式发布上线,再根据未来生

阿里巴巴向全社会开放黑科技:“泡在水里”的服务器

为了让数据中心更绿色,阿里工程曾将服务器“泡在水里”进行散热,节能超70%,今天这项黑科技的神秘面纱被揭开。2020年1月6日,阿里巴巴宣布将“浸没式液冷数据中心技术规范”向全社会开放。这项规范旨在用

Stack Overflow 上 370万浏览量的一个问题:如何比较 Java 的字符串?

在逛StackOverflow的时候,发现了一些访问量像喜马拉雅山一样高的问题,比如说这个:如何比较Java的字符串?访问量足足有370万+,这不得了啊!说明有很多很多的程序员被这个问题困扰过。PS:

GoWeb教程_07.6. 字符串处理

字符串在我们平常的Web开发中经常用到,包括用户的输入,数据库读取的数据等,我们经常需要对字符串进行分割、连接、转换等操作,本小节将通过Go标准库中的strings和strconv两个包中的函数来讲解

Java 8 API 示例:字符串、数值、算术和文件

Java8API示例:字符串、数值、算术和文件 大量的教程和文章都涉及到Java8中最重要的改变,例如lambda表达式和函数式数据流。但是此外许多现存的类在JDK8API中也有所改进,带有一些实用的

分享 6 个 Go 处理字符串的技巧

如果你从Ruby或者Python转型到Go,将会有很多语言差异需要学习,其中很多问题都是围绕处理string类型。下面是一些字符串的技巧,这些技巧解决了我在使用Golang的最初几周中遇到的问题。

Go语言高级编程_1.3 数组、字符串和切片

1.3数组、字符串和切片 在主流的编程语言中数组及其相关的数据结构是使用得最为频繁的,只有在它(们)不能满足时才会考虑链表、hash表(hash表可以看作是数组和链表的混合体)和更复杂的自定义数据结构

算法题:判断括号字符串是否有效

题目来源于力扣 理论基础 堆栈&队列 判断括号字符串是否有效 题目描述 给定一个只包括'(',')','{','}','[',']'的字符串,判断字符串是否有效。 有效字符串需满足:左括号必

算法题:判断括号字符串是否有效

题目来源于力扣 理论基础 堆栈&队列 判断括号字符串是否有效 题目描述 给定一个只包括'(',')','{','}','[',']'的字符串,判断字符串是否有效。 有效字符串需满足:左括号必

PHP 开发工程师基础篇 - PHP 字符串

PHP开发工程师基础篇-PHP字符串字符串(String)字符串是一系列字符的集合.如"abc".在PHP中,一个字符代表一个字节,一个字节(Byte)有8比特(bit).PHP仅支持256字符集,因

字符串类型

String的声明:变量值需要用引号(单、双、三引号)包装起来。声明方式的选择:如果字符串中有双引号,推荐使用单引号声明(HTML代码)。如果字符串中有单引号,推荐使用双引号声明(PHP,python

字符串

字符串加号‘+’做字符连接运算(只能同类运算) 字符串赋值运算乘号‘*’做字符串重复累计运算。复制的次数必须是整形而不能是浮点类型。 字符串索引操作,只需要将字符串当作列表操作,填入下标即可,也可做列