重要提示:本文并非学术文章,本人也并非语言和文字学领域人士,只是出于好奇心,根据自己的理解写下这篇文章。本文的参考文档都来源于互联网,而且并未一一考证其准确性和权威性,因此本文仅供参考。

字符集和字符编码的问题一直困扰着我,之间曾经多次尝试把这个问题理解清楚,但始终由于有些细节问题无法自圆其说因而放弃。网上的资料多数描述过于简单,又或者作者本人对问题也了解不深入,容易产生误导。最近我终于下定决心将之前对“乱码”问题的思考更进一步,否则将始终是一丝遗憾。这里也不得不感叹,老外的“科普”做的好啊,网上有很多质量相当高的文章,表述严密,引用充分,例证丰富,我相信在国内各领域的专家也不少,计算机和语言学方面都有很多有建树的大牛,也许是太忙吧。对我个人而言,最重要的一片文章是“Character set encoding basics”,在本文最后有链接的地址,本人的翻译版本在这里:http://blog.chinaunix.net/space.php?uid=11187&do=blog&id=3034493

1.字符集的基本概念

什么是字符集?什么是字符编码?

按照“Character set encoding basics”文中的定义,字符集的编码模型分为以下4个层次

1)抽象字符清单Abstract character repertoire (ACR),无序,无编码;

2)已编码字符集Coded character set (CCS),有序,有编码;

3)字符编码规则Character encoding form (CEF),有序,有编码;

4)字符编码方案Character encoding scheme (CES),有序,有编码,有传输和储存规则(字节序);

这种分层方式,比较偏于学术化,不太容易理解。按我个人的理解,GB2312/GBK/GB18030/ASCII这些字符集编码规则,由于都基于8-bit字节,是属于前三层的,可以认为是三层合一。如果拿Unicode来说明的话,Unicode中定义的所有字符的集合,是第一层;我们通常说的Unicode编码,是指的第二层;现在最常见的UTF-8,是指的第三层。当UCS-4在以8-bit为基础的计算机中存储和传输时,就要涉及字节序的问题,就是第四层,分为big-endian和little-endian。

借用“程序员趣味读物:谈谈Unicode编码”中举的一个记事本例子(内容不同):

1)打开记事本(windows自带的那个),输入“我”;

2)另存为我_ansi.txt,注意,编码选择“ANSI”;

3)另存为我_unicode.txt,注意,编码选择“Unicode”;

4)另存为我_unicode_big.txt,注意,编码选择“Unicode Big Endian”;

5)另存为我_utf8.txt,注意,编码选择“UTF-8”;

保存完以后,看一下4个文件的大小,很有意思吧,分别是2/4/4/6个字节,再用二进制方式(推荐使用ultraedit)查看一下其中的内容:(高位字节在前)

1)ansi:CE D2

2)unicode:FF FE 11 62

3)unicode_big:FE FF 62 11

4)UTF-8:EF BB BF E6 88 91

第一个文件,ansi,比较好解释,2字节,就是GB2312/GBK/GB18030编码,即简体中文windows的默认内码

第二个文件,unicode,就是Unicode编码,“我”的编码是0x620x11,不过前面多了2字节的前导符,FF FE,表示为little-endian

第三个文件,unicode_big,也是Unicode编码,不过前导符变为FE FF,表示big-endian

第四个文件,UTF-8,是在Unicode基础上的二次编码,分别将FE FF(big-endian)和6211进行了二次编码,详细编码过程参见“程序员趣味读物:谈谈Unicode编码”

常见字符集(字符编码规则)

ASCII,读作阿斯克码,7bit表示,美国国家标准信息编码,是最常用英文字母和符号、数字的集合及编码;它的常见别名是ISO 8859-1,Latin1

EASCII,扩展ASCII码,完整的利用一个字节,在ASCII的基础上扩展了一些不常用字符

GB2312,国标中文字符编码,1980年制定并颁布;

GBK,国标码,1995年

GB18030,国标码,2000年

以上这三个编码标准都是向下兼容的,兼容的意思有两方面,其一是指字符的集合,其二是指编码。另外,在微软操作系统中(其实也影响到了Linux领域),经常出现“代码页”(code page)的概念,这些代码页,只是微软自己的定义,可以理解为CP936=GBK。

