Introduction of Character Encoding

字符编码


1. 为什么需要编解码

通俗的说就是计算机语言与人类语言的不一致性衍生了编解码这种技术。 编码就是指我们按照某种规则将人类的字符存储在计算机中。同理,解码则是指将存储在计算机中的二进制数解析显示出来。

在解码过程中,如果使用了错误的解码规则,则导致’a’解析成’b’或者乱码。所以,制定一套通用的编码模型非常必要,接下来看看编码的发展历史。

2. 编码发展历史

先说说常见的字符集有哪些,目前有:ASCII字符集、GBK字符集、BIG5字符集、GB18030字符集、Unicode字符集等。尽管之前知道这些,但是对其产出背景和顺序毫无概念,接下来介绍下字符编码的大概历史。

字符编码历史大致分为三个阶段

  • ASCII阶段

全名为American Standard Code for Information Interchange,翻译出来为:“美国信息交换标准代码”。 刚开始只支持英语,其他语言不能够在计算机上存储和显示。使用一个字节来存一个字符。

  • ANSI编码(本地化)

上面的ASCII码由于只支持英语,随着计算机在各个国家的普及,这种字符集显然不能满足发展需求。为了让计算机支持更多语言,通过使用0x80~0xFF范围的2个字节来表示1个字符。比如:汉字 ‘中’ 在中文操作系统中,使用 [0xD6,0xD0] 这两个字节存储。 在这个阶段,不同的国家和地区制定了不同的标准,由此产生了各种各样的编码标准,如GB2312、BIG5、JIS等。 这些使用两个字节来表示一个字符的各种汉字延伸编码方式,称为ANSI编码。在简体中文系统下,ANSI 编码代表 GB2312 编码;在日文操作系统下,ANSI 编码代表 JIS 编码。
不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。

  • Unicode阶段(国际化)

在ANSI阶段,编码字符集比较多,各国编码也不统一。为了使国际间信息交流更加方便,国际组织制定了unicode字符集,为各种语言中的每一个字符设定了统一并且唯一的数字编号,以满足跨语言、跨平台进行文本转换、处理的要求。接下来的关系梳理详细介绍平时我们所说的各种名词的意义及其所处阶段。

3.关系梳理

在梳理前,把我脑海里能浮现的相关名词都列出来一遍: ASCII/GBK/BIG5/ANSI/GB18030/GB2310/UTF-7/UTF-8/UTF-16/UTF-32/Unicode/UCS/JIS 目前能想到的就这些。虽然已经了解了字符发展的三个阶段,但是面对这么多名词,还是有点晕。现在梳理如下:

  • ASCII阶段
    • ASCII编码
  • ANSI阶段
    • GBK
    • GB18030
    • GB2310
    • BIG5
    • JIS
  • Unicode阶段
    • Unicode
    • UCS
      • UCS-2
      • UCS-4
    • UTF
      • UTF-7
      • UTF-8
      • UTF-16
      • UTF-32

上述的组织结构图,可以基本表示名词之间的关系。为了更清楚理解一些字符集,针对一些典型的名词作解释。 目光拉到unicode阶段,被很多人称之为伟大的阶段。这个阶段的unicode和ucs解释如下:

  • Unicode(统一码、万国码、单一码、标准万国码)是业界的一种标准,它可以使计算机得以体现世界上数十种文字的系统。
  • UCS(Universal Character Set,通用字符集)是由ISO制定的ISO/IEC 10646标准所定义的标准字符集。

既然两个都很通用都很标准,该选择哪个?接下来是给出的发展走向(很好的结合,开发者的福音): 历史上存在两个独立的尝试创立单一字符集的组织,即国际标准化组织(ISO)和多语言软件制造商组成的统一码联盟(unicode.org),由美国的HP、Microsoft、IBM、Apple等几家知名的大型计算机企业所组成的联盟集团。前者开发的 ISO/IEC 10646 项目,后者开发的Unicode项目。因此最初制定了不同的标准。

1991年前后,两个项目的参与者都认识到,世界不需要两个不兼容的字符集。于是,它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。从Unicode 2.0开始,Unicode采用了与ISO 10646-1相同的字库和字码;ISO也承诺,ISO 10646将不会替超出U+10FFFF的UCS-4编码赋值,以使得两者保持一致。两个项目仍都存在,并独立地公布各自的标准。但统一码联盟和ISO/IEC都同意保持两者标准的码表兼容,并紧密地共同调整任何未来的扩展。就内容而言,Unicode和UCS是一致的,并行的。

看完这些,忍不住想知道UTF和UCS还有Unicode的关系是什么,这部分留在下面介绍。

4.utf-8编码规则

有了上面内容做铺垫,走到UTF-8,想说我们已经从编码标准走到编码方案了!这块会详细介绍UTF-8的编码方案。在介绍UTF编码方案前,还是需要对上面的Unicode编码标准再做下具体的解释。

