编码简述

目录
[隐藏]

最近遇到一次编码问题,就想着了解一下各种编码方案,以为不难,实际上还是花了不短的时间才大致弄清楚了。(从CSDN博客迁过来的,后续还会将自己CSDN博客文章慢慢迁移过来)

1、ISO-8859-1、GB2312、Unicode

最初的计算机只需要表示字母,所以需要的空间很少。对应的一个标准就是 ISO-8859-1 ,这个标准是单字节的,向下兼容 ASCII,编码范围 0x00-0xFF,0x00-0x7F 之间完全和 ASCII 一致,0x80-0x9F 之间是控制字符,0xA0-0xFF 之间是文字字符,主要用来支持欧洲国家的语言。

为了能够让计算机识别中文,那也必须有对应的字符集。最早,中国自己发布了 GB2312,后来微软又在GB2312 的基础上进行扩充就有了 GBK,中国后来又继续扩充字符集,就有了 GB18030。

接着,为了表示日语、韩语、阿拉伯语...等等各种语言,又出现了很多种字符集。但这些字符集都限定在一个较狭窄的范围内,很难跨系统使用,毕竟编码方式不一样。为了统一编码,就有了 Unicode。

1.1 Unicode

Unicode 规定必须使用两个字节,来统一标识所有字符,对于 ASCII 的字符,Unicode 肯定也兼容,只是要扩展到16位,高8位为0。在Unicode中,无论是英文字母还是汉字,都是统一的“一个字符”,也都是统一的“两个字节”。在这里,字符是文化相关符号,字节是存储单元。

Unicode在编码上与其它编码方案并不完全兼容,GBK和Unicode在内码编排上就不同,很难找到对应的规则和算法来转换,转换只能靠查表。

2、区别字符集和编码

看了一些资料后,个人觉得可以简单的理解成:

     字符集仅仅是规定了字符对应的二进制数字的集合,而编码就是规定怎么物理的存储这些二进制数字。

    (Unicode 是一种字符集,UTF-8/UTF-16/UTF-32是字符编码方案)

Unicode 实际上就只是一个字符集,只规定了怎么表示各种字符的二进制数字,但没有规定到底要怎样来存储这些二进制数字。对于英文字母一个字节就够了,但对于某些字符可能需要3个或四个字节来表示,如果统一用4个字节来表示的话,那对于英文字母的存储就浪费太多空间了。

    而 UTF-8 就是一种 Unicode 字符集的实现方式,是在互联网上使用最广的。UTF(Unicode Transfer Format),UTF-8 和 Unicode 并不是直接对应的, 要通过一些算法和规则才能转换。因为 UTF-8 是一种变长的编码方式,可以使用1-4个字节来表示一个符号,根据不同符号而变化字节长度,这样对于不同的字符就能够最大限度地节省存储空间了。具体对应关系可以自行查找。

UTF-8的编码规则很简单,只有二条:

1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。

2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。

下表总结了编码规则,字母x表示可用编码的位。

Unicode符号范围 | UTF-8编码方式
(十六进制) | (二进制)
--------------------+---------------------------------------------
(0000 0000)-(0000 007F) | 0xxxxxxx
(0000 0080)-(0000 07FF) | 110xxxxx 10xxxxxx
(0000 0800)-(0000 FFFF) | 1110xxxx 10xxxxxx 10xxxxxx
(0001 0000)-(0010 FFFF) | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

下面,以汉字“严”为例,演示如何实现UTF-8编码。

已知“严”的 unicode 是4E25(0100111000100101),根据上表,可以发现 4E25 处在第三行的范围内(0000 0800 - 0000 FFFF),因此“严”的 UTF-8编码 需要三个字节,即格式是“1110xxxx 10xxxxxx 10xxxxxx”。然后,从“严”的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,“严”的UTF-8编码是“11100100 10111000 10100101”,转换成十六进制就是E4B8A5。