Unicode,UTF-8,我原来一直以为这两个东东是一回事,后来发现其实理解错了,UTF-8可以理解为是以Unicode为基础进行二次编码的,详见这篇文章:“程序员趣味读物:谈谈Unicode编码”,http://pcedu.pconline.com.cn/empolder/gj/other/0505/616631_1.html

2.关于乱码的思考

什么是乱码

个人认为,如果在储存或传输过程中,计算机中的信息不能被正常解析,从而导致在信息展示的时候出现无法被正确理解的情况,可以认为出现了“乱码”。常见的乱码有两种表现形式:

1)部分中文字符能够正常展示,另外的中文字符被展示为方框;

这种情况多数是由于缺少相应的字体支持,例如,在虚拟机上安装完linux之后,如果没有安装图形界面,默认的字符窗口其实是没有相应的字体支持的,这时的中文只能显示为方框,安装zhcon以后才能够正常展示GBK/UTF8的中文字符。

还有一个场景,部分网页上的字符,并不能被所有浏览器支持,或者该浏览器对某种编码方式的支持不完整,会出现部分字符展示为方框的情况。

另外,如果能够以GBK/GB2312正常展示的网页,如果手工将encoding变更为utf-8,则所有中文字符都会变成方框。

2)几乎所有字符都不能正常展示,许多字符被显示为“?”,或者被显示为一大堆不可理解的古怪字符;

这种情况很可能是由于字符编码不配套,需要具体分析。例如,在浏览器中能够正常显示的页面,如果将其编码更改为其他不兼容的编码,则很多会展示为“?”和乱七八糟字符的组合

乱码产生的原因

产生乱码的原因很复杂,也正是这个原因导致了对乱码问题的分析很难全面和彻底。但是,综合我目前遇到的乱码问题来看,只要将字符展示的过程剖析清楚,一段段的调整,总能找到解决的办法。

字符在计算机中,都是以二进制的方式进行存储的,而且文本本身是不能够标识它使用的编码方式的。也就是说,同一段二进制字节流,可以用很多种不同的编码方式去解码,然后根据解码后的结果(也是二进制字节流),在操作系统中按照预定义好的字体进行展示。所谓字体库,或者字库,其实就是数字和相应展示方式(点阵、truetype等)的组合。计算机本身是不会“体会”到“乱码”的发生的,它只是按照用户选定的字体,根据不同的数字进行展示而已,无论展示的结果如何,都只有人才能判断“乱码”与否。乱码的产生,其实只有两个原因,一是没有使用正确的解码规则来解释字节流,二是使用了错误的展示字体。实际应用当中,编码规则的问题居多。

单字节的编码通常情况下不会出现乱码的问题,特别是英文字符,而双字节由于多数情况下编码规则复杂,另外存在中间截断的问题,会比较复杂。从产生问题的渠道来看,常见的有以下几类:

1)网页展示乱码

多数情况下,可以通过更改页面编码方式来解决。少数情况下,浏览器本身处理多语言字符集有缺陷的时候,无论怎样修改编码方式,都不能彻底解决乱码问题。例如,截至本文定稿,IE9就存在部分UTF-8中文编码无法解析的问题,同样的网页在Chrome和firefox中都没有问题。

2)UNIX/LINUX终端显示乱码

2.1)终端的中文环境;

如果没有合适的中文环境(字库支持),无论解码方式如何正确,也不可能正常展示中文。在常用的终端工具中,例如:Xshell/Secure CRT/Putty,都可以设置终端的字符编解码方式,通常设置的值有两个系列:

其一,GB2312/GBK/GB18030/CP936/ANSI/Default等,其实都是兼容的编码,或者仅仅是名称不一样;

其二,UTF-8,这个是在互联网上最常见的编解码方式了;

另外,如果不是windows下的终端工具,而是系统自身的字符终端,则可以安装字符终端专用的中文环境,例如linux下的zhcon

2.2)cat显示文本文档内容

通过类似cat命令的方式显示纯文本文档的内容,通常只受一个因素的影响,即终端的工具的字符编码方式,常用工具中都可以进行设置。只要文本内容的编码方式与终端的编码方式一致(或兼容),则一定不会出现乱码。

2.3)命令行的中文提示(CLI)

命令行接口Command Line Interface的提示语言,是通过环境变量进行设置的,好几个变量都可以设置,但优先级有区别,其中LC_ALL > LC_XX > LANG,如果想用中文显示提示信息,可以这样设置:

export LC_ALL=zh_CN.gbk

