跟我一起夯实编程基础 – 字符编码

为什么要这篇呢?

很久之前入门学习 java 的时候第一次接触到字符编码这个东西,然后在学些 web 基础的时候接触到了 UTF-8,字符乱码,我以为我足够了解字符编码了

但直到今天我看到了一个问题:一个中文字符占几个字节?

回想当初老师告诉我们一个中文字符占2个字节,但是这种说法其实大错特错,Unicode 编码下一个中文字符占3个字节

所以才有了今天这篇文章,很多东西我们以为足够了解了,但是还是被面试官打趴下了,归根结底我们为什么不知道呢,其实就是我们不是按照这个东西的发展历史来学习的

国外学习资料,很多都喜欢把相关历史发展讲的明明白白的,以前不甚理解,但是现在我理解了,不了解历史,你就抓不住全部


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

时间从二战后来到50年代末60年代初,计算机发展很迅速,从军用、科学像办公、教育等其他方向扩展,这时为了显示的标准迫切的需要统一的文字存储,展示方式

这直接崔生了世界首个字符集:ASCII 的诞生,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 就是这个字符所在的编码

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

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

为了应对这种情况,有的 编码集 采用可变字长,有的字符用1个字节,有的字符用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 是3个

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

大家看图:


字符占位对照图

编码英文字节数中文字节数
GB231212
GBK12
GB1803012
ISO-8859-111
UTF-813
UTF-1624
UTF-3244
UTF-16BE22
UTF-16LE22

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.im/post/5e35610ae51d45529f730761

「点点赞赏,手留余香」

    还没有人赞赏,快来当第一个赞赏的人吧!
JAVA
0 条回复 A 作者 M 管理员
    所有的伟大,都源于一个勇敢的开始!
欢迎您,新朋友,感谢参与互动!欢迎您 {{author}},您在本站有{{commentsCount}}条评论