字符编码那些事儿

6次阅读

共计 6271 个字符,预计需要花费 16 分钟才能阅读完成。

身为一名要冲出国门的国际化码农????,字符编码是必备课题。小拽本文依次介绍下 字节,ASCII,GB2312,GBK,GB18030,UNICODE,UTF8,UTF16,ICU 等到底是什么鬼?最后理论结合实际,研究下网站中经常出现的“锟斤拷,��,烫烫烫烫烫,屯屯屯屯屯屯”是什么神兵利器 O(∩_∩)O?

一、二进制和字节

大概一百多年前,贝尔实验室制造了世界上的第一个晶体管,晶体管具有开合 (0 和 1) 的状态,这种 01 状态的变化被称为 二进制位(bit)

过了几年,英特尔把八个可以开合的晶体管组合,做为一个基本的记录单元,这个单元被称作 字节(byte),所以 一个 byte 由 8 个 bit 构成,可以表达 256 种状态

又过几年,祖师爷冯诺依曼设计了一台可以存储和处理(冯诺依曼体系)字节变动的机器 ENIAC,后来这个机器被称作 计算机

二、标准 ASCII

计算机运行是二进制,如何用二进制位来标识人类语言,就需要和计算机有一套约定关系。例如约定,0100 1111 代表 O,0100 1011 代表 K,那么存储为 01001111 01001011 的两个字节就代表 OK,这套约定关系被称作 字符编码

冯祖师爷是的德国人,二战去了美国设计了第一台计算机。起初,只有美国人能用计算机,山姆大叔就根据英语习惯设计了一套映射约定

  • 0-31 标识控制字符,例如换行 [LF],删除[DEL],确认[ACK] 等
  • 32-47 标识符号例如!@#$ 等
  • 48-57 标识 0 - 9 是个阿拉伯数字
  • 65-122 标识大小写字母

大家都按着这个约定来,交流表达起来没啥问题,呵呵,都挺好。于是这个方案就一致通过了,山姆大叔也给这个约定起了个名字 ASCII 编码(American Standard Code for Information Interchange,美国信息互换标准代码)。当时世界上所有的计算机都用同样的 ASCII 方案来保存英文字符。

计算机一个标准字节 8bit 本身可以标识 256 个符号,但 标准的 ASCII 的最高位去掉用做奇偶校验,用剩余 7 位标识 128 个符号,如下图

三、ASCII 扩展字符集

随着计算机的发展,欧洲人开始逐步接触计算机了。

英语用 128 个符号编码就够了,但是用来表示其他语言,128 个符号是不够的 。比如在法语中,字母上方有注音符号,它就无法用 ASCII 码表示。于是,一些欧洲国家就决定, 利用字节中闲置的最高位编入新的符号 。比如,法语中的é的编码为 130(二进制 10000010)。这样一来,这些欧洲国家使用的编码体系, 可以表示最多 256 个符号

IBM 牵头扩充了 ASCII 编码 128-256 位的标识字符,主要是一些欧洲的常用符号,这部分扩展映射被称为ASCII 扩展字符集

四、GB2312

雄关漫道真如铁,而今迈步从头越,美帝国主义万万没有想到,二战后,大量第三世界的人民站起来了,逐步开始使用计算机。但 问题是 256 个字符已经没啥可利用的字节状态来表示汉字了,更何况中华文明有 6000 多个常用汉字需要保存 呢。

但是这难不倒智慧的中国人民,面对帝国主义的压迫,我们毫不客气的做了两件事情,并在 1980 年发表了这个声明

  • 互相尊重:尊重标准 ASCII 规范中 0 -127 位表示的标准字符。
  • 平等互利:ASCII 的 128-256 位,我们用来标识中文,由于中文太多,我们要使用两个字节来表示一个中文 ^_^

中国人民觉的这个声明还不错,毕竟当时计算机的使用范围也不大,基本满足需求,于是就把这种汉字方案叫做 GB2312 编码GB2312 是对 ASCII 的中文扩展

非专业人士可以忽略:GB2312 如何组合,能表示多少个?GB2312 中用两个字节来标识一个汉字,前面的一个字节(他称之为高字节)从 0xA1 用到 0xF7,后面一个字节(低字节)从 0xA1 到 0xFE,简单计算

0xA1:10*16 + 1 = 161  
0xF7:15*16 + 7 = 247 => 247-161 = 86
0xFE:15*16 + 14= 254 => 254-161 = 93
因此 GB2312 可以标识约 86*93=7998 个汉字

实时上 GB2312 标准共收录 6763 个汉字,其中一级汉字 3755 个,二级汉字 3008 个。同时收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的 682 个字符。几乎覆盖了大陆常用的 99.75% 汉字

这样我们就可以组合出大约 7000 多个简体汉字了。在这些编码里,我们还把数学符号、罗马希腊的字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的 全角字符 ,而原来在 127 号以下的那些就叫 半角字符 了。

五、GBK

满足了基础和常用的汉字需求后,但 依然会有很多人的生僻字名字打不出来,屌丝还好,但是一旦牵涉伟人名字打不出来那就坑爹了!改改改,抓紧改!