其中zh表示使用中文输出提示信息,gbk表示使用GBK编码方式输出中文提示信息,这个编码方式要与终端的设置一致或者兼容才可以正常显示;

2.4)输入中文信息

从Shell环境输入中文,与vi/vim这种编辑器的情况稍有不同,编辑器的情况放到下一节说明。按照一般的理解(我原来就是这样理解的),只要能正常显示中文的地方,一定能够正常输入中文。但是,实测的情况略有不同,详见下面的表格。

输入中文信息,我暂时只考虑了以下三种情况:

a)在SHELL命令行中输入中文

这种场景下,如果终端字符集是GBK,LC_ALL为UTF-8时,输入的中文字节流乱序(第一个中文字符的高字节被放到字节流的末尾),无法正常展示。

b)使用cat等方式输入中文,并重定向到文件中

这种场景下,任何时候,都能够正常输入中文

c)使用文本编辑器,详见下一节描述

与仅仅显示中文信息不同,输入中文的时候实际上经历了更多的步骤。最开始从终端工具中输入中文编码字节流,然后经过网络协议传输到服务端,服务端收到字节流以后,根据终端设置的情况,再推送显示信息到终端工具,终端工具进行呈现。在SHELL命令行中输入中文不正常的情况,很有可能是由于服务端的处理逻辑不健全。

2.5)文本编辑器,例如vim

文本编辑器的种类很多,emacs/vi等,vi的版本也很多,各个主流UNIX平台的商业版本实现都不相同,还有vim。本文暂以vim为例子进行说明,其他编辑器的情况应该是类似的。可以参考:“让vim认识更多的编码”,http://blog.chinaunix.net/space.php?uid=20147410&do=blog&id=3018800。

遗憾的是,这篇文章并未给出vim处理这三个内部变量的顺序,经摸索,顺序应该为:

1)fileencoding

2)endocing

3)termencoding

这三个内部变量均可以通过set xxx=xxx的方式进行设置,并可以通过set和set all进行查看。另外,上面提到的博文中是使用LANG变量来改变vim的encoding变量值,但由于LANG的优先级最低,实际使用过程中,使用LC_ALL的效果最好,当然其实也可以直接在vim中使用set进行设置。

个人理解,如果仅仅从输入和输出(显示)的角度来看,其实vim等文本编辑工具并没有必要设置3个不同的变量来进行处理,这大概也是大多数商用unix平台的vi版本都没有类似设置的原因。提供3个变量的原因在于,vim试图提供一些编码转换的方式,例如,通过设置fileencoding变量,可以改变vim写入和读出使用的编码,而termencoding仅仅改变显示时使用的编码方式,而encoding其实只是提供缓冲。这与数据库的字符集(编码)处理方式是类似的。也可以这样理解,无论这三个变量设置为何值,其实并不见得不会影响数据的输入和展示。例如,在我们输入输出中文信息的时候,即便fileencoding=encoding=termencoding=iso8859(英文字符集),只要文本文件的编码方式与终端的编码方式一致(兼容),比如都是GB2312,文本信息都可以正常展示和输入。

附表:不同设置情况下的中文显示结果

服务端环境:ubuntu 11.10 服务器版

客户端环境:中文win7+Xshell 4

文件编码

终端字符集

LC_ALL

cat输出

SHELL输入

VIM相关

备注

vim显示

vim输入

encoding

fileencoding

termencoding

GBK

GBK

GBK

正常

正常

正常

正常

GBK

GBK

UTF-8

GBK

GBK

乱码

正常

正常

GBK

UTF-8

GBK

UTF-8

GBK

乱码

正常

乱码

乱码

GBK

如果将encoding或termencoding改为utf-8,则可以正常显示

UTF-8

UTF-8

GBK

正常

乱码

乱码

GBK

UTF-8

如果将encoding或termencoding改为utf-8,则可以正常显示

GBK

GBK

UTF-8

正常

乱码

乱码

乱码

UTF-8

latin1

如果将encoding或termencoding改为latin1,则可以正常显示;或者将encoding和fileencoding改为GBK(cp936)

UTF-8

GBK

UTF-8

乱码

乱码

乱码

UTF-8

utf-8

如果将encoding或termencoding改为cp936,则可以正常显示

GBK

UTF-8

UTF-8

乱码

正常

乱码

乱码

UTF-8

latin1

如果将fileencoding改为cp936,则可以正常显示

UTF-8

