- Published on
2.3.编码
- Authors

- Name
- xiaobai
1.计算机如何表示文字?
计算机底层只能处理和存储二进制数(0和1)。那么,我们看到的字母、汉字、表情符号等,是如何被计算机存储和显示的呢?
答案就是:编码。 编码就是一套映射规则,它规定了每个字符对应哪个二进制数字。
2.一个简单的例子:数字编码字母
为了更好地理解,我们可以用"数字编码字母"来举例:
假设我们规定:
- 字母
A用数字1表示 - 字母
B用数字2表示 - 字母
C用数字3表示
在这种规则下,1 就是 A 的"编码"。只要发送方和接收方都知道这套规则(即"编码表"),看到数字 1 就能明白它代表字母 A,从而顺利交流信息。
计算机编码的本质与此完全相同。
3.编码的发展历程
3.1.阶段一:ASCII码 - 英文字符的专属编码
早期计算机主要在英语国家使用,所以诞生了ASCII(美国信息交换标准代码)。
text = "Hello"
encoded = text.encode('ascii')
print(encoded) # b'Hello'
print(encoded.hex()) # 48656c6c6f
print(''.join(f'\\x{b:02x}' for b in encoded)) # \x48\x65\x6c\x6c\x6f
print(encoded.decode('ascii')) # Hello
字节串中ASCII字符直接显示为字符
格式说明:{b:02x} 可以分解为:
b- 要格式化的变量:- 格式说明符开始0- 填充字符2- 最小宽度x- 格式类型(十六进制小写)
ASCII的特点:
- 只有128个字符(英文字母、数字、标点)
- 每个字符1个字节
- 使用7位二进制数(后来扩展为8位),共可以表示128(或256)个字符
- 包含英文字母(大小写)、数字、标点符号以及一些控制字符(如换行、响铃)
- 局限性:无法表示中文、日文、阿拉伯文等成千上万的字符
| 字符 | ASCII码(十进制) | ASCII码(二进制) |
|---|---|---|
A | 65 | 01000001 |
a | 97 | 01100001 |
0 | 48 | 00110000 |
3.2.阶段二:本地化编码 - 各自为政的时代
为了表示各自的语言,各个国家和地区制定了不同的编码标准。
# 中文GB2312/GBK编码示例
text = "中国"
gbk_encoded = text.encode('gbk')
print(gbk_encoded) # b'\xd6\xd0\xb9\xfa'
各国编码标准:
- 中国:GB2312 → GBK → GB18030
- 台湾:Big5
- 日本:Shift-JIS
- 韩国:EUC-KR
问题来了:"乱码"的根源 如果你用一个编码(比如GBK)去保存文件,却用另一个编码(比如BIG5)去打开它,就会显示一堆乱七八糟的字符,这就是"乱码"。因为同一串二进制数字在不同编码规则下对应着不同的字符。
结果:乱码问题严重,各国之间无法正常交流文本信息!
3.3.阶段三:Unicode - 世界大同的字符集
为了解决混乱,Unicode应运而生。它的目标很宏大:为世界上所有语言的所有字符,都赋予一个全球唯一的编号(这个编号叫做"码点")。
Unicode字符集演示
# Unicode 字符集包含全球所有字符
characters = [
'A', # 拉丁字母
'中', # 中文
'🍕', # Emoji
'α', # 希腊字母
'あ' # 日文
]
for char in characters:
print(f"字符: {char}, Unicode码点: U+{ord(char):04X}")
输出:
字符: A, Unicode码点: U+0041
字符: 中, Unicode码点: U+4E2D
字符: 🍕, Unicode码点: U+1F355
字符: α, Unicode码点: U+03B1
字符: あ, Unicode码点: U+3042
格式说明:{ord(char):04X} 可以分解为:
ord(char)- 获取字符的Unicode码点:- 格式说明符开始0- 填充字符4- 最小宽度X- 格式类型(十六进制大写)
Unicode的特点:
- 只定义字符和编号的对应关系,不关心这个编号在计算机中如何存储
- 例如:
- 字符
A的Unicode码点是U+0041(和ASCII的65对应) - 汉字
你的Unicode码点是U+4F60 - 笑脸
😊的Unicode码点是U+1F60A
- 字符
3.4.阶段四:Unicode编码方案 —— 如何将Unicode存储为二进制
Unicode 只负责为每个字符分配一个唯一的“码点”(可以理解为身份证号),但并不规定这些码点在计算机中具体如何存储。要让字符真正变成可以存储和传输的二进制数据,还需要编码方案(如 UTF-8、UTF-16 等)来规定“码点”如何转换为字节序列。
简而言之:Unicode 解决了“每个字符的编号是多少”,而编码方案解决了“如何把编号变成计算机能存储的字节”。
在 Unicode 出现之前,通常是直接将编号存入计算机,但随着字符数量的激增,这种方式会极大浪费存储空间。因此,Unicode 设计了多种编码方案,以便在不同场景下高效地存储和传输字符。
3.4.1.UTF-8(最常用)
text = "Hello 世界 🍕"
utf8_encoded = text.encode('utf-8')
print(f"UTF-8编码: {utf8_encoded}")
print(f"长度: {len(utf8_encoded)} 字节")
# 分析每个字符的UTF-8编码
for char in text:
encoded_char = char.encode('utf-8')
print(f"'{char}' -> {encoded_char} ({list(encoded_char)})")
3.4.2.UTF-16
text = "Hello 世界 🍕"
utf16_encoded = text.encode('utf-16')
print(f"UTF-16编码: {utf16_encoded}")
print(f"长度: {len(utf16_encoded)} 字节")
3.4.3.UTF-32
text = "Hello 世界 🍕"
utf32_encoded = text.encode('utf-32')
print(f"UTF-32编码: {utf32_encoded}")
print(f"长度: {len(utf32_encoded)} 字节")
UTF-8的特点:
- 可变长度:一个字符可以用1到4个字节表示
- 兼容ASCII:所有ASCII字符的UTF-8编码和其ASCII编码完全相同,这意味着一个纯ASCII文件也是一个合法的UTF-8文件
- 高效:对于英文网站,UTF-8比固定使用2或4字节的UTF-16/UTF-32更节省空间
正因为这些优点,UTF-8成为了互联网和现代软件的默认编码标准。
4.编码方案比较
| 编码方案 | 英文字母 | 中文字符 | Emoji | 特点 |
|---|---|---|---|---|
| UTF-8 | 1字节 | 3字节 | 4字节 | 兼容ASCII,网络标准 |
| UTF-16 | 2字节 | 2字节 | 4字节 | Java、Windows内部使用 |
| UTF-32 | 4字节 | 4字节 | 4字节 | 定长,简单但浪费空间 |
所以,一个字符的完整生命周期是: 字符 → Unicode码点(抽象编号) → UTF-8编码(具体字节)
以汉字"你"为例:
- 字符是
你在Unicode花名册里,它的码点是U+4F60通过UTF-8编码规则,这个码点被转换成3个字节的二进制数据:\xE4\xBD\xA0(这是十六进制表示)
5.编程中的实践
在编程时,你会遇到两种基本类型:
str(字符串):在内存中表示的文本,可以看作是Unicode字符的序列。它是人类可读的。s = "Hello 世界"bytes(字节串):二进制数据,是字符经过编码后的实际存储形式。它以b'...'的形式显示。b = b'Hello \xe4\xb8\x96\xe7\x95\x8c'
它们之间的转换就是编码和解码:
编码:
str→bytes将人类可读的文本转换为用于存储/传输的二进制数据。print("你".encode('utf-8')) # 得到 b'\xe4\xbd\xa0'解码:
bytes→str将接收到的二进制数据转换回人类可读的文本。print(b'\xe4\xbd\xa0'.decode('utf-8')) # 得到 "你"
关键:解码时使用的编码必须与编码时使用的编码一致,否则就会产生乱码。
6.总结
6.1.大纲
- 字符集:定义"有什么字符"以及它们的码点是多少(Unicode是现代标准)
- 编码:定义"如何存储字符"(UTF-8是最佳实践)
- 关系:一个字符集可以有多种编码方案
- 黄金法则:用什么编码存储,就用什么编码读取
6.2.现代编程建议
在现代编程中,推荐始终使用:
- 字符集:Unicode
- 编码:UTF-8
这样可以避免大多数字符编码问题!
6.3.记忆口诀
记住这个简单的流程,你就能驾驭绝大多数编码问题: 输入字节流 --(解码)--> 程序内文本 --(编码)--> 输出字节流