GB2312 的编码,使用两个字符,每个都 只用了后 128 位,不合理呀,干脆我们把低字节 127 号之后的内码我们也用了,不浪费

说干就干,于是扩展之后的编码方案被称为 GBK 标准(不知道 K 是不是扩展的缩写 K^_^),GBK 包括了 GB2312 的所有内容,同时又增加了近 20000 个新的汉字(包括繁体字)和符号

非专业人士可以忽略:GBK 如何组合,能表示多少个?第一个字节的值从 0x81 到 0xFE 保持不变,第二个字节的值扩展从 0x40 到 0xFE

0xFE-0x81:254 - 8*16+1 =125
0xFE-0x40:254 - 4*16 =190 

因此,GBK 约标识了 125*190  = 23750 

GBK 共收入 21886 个汉字和图形符号,包括:GB2312 中的全部汉字、非汉字符号;BIG5 中的全部汉字;ISO10646 相应的国家标准 GB13000 中的其它 CJK 汉字
以上合计 20902 个汉字;其它汉字、部首、符号,共计 984 个。

六、GB18030

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

非专业人士忽略:这一系列汉字编码的标准通为 `DBCS`(Double Byte Charecter Set 双字节字符集)。在 DBCS 系列标准里,最大的特点是两字节长的汉字字符和一字节长的英文字符并存于同一套编码方案里,因此他们写的程序为了支持中文处理,必须要注意字串里的每一个字节的值,如果这个值是大于 127 的,那么就认为一个双字节字符集里的字符出现了

从 ASCII 到 GB2312,再到 GBK,而后 GB18030,中华民族终于完成了全量中文字符的编码,简单总结下

  • 第一阶段:中国人民通过对 ASCII 编码的中文扩充改造,产生了 GB2312 编码,可以表示 6000 多个常用汉字。
  • 第二阶段:汉字实在是太多了,包括繁体和各种字符,于是产生了 GBK 编码,它包括了 GB2312 中的编码,同时扩充了很多。
  • 第三阶段:中国是个多民族国家,各个民族几乎都有自己独立的语言系统,为了表示那些字符,继续把 GBK 编码扩充为 GB18030 编码,完成全量中文字符编码。

六、UNICODE

之后的世界,百花齐放,百家争鸣,各国纷纷制造自己的编码规范,同时互相不去理解对方规范,即使同一种语言也区别巨大 ,例如台湾地区中文采用big5 的繁体编码,名字也牛逼大了,叫 大五码

各自为政引来了大量的问题,各个语言互不兼容,此时,一堆大佬看不下去了,勇敢的站了出来,着手解决这个问题,他们成立了一个类似于 TC 的组织,叫做ISO(International Organization for Standardization 国际标准化组织)。

他们采用的方法很简单:废了所有的地区性编码方案,重新搞一个包括了地球上所有文化、所有字母和符号的编码!他们打算叫它 ”Universal Multiple-Octet Coded Character Set”,简称 UCS, 俗称 “UNICODE“。这就是Unicode,就像它的名字都表示的,这是一种所有符号的编码

UNICODE 统一了各国,成为了实事上的大一统的编码规范。这种编码非常大,大到可以容纳世界上任何一个文字和标志。所以只要电脑上有 UNICODE 这种编码系统,无论是全球哪种文字,只需要保存文件的时候,保存成 UNICODE 编码就可以被其他电脑正常解释。

非专业人士忽略:unicode 编码
unicode 开始制订时,计算机的存储器容量极大地发展了,空间再也不成为问题了。于是 ISO 就直接规定:
1:必须用两个字节,也就是 16 位来统一表示所有的字符
2:对于 ASCII 里的那些“半角”字符,unicode 包持其原编码不变,只是将其长度由原来的 8 位扩展为 16 位,3:其他文化和语言的字符则全部重新统一编码。由于”半角”英文符号只需要用到低 8 位,所以其高 8 位永远是 0,因此这种大气的方案在保存英文文本时会多浪费一倍的空间。

七、UTF,UTF8,UTF16

UNICODE 很好的解决了不同语言统一编码的问题,但同样也不完美,有两个主要问题,

  • 字符识别:如何才能区别 unicode 和 ascii?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?
  • 存储浪费 :我们已经知道,英文字母只用一个字节表示就够了,如果 unicode 统一规定,每个符号用三个或四个字节表示,那么 每个英文字母前都必然有二到三个字节是 0,这对于存储空间来说是极大的浪费,文本文件的大小会因此大出二三倍

此时 UTF(unicode transfer format) 标准出现了,顾名思义,是UNICODE 在传输和存储过程中的格式化标准,其中使用最广的是 utf8 和 utf16

UTF-16相对好理解,就是 任何字符对应的数字都用两个字节来保存!我们通常对 Unicode 的理解就是把 Unicode 与 UTF-16 等同 了。但是很显然如果都是英文字母这做有点浪费,明明用一个字节能表示一个字符为啥整两个啊。

