跟我一块儿夯实编程基础 - 字符编码

为何要写这篇呢?

好久以前入门学习 java 的时候第一次接触到字符编码这个东西,稍后在学习 web 基础的时候接触到了 UTF-八、字符乱码。当时我觉得我已经足够了解字符编码了java

但直到我有一天我在掘金上看到一个问题:一个中文字符占几个字节?web

回想当初老师告诉咱们,一个中文字符占2个字节,可是这种说法其实大错特错,Unicode 编码中一个中文字符可不是占2个字节的面试

因此才有了今天这篇文章,不少东西咱们觉得已经足够了解了,可是依然被面试官打趴下。归根结底咱们为何不清楚、不知道呢,就是由于咱们不是按照历史的发展脉络来学习的,因此咱们必然有遗落,有不清楚学习

国外的学习资料,不少都喜欢把相关历史发展讲的明明白白的。之前不甚理解,可是如今我理解了,不了解历史,你就抓不住所有优化


字符集、编码集、储存方式

时间从二战来到50年代末60年代初,计算机发展很迅速,从军用、科学向商业、办公、教育等其余领域扩展,这时为了方便文字的显示、储存、输入,字符编码标准出现了ui

这直接崔生了世界首个字符编码标准:ASCII 的诞生编码

ASCII 是什么东西,就是一组储存一种语言全部字母和字符的 Map 集合。value 是该字母,key 是该字母统一对外调用的标记号码,就像门牌地址同样,让咱们在一堆数据中准确快速的找到你。value 的集合叫:字符集,key 的集合叫:编码集spa

字符集 会把你所在的语言体系里面全部的字母、字符之类的全存进去,这些字符是计算机显示的基础,计算机根据咱们输入的字符代号来找出这些字符自己,而后显示出来3d

好比 value:A 对应的 key:1,咱们在输入时,把1交给计算机,计算机就知道咱们想要显示A这个字符code

计算机是 2 进制存储的,每个 0 或 1 表示一位,8 个一位合起来是 1 个字节,计算机储存是按字节为基本单位存储的

英文由于字符少,因此 7 位的范围:0-128 就能涵盖全部字符了,此时 编码集 使用天然循序序号表示便可,7 位的 2 进制数,好比:0101011

可是在碰到中文、日本等文字后,这些文字不是字母拼接类型的语言,而是单个字符语言,中文里有 3 万个字符。字符集 却是没什么,有什么字符存什么字符就好了。可是 编码集 就有问题了,若是仍是使用天然顺序序号来表示字符编号,那么有可能一个字符的 2 进制编码数会很长很长,很是不利于输入和观察,此时一个中文词语多是这样的:0100100001000101010011000100110001001111。这要是让你输入估计会是个灾难,因此为了解决 编码集 过长的问题,你们决定让 编码集 在输入时使用 16 进制,好比常见的:\u{1f44d},去掉格式化字符,1f44d 就是这个字符所在的编码,这个 16 进制的编码在内存中仍是以对应的 2 进制数储存

还有一个问题,字符在 字符集 中是如何存储的。像英文字符少,因此 7 位 2 进制 128 个位置 就能搞定,这样英文的字符比编码用一个字节就能够了

可是中文呢,还有世界其余的那些语言呢,文字内字符不少,尤为是中文有几万个字符,那 编码集 使用 7 位就不够了,至少也得 16 位 65535 个位置才能放得下。这样的话,一个字符就得用 2 个字节甚至更多字节表示了。可是中文中也会用到数字、应为字母之类的,这些字符如果也用 2 个字节表示,就会浪费存储空间,下降 CPU 计算效率

为了应对这种状况,有的 编码集 采用可变字长,像英文字母之类的字符用 1 个字节,有的字符用 2 个字节或是更多。这种问题就叫作:存储方式优化

有的朋友会问为何 2 个字节会有浪费存储空间的问题呢?屏幕上虽然咱们看着是一个个文字,可是这些文字在计算机,也就是内存中全是按照字符对应的字符编码的 2 进制数储存的, 也就是 编码集 这个东西,因此表示一个字符使用的字节越多,那么越占用,浪费资源

注意如下:

  • 字符集、编码集、存储方式 这3者共同组成了一个字符编码标准,他们其中有任何一个产生变化都会演变成一个新的字符编码标准
  • 有的字符编码标准采用可变字长
  • 字符编码标准之间要兼容很难,不少文字乱码就是字符标准之间不兼容的问题

但愿我这种特例独行的解释能让你们接受,我以为这样最好理解,以上没有抄袭任何诸如百度百科之类的解释,彻底是我本身的认知,有差错请指出,在此万分感谢!


字符编码发展史

1. ASCII 码时代

1960年 ASCII 码 字符编码出台,使用7位编码,有效位置是 128 个,用来统一英文的输入、储存、显示,由于计算机是按字节储存的,因此补了一位,以 0 开头

2. 扩展 ASCII 码时代

ASCII 码 出来后,效果很好,可是欧洲其余国家有本身的语言,本身的字符,因此纷纷盯上了 ASCII 码 没有使用的补 0 的这一位,拓展成了有效空间为 256 个的字符编码。可是呢,这些欧洲国家本身搞本身的,搞出来的字符编码相互不能通用,很是混乱,乱码成了一个棘手的问题

3. GB2312/GBK 时代

1981年,我过出台了本身的面向中文的字符编码:GB2312,包含 7445 个字符,包括 6763 个汉字,682 个字符

虽然又推出了:GBK,支持更多的中文字符,支持共 21003 个汉字,而且完整支持中日韩文字

GB2312/GBK 系中文字符标准,window 中文版默认就是使用 GB2312 这个字符编码,特色是每一个字符使用2个字节

4. Unicode 万国码

前面说过,你们本身搞本身的字符编码,整个相互不通用,竟是乱码,随着互联网的发展,这样但是不行的,随后 ISO 组织出面集合大伙搞了统一的,你们一块儿使用的,兼容各自字符编码的国际统一码:Unicode

Unicode 使用4个字节(能够扩容支持更多字节)的字符范围,预设100多万个字符位置,以容纳世界上全部的语言,特殊字符,emoji 表情这些

Unicode 把目前分红 17个扇区,每一个扇区有 65535 个位置,规定不一样类型的字符存储在不一样的扇区

有一点十分重要,Unicode 只是一种 编码集 规范,规定了一个字符对应的字符的位置,可是针对每一个字符都占用4个字节的问题,又产生了 UTF 这种通过优化的 字符编码规范


UTF 编码

其余的都不用详说了,UTF 编码 是咱们平时最经常使用的,须要详细的展开一下,目前 UTF 编码 有3种规范:

  • UTF-8: 可变字符编码,占用1到4个字节
  • UTF-16: 可变字符编码,占用2到4个字节
  • UTF-32: 不可变字符编码,统一使用4个字节表示一个字符

你们要知道这3实际上是一回事,搞清楚一个其余也就明白了,都是优化字节占用量。不少时候 Unicode 4个字节的储存方式里,这4个字节的数字里面不少都是没有用的,纯粹为了补位的,像英文1个字节就够了,这就是优化的原动力

UTF-8 使用一至四个字节为每一个字符编码

  • 使用一个字节编码:128 个 ASCII 字符(Unicode 范围由 U+0000 至 U+007F)
  • 使用二个字节编码:带有变音符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文及马尔代夫语(Unicode 范围由 U+0080 至 U+07FF)
  • 使用三个字节编码:其余基本多文种平面(BMP)中的字符(CJK属于此类-Qieqie注),中文就在这个范围内
  • 使用四个字节编码:其余 Unicode 辅助平面的字符,好比 emoji 表情

UTF-16UTF-8 不一样的地方在于英文等字符再也不是一个字符编码了,而是2个

UTF-32 统一使用4个字节编码,咱们处理 emoji 表情符号基本上都是转成 UTF-32 来显示

你们看懂了吗~ 这就是 UTF-8 被普遍采用的缘由,对于英文的优化真是好...

有一道经典的面试题:中文占几个字符,这下你们知道怎么回答了吧,GBK 是2个,UTF-8 是3个,UTF-8 是4个

为啥是3个呢?UTF 里面每8位开头都有表示分类和位置的占位,3个字节里面正好有1个字节被这种占位占走了,剩下的2位才能承载中文那几万个字符,因此 UTF 编码中中文统一都是用3个字符编码

你们看图:


字符占位对照图

编码 英文字节数 中文字节数
GB2312 1 2
GBK 1 2
GB18030 1 2
ISO-8859-1 1 1
UTF-8 1 3
UTF-16 2 4
UTF-32 4 4
UTF-16BE 2 2
UTF-16LE 2 2

Dart、Flutter 中的 emoji

让我对字符编码产生疑问的是从 emoji 显示这个问题开始的,这里记录下我找到的资料:

  • Dart 文字显示默认是 UTF-16 的
  • 咱们兼容 emoji 的话最好用 UTF-32
  • Flutter 提供了 Runes 这个类,来存储、转换 UTF-32 编码的字符

不知作别的平台怎么让 emoji 显示出来的,反正 Flutter 想显示 emoji 必须使用 UTF-32 这一种方式

Runes emojiString = new Runes('\u2665 \u{1f605} \u{1f60e} \u{1f47b} \u{1f596} \u{1f44d} 哇哈哈哈哈!!!');
var index = String.fromCharCodes(emojiString)
复制代码

相关文章
相关标签/搜索