乱码问题详解系列(1)

一、           乱码问题概述

         编程中乱码问题,一般会出现在编写涉及网络传输存储类的程序中,当传输存储了汉字类的信息后,在最后读取查看时,汉字变为了无法辨识的空白、无意义字符、非预期的偏僻汉字、问号等。程序员基本都会碰到乱码问题,根据所处的编码场景,解决的方式也多种多样。常见乱码出现场景有:

1)  代码中有汉字注释,在vi、eclipse等编辑器中打开时变为乱码。

2)  从网页中提交保存包含汉字的表单信息后直接返回,网页展示为乱码。

3)  从网页中提交保存包含汉字的表单信息到数据库后,再查看时变为乱码。

4)  通过mysql  client等数据库客户端,查看数据库中汉字信息时,为乱码。

5)  …

很多时候,你发现刚在一个场景中费劲九牛二虎之力解决了乱码后,场景略有变化,乱码问题再次袭来,不胜其烦,让人困惑。乱码问题在我看来,是做为开发人员必须越过的一个门槛。我们不仅要知道具体场景中实战技巧,更要掌握编码知识以及乱码出现的根本原因,这样从本质来看现象,问题才能变得简单起来。因此,本文首先会介绍乱码问题根本原因以及解决方法,其次介绍编码知识、最后依次详解vi、mysql、java、php等场景中乱码现象、具体解决方法以及具体原因。

 

二、           导致乱码的根本原因

         在计算机中,所有字符最终在内存或存储介质中都是以一个字节或多个字节来存储表达的,同一个字符在不同编码中对应字节组是不一样的。例如汉字“中”在gbk编码中表示为2个字节“0xD6 0xD0”,而在utf8编码中则表示为3个字节“0xE4 0xB8 0xAD”。我们必须理解这样一个概念,计算机中字符必然包含两个元素:字符对应二进制的字节数组和对应编码。如果关联的编码正确,则从人的角度比喻,这就是计算机看到的字符。如果关联的编码错误,则从人的角度比喻,这就计算机看到的乱码。

在字符传输、存储、读取、显示等过程中,由于相关过程中对应的编码是不一样的(例如你的本地计算编码是默认编码gbk,而服务器端默认编码为utf8),同一字符对应二进制字节数组会根据当前计算机场景的编码不同而进行转换。转换可以理解为包含了两个过程:解码和编码。解码是指计算机通过知道当前二进制字节数组是什么编码,进而理解当前字符是什么,而编码则是计算机根据目标编码将它理解的字符转换为对应二进制字节数组。解码在有的语言中会隐式存在一次转换,这个后续在Java语言以及编码基础知识中会继续介绍,在这里,我们就简化为计算机理解字符(知道其关联编码)即可。

乱码在转换的各个过程中,都可能出现。我们以一个单个过程来剖析,假定转换前字符由二进制数组A以及当前编码B组成,转换的目标为二进制数组C以及目标编码D,即[A,B]转换为[C,D],乱码的根本原因包括如下两种。

1)“识别原编码错误”:计算机识别的转换前字节数组的编码B是错误的。例如字符“中文”的编码B为utf8,对应着字节数组A为6个字节“0xE4 0xB8 0xAD 0xE6 0x96 0x87”,如果计算机错误认为A的关联编码B为gbk,则会出现乱码。因为gbk中2个字节对应着一个字符,这时字节数组A就理解为3个乱码字符“涓枃”。

附一段Java代码作为以上“识别原编码错误”示例的code:

            String zhongWen = "中文";

             //“中文”utf8编码的字节数组

            byte[] bytes = zhongWen.getBytes("utf8");

            //误把字节数组编码指定为gbk

            String str = new String(bytes, "gbk");

        System.out.println("utf8 as gbk: " + str);

 

 

输出结果:

utf8 as gbk: 涓枃

 

 

         2)“转码信息损失”:计算机识别转换前字节数组的编码B正确,但是目标编码D的码表小于B,不能编码B中某些字符,强行转码丢失了编码信息后,此时也可能会出现乱码。还是以字符“中文”为例,其编码B为utf8,当目标编码为iso-8859-1时,由于iso-8859-1编码无法表达汉字,强行转换会有乱码。在Java语言中,针对iso-8859-1无法转码的“中文”的情况,其默认处理方式是转码为问号,即字节“0x3f 0x3f”,即为乱码“??”。

         附一段Java代码示例:

            String zhongWen = "中文";

            //java内置unicode编码的汉字中文转码为iso-8859-1的字节数组

            byte[] bytes = zhongWen.getBytes("iso-8859-1");

            //iso-8859-1编码的字节数组转码为java的字符串(内部编码为unicode),以便显示

            String str = new String(bytes,"iso-8859-1");

            System.out.println("unicode to iso-8859-1: " + str);

 

         输出结果:    

unicode to iso-8859-1: ??

 

         在实际应用场景中,往往都会有多次转换,任何环节中出现以上两种情形,就可能出现乱码。

 

三、           解决乱码的基本方法

避免乱码的包括如下的两种基本方法:

1)            正确识别原编码

程序本身是无法准确知道传给自身的字节流的编码是什么,因此,我们需要在整个网络传输或存储的各个转换过程中,都明确在程序或配置中指定原编码以及目标编码。简单的方法是系统涉及每个转换环节,使用一样的原编码和目标编码。

不同语言的编码指定方式是不一样的,例如html通过标签指定、http传输中通过头指定、java中通过设置请求、响应编码指定、vi通过环境编码指定等,具体各种语言或程序指定方式在后续具体场景系列中会详解。

有时虽然中间编码识别错误了多次,但最终读取的汉字是正常的,这是为什么呢?。这是负负得正的效果,只要是中间编码转换没有存在编码信息损失情况,两次对称的错误目标编码会得到正确最终解码。实际应用中,在了解编码本质基础上,我们要规避此类让人困惑的编码设置方式。

2)            选择合适目标编码

转换过程中,避免使用字符集比原编码少的编码作为目标编码。当目标编码不包含原字符时,强行转换时,会出现信息丢失。例如将utf8汉字字符转化为iso-8859-1字符时,由于iso-8859-1没有包含汉字码表,就无法编码,Java语言统一转换为“?”,原字符信息丢失。一旦出现信息丢失,后续转换中即使采取纠正措施(例如负负得正),也无法再恢复原先汉字等原始字符,因此,这种情况下,目标编码应选择字符集中包含了汉字的编码,例如gbk、gb2312、utf8等。

 

四、           总结

本系列(1)介绍了乱码两个根本原因:识别原编码错误和转码信息损失,以及两个根本解决方式:正确识别原编码和选择合适目标编码。但是要解决实际编程中碰到乱码问题,我们还需要了解各类编码的基本知识、具体场景编码配置方式。在后续《编码详解系列》我会继续介绍编码基础知识以及常见的应用场景。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值