Unicode 与字符编码:一篇讲明白
很多 Python 程序员都遇到过 UnicodeDecodeError,也常被问到「UTF-8 到底是什么」。这篇文章用最少的概念把这件事讲清楚:Unicode 是字符集,编码是存储方式,两者不是一回事。
一、Unicode:全世界字符的字典
Unicode 给世界上每一个字符分配了一个唯一的编号,叫码点(code point)。
码点范围:U+0000 ~ U+10FFFF(共 1,114,112 个位置)
| 字符 | 码点 | 十进制 |
|---|---|---|
| A | U+0041 | 65 |
| 中 | U+4E2D | 20013 |
| 文 | U+6587 | 25991 |
Python 里可以直接互相转换:
ord("中") # 20013 字符 → 码点
hex(20013) # '0x4e2d' 码点 → 十六进制
"\u4e2d" # '中' 码点 → 字符
"\U00004e2d" # '中' 同上,大写 U 表示 8 位十六进制二、ASCII 与 Unicode 的关系
ASCII 是最早的字符编码标准,只有 0~127 共 128 个字符,覆盖英文、数字和常用符号。
Unicode 在设计上完全兼容 ASCII:码点 U+0000~U+007F 就是 ASCII 那 128 个字符。
| ASCII | Unicode | |
|---|---|---|
| 范围 | 0~127(128 个) | 0~10FFFF(110 万+) |
| 能表示 | 英文、数字、符号 | 全世界所有文字 |
| 存储占用 | 固定 1 字节 | 1~4 字节(取决于编码方式) |
一句话结论:ASCII 是 Unicode 的子集,Unicode 是 ASCII 的超集。
三、编码:把码点变成字节
码点只是「编号」,计算机最终要存的是字节。把这个编号翻译成字节的过程,叫编码(encoding)。
以「中」= U+4E2D = 20013 为例,同一个字符在不同编码下占用空间完全不同:
UTF-8 → 3 字节:e4 b8 ad
UTF-16 → 2 字节:4e 2d
UTF-32 → 4 字节:00 00 4e 2d
GBK → 2 字节:中
为什么会有这么多编码?
因为没有一种方案在所有场景都最优:
| 编码 | 优势 | 典型场景 |
|---|---|---|
| ASCII | 固定 1 字节,最省空间 | 纯英文环境 |
| UTF-8 | 英文只占 1 字节,不浪费空间,且完全兼容 ASCII | Web、网络、Linux(事实标准) |
| UTF-16 | 中日韩文字统一 2 字节 | Windows、Java |
| UTF-32 | 固定 4 字节,程序处理最简单 | 程序内部计算 |
| GBK | 中文老系统的兼容方案 | 老旧中文系统 |
UTF-8 的变长规则
UTF-8 是变长编码,字节数由码点大小决定:
| 码点范围 | 字节数 | 示例 |
|---|---|---|
| U+0000 ~ U+007F | 1 | A → 0x41 |
| U+0080 ~ U+07FF | 2 | é → 0xc3 0xa9 |
| U+0800 ~ U+FFFF | 3 | 中 → e4 b8 ad |
| U+10000 以上 | 4 | 😄 → f0 9f 98 84 |
这个设计的精妙之处:纯英文文本在 UTF-8 下和 ASCII 完全一样,一个字节都不浪费。
四、Python3 里的字符串
Python 3 做了一个重要决定:str 永远是 Unicode 字符串,内部就存码点,不存在「编码歧义」。
s = "中文" # str,Unicode 字符串
b = s.encode("utf-8") # str → bytes,编码
s = b.decode("utf-8") # bytes → str,解码编码和解码是一对逆操作:
s = "中文"
b = s.encode("utf-8") # b'\xe4\xb8\xad\xe6\x96\x87'
b.decode("utf-8") # '中文'UnicodeDecodeError 的本质:用 A 编码写进去,用 B 编码读出来,翻译对不上。
五、文件读写:最容易踩坑的地方
str 存进文件时必须指定编码。Python 3 不会帮你猜,也不会报错——它会默默使用平台默认值,在你的电脑上正常,换一台机器就炸。
# ✅ 推荐:显式指定编码
with open("file.txt", "w", encoding="utf-8") as f:
f.write("中文")
with open("file.txt", encoding="utf-8") as f:
content = f.read()
# ❌ 不推荐:依赖系统默认
open("file.txt", "w").write("中文")补充:很多老代码里会写
sys.setdefaultencoding("utf-8"),这是无效的——Python 3 里这个函数被冻结了,改不了任何行为。真正要改的是open()的encoding参数,以及文件本身声明的编码。
六、记住这一句话就够了
Unicode 是字符集(有哪些字符),编码是存储方式(怎么存字节)。
Python3 里
str用 Unicode,bytes用编码;两者之间的转换只有encode()和decode()。
速查表
ord("中") / chr(20013) # 码点 ↔ 字符
"\u4e2d" # 转义写法
s.encode("utf-8") / b.decode("utf-8") # str ↔ bytes
open(f, encoding="utf-8") # 读文件