3、什么是大端序和小端序?对于(EC 8D)

    如果存储时,高位在前,低位在后,那就是大端序(大头方案)(EC 8D,存储时)

    如果存储时,低位在前,高位在后,那就是小端序(小头方案)(8D EC,存储时)

    Unicode规范中定义,每个文件的最前面分别加入一个表示编码顺序的字符,这个字符叫做“zero width no-break space”(零宽度非换行空格),用FFFE表示。

    如果是FE FF,就表示大端序;如果是FF FE,就表示小端序。

4、什么叫带BOM的编码?

前面提到了Unicode只是字符集,有好几种实现它的编码方案,那怎么来区别到底使用的是什么编码呢?

    为了确定一段二进制数字到底是哪一种编码方案。就在文件的最前面保存一个标签,这个标签就叫做BOM(Byte Order Mark,字节序标记)。

    如果BOM是FF FE,那对应的编码就是UTF-16 little endian

    如果BOM是FE FF,那对应的编码就是UTF-16 big endian

    如果是EF BB BF,那对应的编码就是记事本下的UTF-8

    如果没有,则对应ANSI。

4.1 Windows下记事本中的四种编码方式:ANSI、Unicode、Unicode big endain、UTF-8

ANSI 并不是一种具体的编码方案,应该说是一种字符代码吧。是根据系统默认语言来决定的,表示当前系统的遗留编码。如果系统是简体中文,ANSI就是GB2312;如果是繁体中文,ANSI就是Big5;如果是日语,那又是另外一种编码。

    Unicode 其实也并不是 Unicode,实际上是带 BOM 的 小端序的UTF-16 编码,使用两个字节来存储字符的 Unicod e码。只是微软称作 Unicode。

    Unicode big endian 就是带 BOM 的 大端序的UTF-16 编码

    UTF-8 实际上是 带BOM的UTF-8;也被微软简化了,对于程序开发确实还是会有点影响。

上面提到的两点可以很简单的验证,只要下载一个十六进制编辑器,我使用的是UltraEditor。

    首先用记事本提供的4种编码方式分别保存4份文件。

    然后UltraEditor的十六进制功能分别打开。就可以很清楚的看到文件最开始对应的十六进制数。对应关系,就是上面提到的那样。

为了验证记事本下的UTF-8和一般的UTF-8的区别。可以分别用 Notepad++ 的UTF-8无BOM格式编码和UTF-8格式编码创建两个新文件,然后在十六进制编辑器中打开,看前缀的十六进制数是什么样的。

5、编码转换带来的乱码:"锟斤拷"

在GB2312等字符集,向Unicode转换时,总有一些不能很好的转换,或者有一些字符本身就非法的。Unicode规定以U+FFFD当作一个占位符来表示这些字符。那用UTF-8编码它就是EF BF BD,连续多个这样的字节序列出现就成了EF BF BD EF BF BD。如果用UTF-8来解析就是问号,如果用GBK解析,一个汉子两个字节,就变成了“锟斤拷”。

“烫烫烫”和“屯屯屯”

这样的乱码与编码转换无关,出现肯定是开了内存没有初始化。在VC的Debug模式下,会把未初始化的栈内存全部填成0xCC,未初始化的堆内存都填成0xCD。再使用GBK编码,CC CC 就是“烫”,CD CD 就是“屯”。现在回想起以前经常遇到这个乱码现象,但都没有想着要去搞清楚一下,真是太不会学习了。

6、总结

在程序开发中,还是尽量要明确指定所使用的编码方案比较好,这样不容易乱码。

如果是程序相关内容,建议还是用更专业的文本编辑器来编写更好,起码会很明确的告知你使用的到底是什么编码,到底带不带BOM。

使用记事本的话,要很清楚保存的编码格式是什么。

如果要跨平台,跨系统,尽量使用不带BOM的UTF-8。对于PHP来说,带BOM的话可能会有很严重的问题。

虽然花了蛮长的时间,但磨刀不误砍柴工,确实感觉还是收获了很多。

7、参考链接

  1. https://www.zhihu.com/question/20167122
  2. http://jimliu.net/2015/03/07/something-about-encoding-extra
  3. https://www.zhihu.com/question/20650946
  4. http://blog.csdn.net/lvxiangan/article/details/8151670

发表评论

电子邮件地址不会被公开。 必填项已用*标注

To