Unicode只与ASCII兼容(更准确地说,是与ISO-8859-1兼容),与GB码不兼容ISO 10646定义了一个31位的字符集。 在这巨大的编码空间中,迄今为止,只分配了前65534个码位 (0x0000 到 0xFFFD)。被编码在16位BMP以外的字符都属于非常特殊的字符(比如象形文字),且只有专家在历史和科学领域里才会用到它们。 中、日、韩的三种文字占用了Unicode中0x3000到0x9FFF的部分 Unicode目前普遍采用的是UCS-2(2字节),即用两个字节来编码一个字符。事实上Unicode对汉字支持不怎么好,因为简体和繁体总共有六七万个汉字,而UCS-2最多能表示65536个,所以Unicode只能排除一些几乎不用的汉字,好在常用的简体汉字也不过七千多个,为了能表示所有汉字,Unicode也有UCS-4规范,就是用 4个字节来编码字符。 Unicode只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。比如,汉字”严”的Unicode是十六进制数4E25,转换成二进制数足足有15位(100111000100101),也就是说这个符号的表示至少需要2个字节。表示其他更大的符号码可能需要3个字节或者4个字节,甚至更多。 这里就有两个严重的问题: 第一个问题是,如何才能区别Unicode和ASCII?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢? 第二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果Unicode统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的。它们造成的结果是出现了Unicode的多种存储方式,也就是说有许多种不同的二进制格式,可以用来表示Unicode,即UTF。

上述这段加强了我们对unicode编码规范的理解。同时抛出的两个问题,也预示了UTF编码方案出现的必然性。 接下来,我们就通过组织结构图的形式,介绍UTF-8编码方案,来理解UTF如何解决这两个问题。

  • UTF-8基本概念
    • 一种针对Unicode的可变长度字符编码,也是一种前缀码
  • UTF-8优势
    • 它可以用来表示Unicode标准中的任何字符,且其编码中的第一个字节仍与ASCII兼容,这使得原来处理ASCII字符的软件无须或只须做少部份修改,即可继续使用
  • UTF-8应用
    • 它逐渐成为电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码
  • UTF-8编码规则
    • 对于单字节的符号,字节的第一位设为0,后面7位为这个符号的Unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的
    • 对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的Unicode码
  • UTF-8编码规则&流程
    • 1、Unicode符号范围(16进制) UTF-8编码方式(2进制)
      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
    • 2、请先看上表,很重要,UTF-8的编码规则依赖该表
    • 3、挑选一个汉字出来,比如说洁小瑜的“瑜”字。
      • “瑜”字的unicode值为“745c(0111-0100-0101-1100)”
    • 4、对比上面1步骤中的表和“瑜”字的Unicode值。
      • 此时表的作用体现出来了,745C明显是在上表第三行范围内,即大于0800,但是小于FFFF,此时看到是三个字节,即“瑜”字在UTF-8中使用3个字节来表示。
    • 5、开始填充UTF-8啦!!
      • 按照之前我们说的UTF-8编码规则,结合表中第三行的二进制格式“1110xxxx 10xxxxxx 10xxxxxx”
      • 从”瑜”的最后一个二进制位开始,依次从后向前填入格式中的x处,多出的位补0。
      • 这样就得到了,”瑜”的UTF-8编码是”11100111 10010001 10011100”,转换成十六进制就是E7919C。

上述的组织结构图,就是UTF-8作为编码方案的基本概念和流程,同时UTF-8通过制定编码规则及其灵活的字节数解决了上面提到的unicode引入的两个问题。关于UTF-8还有很多内容,本节暂时介绍到这里。

5.大端与小端

这块较为独立但是和编码息息相关。

  • 什么叫大端,什么叫小端
    • 所谓的大端模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中
    • 所谓的小端模式,是指数据的低位保存在内存的低地址中,而数据的高位保存在内存的高地址中
  • 为什么会有大小端
    • 这块还是自行谷歌哇。目前的理解就是计算机多字节的出现,使得寄存器对这种多字节的存放方式有差异而引入大小端。
  • 大小端在内存中的存放方式
存放地址 little-endian存放内容 big-endian存放内容
0x4000 0x12 0x78
0x4001 0x34 0x56
0x4002 0x56 0x34
0x4003 0x78 0x12
  • 如何判断大小端

bool IsBigEndian() { int a =1 ; if(((char*)&a)[3] ==1) return true ; else return false ; }

总体思想,主要是看地址存放的内容来判断大端小端。

6.utf-8为何不存在字节序问题

UTF-8以单个字节为编码单元,它的字节顺序在所有系统中都是一様的,没有字节序的问题。个人理解就是有点像char[],每次往地址里存放的内容是没有字节序问题的,并且utf-8编码方案也指定了存放的规则,并不依赖大小端系统。

7.字符转换工具

  • Linux下利用Vim查看文件编码和进行编码转换。

在VIM中查看文件编码:set fileencoding 在VIM中执行文件编码转换:set fileencoding=utf-8 如果想让VIM支持gbk或者utf8,需要修改~/.vimrcset 中的fileencodings=utf-8,gbk 这样VIM就会按照这个顺序去匹配文件的编码

  • php相关的转码函数
    • mb_convert_encoding
    • iconv 既是php中的函数,也可以作为linux下的命令使用
  • iconv命令
    • 格式如下:iconv -f from-encoding -t to-encoding inputfile -o outputfile.命令一目了然。
    • iconv支持的字符集也很多,具体可以用到时查看。提供一个查看命令:iconv -l,可以随意grep来查找你所需的字符编码
  • mysql中的字符编码
    • set names gbk
    • set names utf8
    • my.cnf中的default_character_set=utf8
    • 上面这几个是用的较多的,能够基本满足日常需求