UTF-8最大的一个特点,就是它是一种 变长的编码方式 。它可以 使用 1~4 个字节表示一个符号,根据不同的符号而变化字节长度,当字符在 ASCII 码的范围时,就用一个字节表示,保留了 ASCII 字符一个字节的编码做为它的一部分,注意的是 unicode 一个中文字符占 2 个字节,而 UTF- 8 一个中文字符占 3 个字节)。从 unicode 到 utf- 8 并不是直接的对应,而是要过一些算法和规则来转换。

非专业人士直接忽略:unicode 如何转换成 utf-8
以小拽的 "拽" 字为例

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
中文一般三个字节,前置标识位

举个栗子,中文:拽 unicode 是 25341
25341                    十进制 unicode
0x62fd                   十六进制 
0110 0010 1111 1101      二进制 

### 套上模板
0110      001011    111101      二进制  25341
1110xxxx  10xxxxxx  10xxxxxx    模板第三行
11100110  10001011  10111101    utf8 二进制
e   6     8   b     b   d       utf8 十六进制【一切为了节省】最终 utf8 拽对应的就是 0xe68bdb

UTF- 8 就是在互联网上使用最广的一种 unicode 的实现方式,这是为传输而设计的编码,并使编码无国界,这样就可以显示全世界上所有文化的字符了。

简单对比下 GB 系列,UTF8,UTF16

  • UTF16:不推荐使用 utf16,因为utf16 最初能表示的字符数有 6 万多,看起来很多,但是实际上目前 Unicode5.0 收录的字符已经达到 99024 个字符,其实不够,有可能出现乱码
  • UTF8:国际化编码首推 UTF8,兼容全量,唯一的问题是空间略有浪费!
  • GB 系列:GB 系列都是双字节字符集,相对节省空间,如果只是国内使用 GB18030 完全可以兼容所有

八、“锟斤拷��”是什么

通过上面介绍,可以看出来,各个编码规则是不一样的,目前互联网浏览器默认传输和解析方式是 UTF8,但是部分老的网页采用 GB 系列,就会出现传输过程 UTF8 解析不了,展示 GB 错乱问题。

UNIDCODE 规定:当 unicode 遇到解释失败的字时,会尝试用「U+FFFD」来代替,「U+FFFD」乃是 unicode 的一个占位符,显示为 �

而 utf8 识别为异常的传输字符后,传到页面转为双字节展示的 GB 会怎么样呢?

➜ xiaozhuai ✗ python
Python 2.7.10 (default, Aug 17 2018, 19:45:58)
[GCC 4.2.1 Compatible Apple LLVM 10.0.0 (clang-1000.0.42)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> s = (u'\uFFFD'.encode('utf8')*2)
>>> print(s.decode('gbk'))
锟斤拷

也就产生了,传说中的 ” 锟斤拷 ” 神器!

另外还有几个神器:” 烫烫烫烫烫,屯屯屯屯屯屯 ”
“烫”主要出没于 windows 平台下,ms 的 vc++ 编译器中,当你在栈内开辟新内存时,vc 会使用 0xcc 来初始化填充,很多个 0xcc 连起来就成了 烫烫烫烫烫 同理在堆内开辟新内存时,会用 0xcd 填充,这便是 屯屯屯屯屯屯
不管是“锟斤拷”还是“烫”都要求最后是用 GB 码输出。

九、ICU

在 unicode 的统治下,世界各国的基本编码不会出现乱码等异常。但当中华民族逐步强大,准备冲出中国统一世界的时候,发现各国的货币,时间,数字等表示灰常不统一,例如数字 1234.5,英文表示 1,234.5,葡语表示确是 1.234,5,很是苦恼。

此时 IBM 站了出来,叫上 google,apple 等小伙伴,遵循 ”IBM 公共许可证 ”,开源了一套基于 unicode 的国际化组件 ICU(International Component for Unicode
)。 根据各地的风俗和语言习惯,实现对数字、货币、时间、日期、和消息的格式化、解析,对字符串进行大小写转换、整理、搜索和排序等功能,ICU4C 提供了强大的 BIDI 算法,对阿拉伯语等 BIDI 语言提供了完善的支持。

ICU 成为了目前国际化组件的实事标准,底层依赖 UNICODE 和 CLDR,官方提供了 C /C++ 和 JAVA 的 SDK,ICU4C 和 ICU4J,同时,各个语言在此基础上开发了各个语言的版本,例如 php 的 intl 组件。

十、实事标准

字符编码的从产生,发展,到国际化一步一步走来,逐步形成了下列实事标准

  • 字符集:UNICODE
  • 字节编码:UTF8
  • 国际化:ICU

需要注意的是,mysql 的 utf8 并不完全兼容标准的 utf8 编码,后续推出了 utf8mb4 完全兼容,所以推荐采用 utf8mb4

参考网站:

  • BIDI:https://www.ibm.com/developer…
  • CLDR:http://cldr.unicode.org/
  • ICU:http://site.icu-project.org/d…
  • UNICODE:http://www.unicode.org/standa…

【转载请注明:字符编码那些事儿 | 靠谱崔小拽】

正文完
 0