从 ASCII 到 UTF-8:一部字符集的发展史
当你在键盘上按下一个A,或者输入一个你,计算机究竟是如何知道它们是什么字符的?
今天我们已经习惯了 UTF-8、Unicode 等名词,但这些标准并不是凭空出现的,而是计算机工业几十年发展的结果。
本文将按照时间顺序,完整介绍字符集的发展历史、出现背景、解决的问题,以及为什么最终走向了 Unicode 和 UTF-8。
一、字符编码为什么会出现?
计算机本质上只认识两种状态:
0 1CPU、内存、硬盘里面都只有二进制。
那么问题来了。
当用户按下键盘上的A时,计算机应该存储什么?
存成:
01000001还是
00110110还是别的?
如果没有统一标准,不同厂商可以随便定义。
例如:
IBM 规定
A = 00100001另一家公司规定
A = 11001010那么一台 IBM 计算机保存的文件,另一台计算机打开以后,看到的可能就不是 A,而是一个完全不同的字符。
这就是早期计算机面临的第一个问题:
不同设备之间无法交换文本。
于是,人们开始思考:
能不能给每一个字符分配一个唯一的编号?
这就是字符编码诞生的原因。
二、ASCII:计算机历史上的第一个统一字符集
1963 年,美国标准协会(ANSI 的前身)制定了 ASCII(American Standard Code for Information Interchange)。
它的目标非常简单:
所有英文字符都使用统一的数字表示。
例如:
| 字符 | 十进制 | 十六进制 |
|---|---|---|
| A | 65 | 0x41 |
| B | 66 | 0x42 |
| a | 97 | 0x61 |
| 0 | 48 | 0x30 |
| 空格 | 32 | 0x20 |
以后无论是哪家公司生产的计算机,只要看到数字 65,就知道这是字符 A。
ASCII 第一次实现了不同计算机之间的文本互通。
三、为什么 ASCII 只有 128 个字符?
今天看来,128 个字符非常少。
但在 1960 年代,这已经是一个非常合理的设计。
当时的计算机:
- 内存极其昂贵
- CPU 运算能力有限
- 网络几乎不存在
工程师统计了一下:
英语需要:
- 26 个大写字母
- 26 个小写字母
- 10 个数字
- 标点符号
- 一些控制字符
全部加起来不到 128 个。
因此决定:
使用7 位(二进制)表示所有字符。
2^7 = 128于是 ASCII 的编码范围就是:
0 ~ 127四、ASCII 里的控制字符
很多人第一次看到 ASCII 表都会疑惑:
为什么前面几十个字符根本不能打印?
例如:
| 编码 | 名称 | 含义 |
|---|---|---|
| 0 | NUL | 空字符 |
| 7 | BEL | 响铃 |
| 8 | BS | 退格 |
| 9 | TAB | 制表符 |
| 10 | LF | 换行 |
| 13 | CR | 回车 |
| 27 | ESC | 转义 |
这些字符其实不是给人看的。
而是给当年的机械设备使用的。
例如当时最流行的是电传打字机(Teletype)。
LF(Line Feed)表示:
把纸向上移动一行。
CR(Carriage Return)表示:
把打印头移动到最左边。
直到今天:
Windows 使用:
CR + LFLinux 使用:
LF都是那个时代留下来的历史遗产。
五、ASCII 的优点与局限
ASCII 的成功几乎改变了整个计算机行业。
它第一次实现了:
- 不同计算机之间可以交换文本
- 编程语言可以统一表示字符串
- 操作系统可以统一处理字符
但是它有一个致命缺点:
它只考虑了英语。
例如:
你没有。
é没有。
ß没有。
あ没有。
对于美国来说没有问题。
但是对于欧洲、中国、日本来说,ASCII 根本无法使用。
于是新的问题出现了。
六、扩展 ASCII:第一次尝试解决多语言问题
后来计算机普遍采用 8 位字节(Byte)。
工程师发现:
ASCII 实际只用了 7 位。
最高位一直没有使用。
于是他们决定:
把范围扩展到:
0~255即:
2^8 = 256这就是 Extended ASCII。
这样欧洲国家终于可以加入:
é ü ñ ø等字符。
看起来问题解决了。
实际上,更大的问题才刚刚开始。
七、代码页(Code Page):乱码时代的开始
欧洲每个国家都希望把自己的字符放进去。
于是:
法国设计了一套。
德国设计了一套。
俄罗斯设计了一套。
日本设计了一套。
中国设计了一套。
结果:
同样一个数字:
130在法国表示:
é在德国表示:
ä在俄罗斯表示:
Ж同一个文件,在不同国家打开,显示完全不同。
这就是无数程序员都遇到过的:
乱码。
微软后来提出了 Code Page(代码页)的概念。
例如:
- CP437
- CP850
- CP932
- CP936(GBK)
- CP950
不同国家选择不同代码页。
虽然本地问题解决了。
但是国际交流依然混乱。
八、中国自己的字符集:GB2312、GBK、GB18030
汉字数量远远超过欧洲文字。
ASCII 根本无法表示。
1980 年,中国制定了 GB2312。
设计思路非常简单:
ASCII 保持不变。
英文仍然使用一个字节。
汉字使用两个字节。
例如:
A ↓ 41但是:
你 ↓ C4 E3后来随着汉字越来越多。
GB2312 已经不够。
于是:
GBK
支持两万多个汉字。
后来又发展为:
GB18030。
直到今天,它仍然是中国的重要国家标准。
九、真正的问题:世界没有统一字符集
到了 1990 年代。
整个世界几乎处于:
美国:
ASCII
欧洲:
ISO-8859
日本:
Shift-JIS
中国:
GBK
韩国:
EUC-KR
俄罗斯:
KOI8
互联网开始普及。
电子邮件开始流行。
网页开始出现。
结果:
一封邮件发出去。
美国正常。
中国乱码。
日本乱码。
俄罗斯乱码。
全球软件产业终于意识到:
不能再让每个国家维护自己的字符集了。
十、Unicode:给世界上每一个字符发身份证
1991 年,Unicode 联盟成立。
它提出了一个革命性的思想:
世界上的每一个字符,都分配一个唯一编号。
例如:
A ↓ U+0041中 ↓ U+4E2D😊 ↓ U+1F60A注意:
Unicode 并不是编码方式。
它更像一本巨大的字典。
里面记录着:
字符 ↓ 唯一编号世界上所有软件,只要遵守 Unicode,就不会再因为字符编号不同而发生冲突。
十一、Unicode 为什么还需要 UTF-8?
很多初学者都会误解:
Unicode 就是 UTF-8。
其实不是。
Unicode 只规定:
中 ↓ U+4E2D但是:
如何存进内存?
如何写进文件?
如何通过网络发送?
Unicode 并没有规定。
于是出现了不同的编码方式。
例如:
UTF-16
UTF-32
UTF-8
它们都能表示 Unicode。
区别只是:
如何编码。
十二、UTF-16:Windows 的选择
UTF-16 使用两个字节作为基本单位。
亚洲文字效率较高。
Windows 至今很多内部 API 仍然采用 UTF-16。
但是它也存在问题:
英文原本:
AASCII:
1 ByteUTF-16:
2 Bytes对于英文来说浪费空间。
另外,UTF-16 还存在字节序(Endian)问题。
跨平台处理相对复杂。
十三、UTF-8:互联网最终的赢家
1992 年,Ken Thompson 和 Rob Pike 设计出了 UTF-8。
UTF-8 有几个极其优秀的特点:
第一:
完全兼容 ASCII。
例如:
ASCII A ↓ 41UTF-8:
A ↓ 41完全一致。
第二:
支持世界所有语言。
第三:
英文仍然只占一个字节。
第四:
网络传输效率高。
第五:
不存在字节序问题。
例如:
A ↓ 41中 ↓ E4 BD A0😊 ↓ F0 9F 98 8A由于互联网中英文内容占比一直很高。
UTF-8 几乎兼顾了:
兼容性
效率
扩展性
最终成为互联网事实上的统一标准。
今天:
HTML
JSON
XML
Linux
Git
Python
Go
Rust
JavaScript
几乎全部默认采用 UTF-8。
十四、字符集发展的本质
回顾整个发展过程,会发现每一次升级,其实都是在解决上一代无法解决的问题。
ASCII:
解决了英文字符统一编码的问题。
扩展 ASCII:
尝试加入更多欧洲字符。
Code Page:
解决各地区本地化问题。
GB2312、GBK:
解决中文输入输出问题。
Unicode:
解决全球字符编号统一问题。
UTF-8:
解决 Unicode 如何高效存储和网络传输的问题。
整个字符编码的发展史,本质上就是计算机逐渐从"只服务英语世界",成长为"支持全世界所有文字"的过程。
总结
很多初学者会觉得字符集非常复杂。
实际上,只需要记住一句话:
字符集(Character Set)决定"一个字符对应什么编号";编码(Encoding)决定"这个编号如何存储成字节"。
ASCII 是字符集,也是编码。
Unicode 是字符集,不是具体编码。
UTF-8、UTF-16、UTF-32 是 Unicode 的不同编码方式。
理解了这一点,再去学习操作系统、网络协议、编译器、数据库、浏览器,就会发现很多曾经令人困惑的问题,其实都可以归结为一句话:
计算机从来不认识文字,它只认识数字;而字符编码,就是人类与计算机之间约定好的翻译规则。