字符编码

为什么写这篇文章

  字符编码的问题一直以来困然着我,每次遇到相关问题需要解决,都要重新去搜索学习。但是,网上的一些文章往往也是自相矛盾的半成品,再加上windows的奇怪命名,导致我对这些概念似懂非懂,所以想借此机会重新理顺这些概念顺带学习。


相关概念

字符集(character set)

  为每一个「字符」分配一个唯一的 ID(学名为码位 / 码点 / Code Point)。

  • ASCII,ISO-8859-1:英语,128/25* 6个字符
  • GB2312:简体中文
  • BIG5:繁體中文
  • GBK:简体中文
  • GB18030:中文,日文,朝鲜语
  • Unicode,UCS:世界
  • MIME 编码
  • ANSI 本地编码,不同地区使用本地方式,中国为GBK,中国台湾为BIG5
编码规则/编解码(encoding)

  将「码位」转换为字节序列的规则(编码/解码 可以理解为 加密/解密 的过程)

  • UCS——UCS-2,UCS-4
  • Unicode——UTF-8,UTF-16,UTF-32,UTF-7

字符编码

ASCII

  因为是美国人最早使用计算机的,所以美国国家标准协会ANSI制定了一个标准,规定了常用字符的集合以及每个字符对应的编码,这就是ASCII字符集(Character Set),也称ASCII码。ASCII码我们理解起来相对比较简单。它是通过8位的字节来组合,最多可组合256种不同状态,它的使用过程就是简单的查表过程。我们需要注意一点的是ASCII本身就直接规定了字符和字符编码的方式,所以==ASCII既是字符集又是编码方案==。

