第七篇:字符编码——从 ASCII 到 Unicode
前言
你是否遇到过乱码问题?中文网页显示为 ä¸å›½、文件名变成问号,或者爬虫爬回来的数据打不开?这些问题的根源都是字符编码。本文彻底讲解 Python 中的字符编码体系:ASCII、GB2312、Unicode、UTF-8 的发展历程与区别,Python 3 中字符串的编码方式,以及 encode() 和 decode() 的正确用法。
什么是字符编码?
计算机只能存储和处理数字(0 和 1)。为了让计算机能够表示字母、汉字等字符,人类发明了编码表——为每个字符分配一个唯一的数字编号。
字符 'A' → 编码表查找 → 数字 65 → 二进制 01000001 → 存入内存
字符 '中' → 编码表查找 → 数字 20013 → 二进制 ... → 存入内存
不同的编码标准有不同的规则,混用编码就会乱码。
ASCII 编码:一切的起点
ASCII(American Standard Code for Information Interchange)是最早的字符编码标准,发布于 1963 年。
| 项目 | 说明 |
|---|---|
| 位数 | 7 位(实际占 1 字节) |
| 字符数 | 128 个(0 ~ 127) |
| 覆盖范围 | 英文大小写字母、数字 0-9、常用标点、控制字符 |
ASCII 编码表片段
| 十进制 | 十六进制 | 字符 | 说明 |
|---|---|---|---|
| 48 | 0x30 | 0 |
数字零 |
| 65 | 0x41 | A |
大写 A |
| 97 | 0x61 | a |
小写 a |
| 10 | 0x0A | \n |
换行符 |
| 32 | 0x20 | |
空格 |
Python 中使用 ASCII
# ord():获取字符的 ASCII 码值
print(ord('A')) # 65
print(ord('a')) # 97
print(ord('0')) # 48
# chr():将 ASCII 码值转回字符
print(chr(65)) # 'A'
print(chr(97)) # 'a'
ASCII 的局限很明显:只覆盖了英语字符,无法表示中文。
GB2312 与 GBK:中文编码方案
GB2312 是中国第一个官方汉字编码标准,发布于 1980 年。
| 编码标准 | 年份 | 收录字符 |
|---|---|---|
| GB2312 | 1980 | ~6700 汉字 |
| GBK 1.0 | 1995 | ~21000 汉字 |
| GB18030 | 2000 | ~70000 汉字 |
# 使用 GBK 编码读写文件
with open("chinese.txt", "r", encoding="gbk") as f:
content = f.read()
# 将字符串编码为 GBK 格式的字节
text = "你好,中国!"
gbk_bytes = text.encode("gbk")
print(gbk_bytes) # b'\xc4\xe3\xba\xc3\xa3\xac\xd6\xd0\xb9\xfa\xa3\xa1'
# 从 GBK 字节解码为字符串
decoded = gbk_bytes.decode("gbk")
print(decoded) # 你好,中国!
Unicode:统一字符的标准
Unicode 的核心理念:给世界上每个字符分配一个唯一的编号(码点)。
| 项目 | 说明 |
|---|---|
| 起始版本 | Unicode 1.0(1991 年) |
| 当前版本 | Unicode 15.0+ |
| 码点数 | 已分配超过 149,000 个字符 |
# Unicode 码点格式:U+XXXX
print(hex(ord('A'))) # 0x41 — U+0041
print(hex(ord('中'))) # 0x4e2d — U+4E2D
print(hex(ord('😀'))) # 0x1f600 — U+1F600
print(chr(0x4e2d)) # '中'
print(chr(0x1f600)) # '😀'
BMP(基本多文种平面)U+0000~U+FFFF 包含几乎所有常见文字。
UTF-8:Unicode 的终极实现
Unicode 定义编号,UTF-8 是实际存入文件或网络的编码格式。
为什么选择 UTF-8?
| 编码 | 字节特点 | 适用场景 |
|---|---|---|
| ASCII | 1 字节 | 仅英文 |
| UTF-8 | 1~4 字节 | 互联网首选 ✅ |
| UTF-16 | 2~4 字节 | Java 内部存储 |
| UTF-32 | 4 字节 | 固定宽度场景 |
UTF-8 的优势:
- 完全兼容 ASCII — ASCII 字符仍占 1 字节
- 自同步 — 即使读取中间位置也能正确定位字符边界
- 无字节序问题 — 不需要 BOM
UTF-8 编码规则
| 字符范围 | UTF-8 字节数 | 字节模式 |
|---|---|---|
| U+0000 ~ U+007F | 1 字节 | 0xxxxxxx |
| U+0080 ~ U+07FF | 2 字节 | 110xxxxx 10xxxxxx |
| U+0800 ~ U+FFFF | 3 字节 | 1110xxxx 10xxxxxx 10xxxxxx |
| U+10000+ | 4 字节 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
具体编码示例
# ASCII 字符 — 1 字节
print(len("A".encode("utf-8"))) # 1
# 中文(属于 BMP,占 3 字节)
print(len("中".encode("utf-8"))) # 3
# U+4E2D → E4 B8 AD
# 表情符号(占 4 字节)
print(len("😀".encode("utf-8"))) # 4
# U+1F600 → F0 9F 98 80
print("中".encode("utf-8").hex()) # e4b8ad
Python 3 中的字符串与编码
字符串的本质
Python 3 的所有字符串都是 Unicode 对象:
name = "小明"
print(type(name)) # <class 'str'>
print(len(name)) # 2 — 两个字符
print([ord(c) for c in name]) # [23567, 26126] — Unicode 码点
encode() 与 decode()
这是处理编码的核心方法:
# encode(): str → bytes(编码:字符串 → 字节序列)
text = "你好"
utf8_bytes = text.encode("utf-8")
print(utf8_bytes) # b'\xe4\xbd\xa0\xe5\xa5\xbd'
print(type(utf8_bytes)) # <class 'bytes'>
gbk_bytes = text.encode("gbk")
print(len(utf8_bytes)) # 6 — UTF-8 占 6 字节
print(len(gbk_bytes)) # 4 — GBK 占 4 字节
# decode(): bytes → str(解码:字节序列 → 字符串)
decoded_utf8 = utf8_bytes.decode("utf-8")
print(decoded_utf8) # "你好"
# ⚠️ 编码和解码必须匹配
wrong_decode = utf8_bytes.decode("gbk", errors="replace")
print(wrong_decode) # 乱码
文件的读写编码
# 写入文件时指定编码
with open("output.txt", "w", encoding="utf-8") as f:
f.write("这是一段中文文本\n")
# 读取文件时指定编码
with open("output.txt", "r", encoding="utf-8") as f:
content = f.read()
BytesIO 与 StringIO
在内存中进行字节级操作:
from io import BytesIO, StringIO
bio = BytesIO()
bio.write("你好".encode("utf-8"))
print(bio.getvalue()) # b'\xe4\xbd\xa0\xe5\xa5\xbd'
bio.seek(0)
data = bio.read().decode("utf-8")
print(data) # "你好"
常见编码问题排查指南
乱码排查步骤
# 尝试多种编码读取
filename = "data.txt"
for enc in ["utf-8", "gbk", "latin-1"]:
try:
with open(filename, "r", encoding=enc) as f:
content = f.read()
print(f"{enc}: 成功读取")
break
except UnicodeDecodeError:
continue
Emoji 导致报错
某些数据库不支持 emoji(需要 4 字节的 UTF-8),确保使用 utf8mb4 编码。
URL 中的中文
import urllib.parse
name = "北京美食"
encoded = urllib.parse.quote(name)
print(encoded) # %E5%8C%97%E4%BA%AC%E7%BE%8E%E9%A3%9F
BOM 前缀问题
with open("bom_file.txt", "r", encoding="utf-8-sig") as f:
content = f.read() # 自动跳过 BOM
编码速查表
| 编码 | 位数 | 兼容性 | 用途 |
|---|---|---|---|
| ASCII | 7 位 | 基础 | 早期英文 |
| UTF-8 | 1~4 字节 | 兼容 ASCII | 互联网首选 |
| UTF-16 | 2~4 字节 | 不兼容 | Java 内部 |
| GBK | 2 字节 | 兼容 GB2312 | Windows 中文 |
| Unicode | — | 抽象标准 | 编号系统 |
总结
本文讲解了字符编码的核心知识:
- ASCII — 128 个英文字符,编码表的起点
- GBK/GB2312 — 中文编码标准
- Unicode — 万国码编号系统
- UTF-8 — 最常用的实现,1~4 字节可变长
- encode()/decode() — Python 编码转换核心方法
- 乱码排查 — 检查编码、多编码尝试、注意 BOM
下一篇文章将深入讲解变量与常量:动态类型机制、赋值语句本质、引用模型。
本文是 Python 教程系列的第七篇。
