身为一名要冲出国门的国际化码农🙃,字符编码是必备课题。小拽本文依次介绍下字节,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 的中文扩展。
1 | 非专业人士可以忽略: GB2312如何组合,能表示多少个? |
这样我们就可以组合出大约7000多个简体汉字了。在这些编码里,我们还把数学符号、罗马希腊的字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的全角字符
,而原来在127号以下的那些就叫半角字符
了。
五、GBK
满足了基础和常用的汉字需求后,但依然会有很多人的生僻字名字打不出来,屌丝还好,但是一旦牵涉伟人名字打不出来那就坑爹了!改改改,抓紧改!
GB2312的编码,使用两个字符,每个都只用了后128位,不合理呀,干脆我们把低字节127号之后的内码我们也用了,不浪费。
说干就干,于是扩展之后的编码方案被称为GBK
标准(不知道K是不是扩展的缩写K^_^),GBK包括了GB2312的所有内容,同时又增加了近20000个新的汉字(包括繁体字)和符号。
1 | 非专业人士可以忽略: GBK如何组合,能表示多少个? |
六、GB18030
中华民族大团结,后来少数民族也要用电脑了,于是我们需要再次扩展,又加了几千个新的少数民族的字,GBK扩成了GB18030
。从此之后,中华民族的文化就可以完美的在计算机时代中传承了。
1 | 非专业人士忽略: |
从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 编码就可以被其他电脑正常解释。
1 | 非专业人士忽略:unicode 编码 |
七、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并不是直接的对应,而是要过一些算法和规则来转换。
1 | 非专业人士直接忽略:unicode 如何转换成utf-8 |
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会怎么样呢?1
2
3
4
5
6
7➜ 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.
u'\uFFFD'.encode('utf8')*2) s = (
'gbk')) print(s.decode(
锟斤拷
也就产生了,传说中的”锟斤拷”神器!
另外还有几个神器:”烫烫烫烫烫,屯屯屯屯屯屯”
“烫” 主要出没于 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
参考网站: