android 扫盲 - 字符编码
不愿意看文字的,请转看B站的视频,讲的一样清晰:文字频频乱码,这背后是显卡的扭曲还是规则的沦丧?
为什么要写这篇呢?
很久之前入门学习 java 的时候第一次接触到字符编码这个东西,稍后在学习 web 基础的时候接触到了 UTF-8、字符乱码。当时我以为我已经足够了解字符编码了
回想当初老师告诉我们,一个中文字符占2个字节,但是这种说法其实大错特错,Unicode 编码中一个中文字符可不是占2个字节的
所以才有了今天这篇文章,很多东西我们以为已经足够了解了,但是依然被面试官打趴下。归根结底我们为什么不清楚、不知道呢,就是因为我们不是按照历史的发展脉络来学习的,所以我们必然有遗落,有不清楚
国外的学习资料,很多都喜欢把相关历史发展讲的明明白白的。以前不甚理解,但是现在我理解了,不了解历史,你就抓不住全部
字符集、编码集、储存方式
时间从二战来到50年代末60年代初,计算机发展很迅速,从军用、科学向商业、办公、教育等其他领域扩展,这时为了方便文字的显示、储存、输入,字符编码标准出现了
这直接崔生了世界首个字符编码标准:ASCII
的诞生
ASCII
是什么东西,就是一组储存一种语言所有字母和字符的 Map 集合。value 是该字母,key 是该字母统一对外调用的标记号码,就像门牌地址一样,让我们在一堆数据中准确快速的找到你。value 的集合叫:字符集
,key 的集合叫:编码集
字符集
会把你所在的语言体系里面所有的字母、字符之类的全存进去,这些字符是计算机显示的基础,计算机根据我们输入的字符代号来找出这些字符本身,然后显示出来
比如 value:A 对应的 key:1,我们在输入时,把1交给计算机,计算机就知道我们想要显示A这个字符
计算机是 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-16
与 UTF-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)
转载自:https://juejin.cn/post/6844904055660085255