UTF-8

UTF-8

正常

正常

正常

UTF-8

3)数据库乱码

数据库中与编码/字符集相关的设置主要有两个,一个是数据库本身的编码,另一个是客户端环境的编码。网上有很多关于数据库乱码问题的讨论,多数并没有涉及到问题的本质。数据库中保存的数据,其实与文件方式保存的数据没有什么两样,都只是字节流而已,而字节流本身通常是不能自我标识的,例如,如果仅仅根据二进制的编码,无法判断出它的内容是采用GB2312编码,还是EASCII编码,或者是一个图像信息。也许,正是由于字节流无法标识自己,因此需要有一个参数来标识数据库使用的文字编码。在客户端与服务端的编码设置统一的时候,无论在数据库的字段中存储什么样的数据,都是不影响数据的储存和展示的,原因是,不会发生编码转换。

例如,网上很多帖子讨论到乱码问题的时候,给出的建议都是,将数据库的字符集设置为utf-8,这当然不会有什么问题,utf-8编码是被最广泛使用的编码标准,所以支持也相当完备,特别是utf-8编码几乎可以被所有软件“识别”出来(特征码)。这样一来,实际上掩盖了编码的问题。其实,如果仅仅为了储存和展示中文信息,将数据库的字符集设置为iso-8859-1(单字节)编码,客户端的语言环境也设置为同样的编码方式,存取中文数据,也不会有任何乱码的情况发生。之所以产生乱码,是由于在某些地方出现了编码方式的不匹配。

比如,数据库的编码设置为GBK,但是客户端的设置为UTF-8,那么如果在客户端使用UTF-8的编码方式输入中文数据,当客户端软件发现这种不一致时,会执行从UTF-8到GBK的编码转换,然后通过网络插入到数据库的具体字段中。当这段数据被读取时,如果客户端的设置为UTF-8,那么同样要发生GBK到UTF-8的转换,最终以UTF-8的形式展示数据。但是,如果数据被读取时,客户端的设置为GBK,则数据无需转换就可以以GBK的形式直接呈现,然而,如果客户端是设置为UTF-8编码的网页,但使用GBK方式访问数据库,那么数据被最终呈现时就会出现乱码。

总之,数据库提供设置数据库和客户端编码方式的选项,只是为了更好的提供编码转换工作,并不是必需的,无论设置成何种编码方式,与实际存储在字段中的数据都没有必然联系,只是会在编码转换的时候提供方便,否则,这些转换工作就只能完全交给客户端来完成。

3.尚未解决的疑问

1)关于windows剪贴板的实现机制中,是否包括了编码转换

从现象上来看,当从一个ansi编码的文本中拷贝中文字符,再到utf-8编码的文本中进行粘贴,没有出现乱码,但是这两种编码方式是不同的,也就是说,必然在这个过程中出现了编码转换,个人怀疑是利用剪贴板进行复制的时候,进行了编码转换,将复制的文本保存为操作系统内码,然后粘贴的时候由应用程序进行内码到utf-8编码的转换,完成粘贴。

2)输入法输入不同编码的文本时,采取什么机制?

当打开一个cp936编码的文件进行编辑时,输入法的输出是cp936编码的,但打开一个utf-8编码文件进行编辑时,输入法的输出变成了utf-8的,输入法是如何知道什么时候应该使用什么编码的?个人猜想,有可能输入法的输出只是操作系统的内码,在文本编辑器中进行内码到其他编码的转换。

4.参考资料

4)“Java应用中的汉字乱码问题分析”,计算机技术与发展,2006年1月第1期,刘长生等

