乱码是个大坑,相信每个人都遇过,而且是个绕不过去的坑。我理解每个程序员都应该写一篇编码相关的博文,梳理自己对这一块的理解,下面是我反复理解多次之后的学习小结。

1、从记事本的不同编码说起:

打开记事本,输入“我我”,保存为ansi编码(其实是gb2312,这也是默认编码)。再分别另存为unicode(其实是utf-16 little endian)、unicodeBigEndian(其实是utf-16 big endian)、utf8,用UltraEdit打开,切换到二进制模式,内容如下:

编码 内容
ansi CE D2 | CE D2
unicode FF FE | 11 62 | 11 62
unicode big endian FE FF | 62 11 | 62 11
utf-8 EF BB BF | E6 88 91 | E6 88 91

可以看出,“我”在gb2312里的编码是CE D2,在utf-16 little endian里是11 62,在utf-8里是E6 88 91。FF FE是文件头,用来标识这个文件是unicode little endian格式的,同理,EF BB BF标识文件是utf-8格式。所谓的endian,是字节顺序,不同的操作系统,可能小字节在前、也可能大字节在前,既然不一样,就用一个文件头(学名叫BOM,byte order mark,字节顺序标识符)来标识。具体方法是:找一个在unicode里不存在的字符(FF FE)来表示,EF BB BF是FF FE在utf-8里的编码。各种编码的BOM详见下表:

bytes encoding
EF BB BF utf-8
FF FE utf-16 little endian
FE FF utf-16 big endian
FF FE 00 00 utf-32 little endian
00 00 FE FF utf-32 big endian

2、乱码的各种现象

文本保存在文件里都是字节byte,那这些byte数组是怎么显示为具体的字符呢?以ansi格式的“我”为例,CE D2显示在界面上的步骤是这样的:windows首先将CE D2转换成它内部使用的编码格式unicode,然后按照unicode编码去字体文件中查找字体图像,最后将图像画在窗口的指定位置上。如下:

  1. 字符首先以某种编码保存在文件中。
  2. windows将文件中的编码映射为unicode编码。
  3. windows根据unicode编码去字体文件中查找字体图像,并画在窗口上。

这3步中的每一步错了,都体现为一种典型的乱码。

  1. 错误1:如果弄错了编码格式,比如将“我”的utf-16编码11 62错存成了gb2312的文件,就会出现乱码。
  2. 错误2:如果映射到unicode出错,例如出现了unicode里未定义的字符编码,windows就会使用缺省字符,通常是?。比如“我”的gb2312编码CE D2在unicode里尚未使用,所以映射不到任何一个有效的unicode编码。
  3. 错误3:如果映射到了unicode编码,但在字体文件中找不到对应的字符,windows就会显示字体文件中的缺省图像:空白或方格。

3、各种编码ascii、ansi(gb2312/big5/...)、unicode(utf8/16/32/...)

1) ascii用1个字节(共255个)表示所有英文字符。缺点很明显,就是不够用。对于欧洲那些表音的字母类,就已经捉襟见肘了,再遇到中日韩的表义字符动辄上万,就更不够了。于是ansi想出来填坑,但结果是反而把事情搞麻烦了。其实如果一开始就想到字符不够用的问题,再直接整出个全球统一字符集unicode,就不会有这个大坑了。所以说白了,乱码问题是个历史原因造成的问题(话说软件里那么大坑,哪些不是历史原因造成的!Y2K也是一例,但是谁也没那个远见,所以只能迂回前进了)。

2) 然后,各国都发现ascii不够用,于是各自造出自己的编码来满足需要,中国造出gb2312、台湾造出big5大五码、日本造出shift_jis、其他阿拉伯国家、印度也是类似的。先是IBM弄了代码页(CodePage)、再是微软继承了这一套再加了些自己的定义,整成了ansi,包括所有地区的代码页。通过这些代码页,windows可以实现各地区编码与unicode之间的相互转换。(注:代码页的具体文件为:c:\windows\system32\*.nls文件)代码页是为了兼容已有的程序而存在的,如果我们能强制所有程序都转到unicode,那各地区的ansi编码也就没有存在的必要了,说白了只是发展过程中的中间产物。

3) 最后是unicode。

3.1) 先说说字符集与字体编码的区别。对大多数字符集来说,字符集里的编码 = 字符编码。比如“我”字,在gb2312中的编码是CE D2,在big5中是A7 DA。唯独在Unicode里例外,Unicode使用4个字节/32位(实际只用了31位,最高位必须为0)唯一表示全世界所有可能的字符。unicode的容量为231-1 ≈ 21亿个,截止到2005年只用了10万个,所以应该足够用了。但每个字符4字节,太浪费空间了,于是就产生了各种编码方式,常见的有utf-8、utf-16、utf-32等。这里,unicode是字符集,utf-8/16/32是字符编码。同样是“我”字,在unicode里是00 00 62 11,在utf-8里是E6 88 91,在utf-16里是62 11,在utf-32里才是00 00 62 11。

3.2) unicode到utf8/16/32的转换方式就不详述了。无非又是一些位操作,对理解这个问题的主干无益。有兴趣的可以参看本文最后的引用。

4、对比一下各编码的存储效率

  1. 存储英文时:utf-8 = ansi > utf-16 > utf-32
  2. 存储中文时:ansi ≈ utf-16 > utf-8 > utf-32

当然,这里说的是大多数的情况,中英文混杂、多语言混杂、小语种文本都需要具体讨论。