多字节字符集

  ANSII最多可以组合为256种不同字符,但是对于中文等来说是远远不够的。为了解决这个问题,我们拿我们自己的中文来说,国家为了兼容ASCII就使用了双字节字符集编码(DBCS,Double Byte Character Set)。

  • GB2312

  我们规定:一个小于127的字符意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到 0xF7,后面一个字节(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了。在这些编码里,我们还把数学符号、罗马希腊的字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来在127号以下的那些就叫"半角"字符了。这种汉字方案叫做"GB2312"。GB2312 是对 ASCII 的中文扩展。

  • GBK

  后来还是不够用,于是干脆不再要求低字节一定是127号之后的内码,只要第一个字节是大于127就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字符集里的内容。结果扩展之后的编码方案被称为 GBK 标准,GBK 包括了 GB2312 的所有内容,同时又增加了近20000个新的汉字(包括繁体字)和符号。

  • GB18030

  后来少数民族也要用电脑了,于是我们再扩展,又加了几千个新的少数民族的字,GBK 扩成了 GB18030。从此之后,中华民族的文化就可以在计算机时代中传承了。

  通过上面的介绍我们可以知道,==GB2312,GBK,GB18030都是字符集和编码连锁的方案==。

Unicode

  因为不光我们中国有自己的ASCII衍生字符集出现,中国台湾,韩国,日本等都有一套自己的轮子(字符集),所以在一台电脑上显示全部的语言是非常困难的,需要安装各种字符系统。所以一个叫 ISO (国际标谁化组织)的国际组织决定着手解决这个问题。他们采用的方法很简单:废了所有的地区性编码方案,重新搞一个包括了地球上所有文化、所有字母和符号的编码!他们打算叫它"Universal Multiple-Octet Coded Character Set",简称 UCS, 俗称 "UNICODE"。ISO 就直接规定必须用两个字节,也就是16位来统一表示所有的字符,对于ascii里的那些"半角"字符,UNICODE 包持其原编码不变,只是将其长度由原来的8位扩展为16位,而其他文化和语言的字符则全部重新统一编码。这样就可以2^16=65536个字符码空间。

  在Unicode出现之前,所有的字符集都是和具体编码方案绑定在一起的,都是直接将字符和最终字节流绑定死了,例如ASCII编码系统规定使用7比特来编码ASCII字符集;GB2312以及GBK字符集,限定了使用最多2个字节来编码所有字符,并且规定了字节序。这样的编码系统通常用简单的查表,也就是通过代码页就可以直接将字符映射为存储设备上的字节流了。这种方式的缺点在于,字符和字节流之间耦合得太紧密了,从而限定了字符集的扩展能力。假设以后火星人入住地球了,要往现有字符集中加入火星文就变得很难甚至不可能了,而且很容易破坏现有的编码规则。因此Unicode在设计上考虑到了这一点,将字符集和字符编码方案分离开。也就是说,虽然每个字符在Unicode字符集中都能找到唯一确定的编号(字符码,又称Unicode码),但是决定最终字节流的却是具体的字符编码。例如同样是对Unicode字符“A”进行编码,UTF-8字符编码得到的字节流是0x41,而UTF-16(大端模式)得到的是0x00 0x41。

常见的Unicode编码
  • UCS-2/UTF-16

  UCS-2和UTF-16对于BMP层面的字符均是使用2个字节来表示,并且编码得到的结果完全一致。不同之处在于,UCS-2最初设计的时候只考虑到BMP字符,因此使用固定2个字节长度,也就是说,他无法表示Unicode其他层面上的字符,而UTF-16为了解除这个限制,支持Unicode全字符集的编解码,采用了变长编码,最少使用2个字节,如果要编码BMP以外的字符,则需要4个字节结对,这里就不讨论那么远,有兴趣可以参考维基百科:UTF-16/UCS-2。

  • UTF-8

  UTF-8应该是目前应用最广泛的一种Unicode编码方案。由于UCS-2/UTF-16对于ASCII字符使用两个字节进行编码,存储和处理效率相对低下,并且由于ASCII字符经过UTF-16编码后得到的两个字节,高字节始终是0x00,很多C语言的函数都将此字节视为字符串末尾从而导致无法正确解析文本。因此一开始推出的时候遭到很多西方国家的抵触,大大影响了Unicode的推行。后来聪明的人们发明了UTF-8编码,解决了这个问题。

代码中的字符串

  我们在代码中写一行字符串例如:

std::string str = "abc 你好!";

  它在编辑时存储时,编译存储和运行时都要具体分析。

  源码字符集和字符编码:在源文件中的字符串在存储在磁盘二进制文件中。Visual studio默认会用ANSI格式的文件存储,ANSI在windows中会按照操作系统的区域设置的编码方式来存储源文件,简体中文往往是GBK(字符集和字符编码都为GBK),中国台湾繁体为BIG5。GCC的源代码字符集为unicode,编码默认为utf-8。

  执行字符集和字符编码:代码源文件编译为二进制文件时,Visual Studio会使用系统区域设置(简体中文为GBK)的字符集编码方式去存储,所以很多采用utf-8编码开源的代码库在使用vs编译时会出现乱码。GCC执行字符集为unicode,编码默认为utf-8。

  运行环境编码:二进制文件在实行时,在windows中默认的字符编码页为gbk,所以如果在二进制文件中存储的字符集字符编码不是gbk,那么就会出现乱码。

  visual studio中可以在属性中设置/utf-8来将原字符集和执行字符集同时指定为utf-8。/UTF-8

常见问题

Unicode是两个字节吗?

  Unicode只是定义了一个庞大的、全球通用的字符集,并为每个字符规定了唯一确定的编号,具体存储为什么样的字节流,取决于字符编码方案。推荐的Unicode编码是UTF-16和UTF-8。

带签名的UTF-8指的是什么意思?

  带签名指的是字节流以BOM标记开始。很多软件会“智能”的探测当前字节流使用的字符编码,这种探测过程出于效率考虑,通常会提取字节流前面若干个字节,看看是否符合某些常见字符编码的编码规则。由于UTF-8和ASCII编码对于纯英文的编码是一样的,无法区分开来,因此通过在字节流最前面添加BOM标记可以告诉软件,当前使用的是Unicode编码,判别成功率就十分准确了。但是需要注意,不是所有软件或者程序都能正确处理BOM标记,例如PHP就不会检测BOM标记,直接把它当普通字节流解析了。因此如果你的PHP文件是采用带BOM标记的UTF-8进行编码的,那么有可能会出现问题。

Unicode编码和以前的字符集编码有什么区别?

  早期字符编码、字符集和代码页等概念都是表达同一个意思。例如GB2312字符集、GB2312编码,936代码页,实际上说的是同个东西。但是对于Unicode则不同,Unicode字符集只是定义了字符的集合和唯一编号,Unicode编码,则是对UTF-8、UCS-2/UTF-16等具体编码方案的统称而已,并不是具体的编码方案。所以当需要用到字符编码的时候,你可以写gb2312,codepage936,utf-8,utf-16,但请不要写unicode(看过别人在网页的meta标签里头写charset=unicode,有感而发)。

引用参考

字符编码的故事

关于字符编码,你所需要知道的

梁海的知乎回答

你可能感兴趣的:(字符编码)