ccs 中文乱码_关于字符集和乱码的思考相关推荐

  1. idea console中文乱码_Python3的字符编码乱码问题解决思路

    在乱码问题上,Python3相比Python2已经好多了,但在处理外来字符时比如文件或者网站时还是会出现乱码问题. 乱码的原因很多,一个是来源的字符编码在接收时处理不当,编程语言默认的UTF8处理gb ...

  2. gdb中文乱码_关于中文和乱码

    VS Code输出中文会出现乱码,很多人都遇到过.这是因为源代码默认是UTF-8编码,cmd/PowerShell是GBK编码.直接编译,会把"你好"输出成"浣犲ソ&qu ...

  3. confluence mysql 中文乱码_解决confluence的乱码问题

    使用confluence时发现一些含有中文的页面中,中文都变成了问号. 继续搜索解决方案,发现时数据库中数据的格式不对, 在mysql中输入以下命令: mysql> show variables ...

  4. 日志中出现乱码_合宙Luat | 乱码搞得一团糟?开源神器帮你轻松修复

    鎰熸仼鐩搁亣 缇庡ソ鐨勬椂鍏変笌澶у鐩镐即 2021锛屾柊骞村揩涔愶紒 相信大家在日常生活中,都见过类似上面的字符串.这些看起来不明所以的内容,通常被称作乱码. 那么乱码是如何产生的,并且如何修复呢 ...

  5. 网页突然乱码_打开网页出现乱码怎么办 打开网页出现乱码解决方法【详解】...

    时代不断发展,科技不断进步,电脑已经成为我们日常生活中不可取代的通讯工具,它可以让我们及时的浏览到世界各地的信息,也可以让我们及时得出里自己的工作文件,极大的方便了人们的生活,但电脑叶总实惠出现各种各 ...

  6. soapui 乱码_接口测试-soapui-中文乱码总结

    可能方案一: 1.在方法的属性框中,选择get还是post方式,这个对请求的参数编码是有影响的: 1.png 2.在请求的属性框中,将 post QueryString 打钩,这个对请求参数采用pos ...

  7. 试题导入mysql乱码_解决Mysql导入乱码问题

    解决Mysql导入乱码问题 方法一: 通过增加参数 –default-character-set = utf8 解决乱码问题 C:\Users\Administrator>mysql -uroo ...

  8. 数据库工作笔记002---新建mysql数据库的时候_对字符集和排序规则的选择

    技术交流QQ群[JAVA,C++,Python,.NET,BigData,AI]:170933152 以前都选择utf8,习惯了,后来发现,有些,小表情类的字符用utf8是存不下的,后来 就改成mb4 ...

  9. 抓包mysql乱码_抓包数据乱码是什么情况?

    为什么会出现这种情况?细心的童鞋可能发现是我们发送给服务器的请求连接的数据不同: 第一张图的信息是{"roomid":98284,"uid":271298361 ...

  10. t3显示乱码_打开T3出现乱码是什么原因,如下图

    用友通普及版安装说明一.安装前注意问题 为确保系统安装成功,请注意以下问题: 1.安装时操作系统所在的磁盘分区剩余磁盘空间应大于500MB,用友T3软件所安装在目标磁盘空间应大于2GB. 2.安装产品 ...

最新文章

  1. 基于ssh的ktv预定管理系统
  2. RxJava Timer:代替handler
  3. chrome浏览器本地文件支持ajax请求的解决方法
  4. Extjs Window用法详解 3 打印具体应用,是否关掉打印预览的界面
  5. SAP Fiori Elements - How complex binding defined in XML view is parsed
  6. 在linux上获得线程id的方法
  7. volatile学习
  8. Redux 主要知识学习总结
  9. Android自定义View之刻度尺
  10. matlab写入二进制文件注意事项
  11. 控件无法安装的问题-Unable to execute file
  12. wordpress网站提示“建立数据库连接时出错”
  13. WBE前端笔记1:HTML中一些不熟悉的标签
  14. 传奇服务器二级密保信息存档,找回玩家账号和二级密码,修改密码和密保
  15. ios版的chrome如何保存网页为PDF
  16. Linux cp 复制文件强制覆盖的用法
  17. 2018 蓝桥杯 C++ A组 1-7
  18. 计算机重装后如何添加打印机,系统重装后,电脑无法连接打印机怎么办?
  19. 吉他箱体模拟效果器插件-Positive Grid BIAS FX 2 DeskTop 2.3.0.6070 Elite WiN
  20. AM335x片上ecap驱动移植

热门文章

  1. 4g网络什么时候淘汰_4G升级5G,4G网络不会被淘汰,与5G继续共存
  2. mysql5.7 64位linux 安装,Linux安装64位Mysql5.7
  3. 谷歌搜索引擎使用语法大全收集
  4. 基于Android Studio游戏开发 飞机大战小游戏
  5. python做购物车代码大全-Python实现一个简单的购物车程序
  6. java2实用教程第六版习题答案
  7. iocomp控件 Crack V512-sp6
  8. python股票自动交易系统_怎样用 Python 写一个股票自动交易的程序
  9. MySQL命令行客户端(一)客户端选项
  10. ofo在MaxCompute的大数据开发之路