5、参考

  1. 伐木丁丁鸟鸣嘤嘤:《浅谈文字编码和Unicode(上)》和他做的小工具CodeView:快速查看文本编码
  2. 阮一峰:《字符编码笔记:ASCII,Unicode和UTF-8
  3. 中韩翻译网 金圣镇:《字体编辑用中日韩汉字Unicode编码表

字符集与字符编码 (charset & encoding)的更多相关文章

  1. Java应用开发中的字符集与字符编码

    事出有因 在向HttpURLConnection的输出流写入内容时,因没有设置charset,导致接收方对数据的验签不一致. URL url = new URL(requestUrl); //打开连接 ...

  2. 关于Unicode,字符集,字符编码,每个程序员都应该知道的事

    关于Unicode,字符集,字符编码,每个程序员都应该知道的事 作者:Jack47 李笑来的文章如何判断一个人是否聪明?中提到: 必要.清晰.且准确的概念,是一切思考的基石.所谓思考,很大程度上,就是 ...

  3. 刨根究底字符编码之十——Unicode字符集的字符编码方式CEF

    Unicode字符集的字符编码方式CEF 一.字符编码方式CEF的选择 1. 由于Unicode字符集非常大,有些字符的编号(码点值)需要两个或两个以上字节来表示,而要对这样的编号进行编码,也必须使用 ...

  4. 关于Unicode,字符集,字符编码

    基本概念 字符[character] 字符代表了字母表中的字符,标点符号和其他的一些符号.在计算机中,文本是由字符组成的. 字符集合[character set] 由一套用于特定用途的字符组成,例如支 ...

  5. [转]字符集、字符编码、XML中的中文编码

    字符集.字符编码.XML中的中文编码 作为程序员的你是不是对于ASCII .UNICODE.GB2321.UTF-7.UTF-8等等不时出现在你面前的这些有着奇怪意义的词感到很讨厌呢,是不是总觉得好象 ...

  6. 字符集、字符编码、XML中的中文编码

    字符集.字符编码.XML中的中文编码 作为程序员的你是不是对于ASCII .UNICODE.GB2321.UTF-7.UTF-8等等不时出现在你面前的这些有着奇怪意义的词感到很讨厌呢,是不是总觉得好象 ...

  7. 字符集、字符编码、国际化、本地化简要总结(UNICODE/UTF/ASCII/GB2312/GBK/GB18030)

    PS:要转载请注明出处,本人版权所有. PS: 这个只是基于<我自己>的理解, 如果和你的原则及想法相冲突,请谅解,勿喷. 环境说明   普通的linux 和 普通的windows.    ...

  8. C# 字符编码类Encoding

    在网络通信中,很多情况下都是将字符信息转成字节序列进行传输.将字符序列转为字节序列的过程称为编码.当这些字节传送到接收方,接收方需要逆向将字节序列转为字符序列.这个过程就是解码. 常见编码有ASCII ...

  9. PythonStudy——字符编码 Character Encoding

    测试一下学习字符编码的问题:解决乱码问题 数据 从 硬盘 => 内存 => cpu应用程序打开文本文件的三步骤1.打开应用程序2.将数据加载到内存中3.cpu将内存中的数据直接翻译成字符显 ...

随机推荐

  1. 僵尸进程学习 & 进程状态列表 & Linux信号学习

    参考这篇文章: http://www.mike.org.cn/articles/treatment-of-zombie-processes-under-linux/ 在Linux进程的状态中,僵尸进程 ...

  2. iOS开发之 Xcode 5 下让你的应用在不同状态(debug, release)有不同的图标

    http://nickcheng.com/post/unique-icons-for-your-app-in-different-state-in-xcode5-debug-release 应用在发布 ...

  3. 转:Unicode汉字编码表

    转自:http://blog.csdn.net/huangxy10/article/details/10012119 Unicode汉字编码表 1 Unicode编码表  Unicode只有一个字符集 ...

  4. JavaScript数学揭密之函数与勾股定理

    一.函数 function show(n){ return n*2; } alert( show(2) ); alert( show(3) ); alert( show(4) ); 二.勾股定理 1. ...

  5. 转!!!Mysql无法创建外键的原因

    在Mysql中创建外键时,经常会遇到问题而失败,这是因为Mysql中还有很多细节需要我们去留意,我自己总结并查阅资料后列出了以下几种常见原因. 1.  两个字段的类型或者大小不严格匹配.例如,如果一个 ...

  6. ScrollVIew 边界阴影效果

    一.删除android ScrollView边界阴影方法方法 1) 在xml中添加:android:fadingEdge=”none” 2) 代码中添加:ScrollView.setHorizonta ...

  7. iOS 静态类库 打包 C,C++文件及和OC混编

    iOS 静态类库 编译 C,C++ 我们都知道,OC 原生支持C, 在 创建的 OC类的 .m 里面,可以直接编写C的代码: 同样 Xcode 也支持 OC ,C++的混编,此时,我们通常把OC创建的 ...

  8. worker中加载本地文件报错的解决方案

    如果在一个swf的主线程中加载文件时,报安全沙箱的错误, 网上有诸多的解决方案.但是如果在一个worker中加载本地文件报类似如下的错误: *** 安全沙箱冲突 *** SecurityError: ...

  9. androidStudio中如何加载字体资源?

    在android中字体的格式总是不能尽善尽美的显示出来 ,  于是要求我们使用一些有美感的字体,加载的方式(就像HTML的字体一样),我们需要通过加载字体的方式来使用android中不曾提供的字体; ...

  10. ARM指令集(下)

    A.2.5   ARM 协处理器指令         ARM 支持协处理器操作,协处理器的控制要通过协处理器命令实现.表A-7给出全部的ARM协处理器指令. 表A-7  ARM 协处理器指令 CDP ...