4. 文件编码:ASCII、UTF-8、GBK等编码方式,编码与解码
说到文件读写,编码问题绝对是个绕不开的坎儿。我刚开始写Python那会儿,就因为在编码上栽过跟头——明明文件内容看着没问题,一读出来全是乱码。后来才明白,这背后其实是计算机怎么存字符、怎么读字符的问题。
说白了,编码就是一套「字符 ↔ 二进制」的映射规则。你写了个「中」字,计算机不认识,它只认0和1。编码就是告诉计算机:这个「中」字,你得存成这么一串二进制。反过来,解码就是把这串二进制再变回「中」字。
核心一句话:编码是写文件时的规则,解码是读文件时的规则。两边必须用同一套规则,否则就是乱码。
4.1 常见的编码方式
目前市面上主流的编码方式,其实就那么几种。我按出现的时间顺序给你捋一捋。
4.1.1 ASCII
这是最古老的编码,诞生于1960年代。它只用了7个bit,总共能表示128个字符。包括英文字母、数字、标点符号和一些控制字符。嗯,这里要注意:ASCII里没有中文,也没有其他非英文字符。
我记得刚学编程时,老师就说:「ASCII是编码的祖宗。」确实,后来的编码大多兼容它。比如大写字母'A',ASCII里是65(十进制),二进制就是01000001。
4.1.2 GBK
中国人要用中文啊,ASCII不够用。于是有了GB2312,后来又扩展成GBK。GBK全称是「汉字内码扩展规范」,它能表示两万多个字符,包括简体中文、繁体中文、日文假名等。
GBK的特点是:英文字符用1个字节,中文字符用2个字节。我在项目中遇到过一个问题——用GBK编码的文件,在Linux系统上打开全是乱码。为什么?因为Linux默认用UTF-8,解码规则对不上。
4.1.3 UTF-8
这是目前最通用的编码,也是我个人的首选。UTF-8是Unicode的一种实现方式,它用1到4个字节表示一个字符。英文字母还是1个字节(兼容ASCII),中文通常用3个字节。
你想想看,UTF-8的好处是什么?它几乎能表示全世界所有语言的字符。而且它没有字节序的问题,跨平台兼容性极好。我现在写代码,只要不是特殊要求,一律用UTF-8。
| 编码方式 | 字节数(英文字符) | 字节数(中文字符) | 兼容性 |
|---|---|---|---|
| ASCII | 1 | 不支持 | 最基础 |
| GBK | 1 | 2 | 中文系统常见 |
| UTF-8 | 1 | 3 | 全球通用 |
4.2 Python中的编码与解码
Python里处理编码,核心就是两个方法:encode()和decode()。一个负责把字符串变成字节,一个负责把字节变回字符串。
# 编码:字符串 → 字节
text = "你好,世界"
utf8_bytes = text.encode('utf-8')
gbk_bytes = text.encode('gbk')
print(utf8_bytes) # b'\xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc\x8c\xe4\xb8\x96\xe7\x95\x8c'
print(gbk_bytes) # b'\xc4\xe3\xba\xc3\xa3\xac\xca\xc0\xbd\xe7'
# 解码:字节 → 字符串
decoded_utf8 = utf8_bytes.decode('utf-8')
decoded_gbk = gbk_bytes.decode('gbk')
print(decoded_utf8) # 你好,世界
print(decoded_gbk) # 你好,世界
看到没?同样的「你好,世界」,用UTF-8编码后是14个字节,用GBK编码后是10个字节。这就是编码方式的区别。
注意:如果用错误的编码去解码,就会得到乱码。比如用GBK去解码UTF-8编码的字节:
wrong = utf8_bytes.decode('gbk')
print(wrong) # 输出:浣犲ソ锛屼笘鐣�
这就是我当年踩过的坑。后来我养成了一个习惯:打开文件时,先确认它的编码格式。
4.3 文件读写中的编码处理
实际项目中,我们经常需要读写文件。Python的open()函数支持指定编码,我建议你每次都显式指定,不要依赖默认值。
# 写入文件(指定编码)
with open('example.txt', 'w', encoding='utf-8') as f:
f.write('你好,世界')
# 读取文件(指定编码)
with open('example.txt', 'r', encoding='utf-8') as f:
content = f.read()
print(content) # 你好,世界
如果不指定编码,Python在Windows上默认用GBK,在Linux/macOS上默认用UTF-8。这就容易出问题——你在Windows上写的文件,拿到Linux上可能就乱码了。
我的建议:所有新项目统一用UTF-8。在文件开头加一行注释:
# -*- coding: utf-8 -*-
虽然Python3默认就是UTF-8,但加上这行能让代码更清晰,也提醒自己注意编码问题。
4.4 编码错误处理
解码时遇到无法识别的字节怎么办?Python提供了几种处理方式:
strict(默认):直接抛出UnicodeDecodeErrorignore:忽略无法解码的字节replace:用问号?替换无法解码的字节backslashreplace:用转义序列\x??替换
# 假设有一段损坏的数据
broken_data = b'\xe4\xbd\xa0\xe5\xa5\xbd\xff\xe4\xb8\x96\xe7\x95\x8c'
# 不同处理方式
print(broken_data.decode('utf-8', errors='strict')) # 报错
print(broken_data.decode('utf-8', errors='ignore')) # 你好世界
print(broken_data.decode('utf-8', errors='replace')) # 你好�世界
我曾经在处理一批老旧数据时,就遇到过编码不干净的情况。当时用了errors='replace',先把数据读进来,再手动修复那些问号。虽然不完美,但至少程序不会崩溃。
4.5 编码检测
有时候你拿到一个文件,不知道它是什么编码。这时候可以用chardet库来检测:
import chardet
# 读取文件的前1024个字节
with open('unknown.txt', 'rb') as f:
raw_data = f.read(1024)
result = chardet.detect(raw_data)
print(result) # {'encoding': 'utf-8', 'confidence': 0.99}
confidence表示检测的置信度,越接近1越可靠。不过要注意,这个检测不是100%准确的,尤其是文件内容很少的时候。
避坑指南:我曾经用chardet检测一个只有几个字符的文件,它告诉我编码是ISO-8859-1,结果用UTF-8解码完全正常。所以,检测结果只能作为参考,最终还是要靠人工确认。
4.6 本章知识体系
下面这张图帮你理清编码与解码的核心逻辑:
说白了,编码和解码就是一对逆操作。你用什么规则编码,就得用什么规则解码。这个道理听起来简单,但实际项目中因为编码问题翻车的案例比比皆是。
我个人习惯是:所有文件统一用UTF-8,所有open()都显式指定encoding='utf-8'。这样虽然不能保证100%不出问题,但至少能避免90%的编码坑。
最后一个小技巧:如果你不确定一个文件的编码,用记事本打开看看。记事本会自动检测编码,虽然不一定准,但能给你一个初步判断。当然,更靠谱的还是用chardet或者file命令(Linux/macOS)。
嗯,关于编码就讲这么多。记住一点:编码是文件的「语言」,你写文件时说的什么语言,读文件时就得用同样的语言去理解。否则,计算机给你的就是一堆乱码。