字符编码这东西,平时写代码时几乎感觉不到它的存在,可一旦出问题,那真是让人抓耳挠腮。乱码、问号、方块字、emoji显示成两个问号,这些场景我相信每个开发者都遇到过。我自己印象最深的一次,是帮朋友处理一个批量导入的CSV文件,明明在本地打开一切正常,传到服务器上就变成了满屏的"文件"这种鬼东西,折腾了大半天才定位到是编码不一致导致的。从那以后,我就养成了一个习惯:凡是涉及文本处理的项目,先把编码问题理清楚再动手。
这篇内容我想把ASCII码和Unicode编码这两件事彻底讲透。从它们各自的诞生背景、编码原理、对照关系,到实际开发中怎么查表、怎么转换、怎么排查乱码问题,我都会结合自己的实操经验来展开。不管你是刚入行的新手,还是工作几年但对编码始终一知半解的开发者,看完之后应该都能对"字符在计算机里到底是怎么存的"这件事有一个清晰的认识。尤其是那些经常需要处理多语言文本、做数据清洗、搞爬虫或者做国际化项目的朋友,这些知识基本是绕不开的基本功。
1. 字符编码的核心逻辑与设计思路拆解
1.1 为什么需要编码:从"人看的字"到"机器存的数"
计算机本质上只认识0和1,它不知道什么是"A",什么是"中",什么是"😀"。所谓字符编码,就是一套人为约定的映射规则,把人类可识别的字符和计算机能存储的二进制数字一一对应起来。你可以把它想象成一本字典:左边是字符,右边是对应的数字编号。编码的过程就是查字典把字符翻译成数字,解码的过程就是拿着数字反查字典把字符还原出来。
这个类比听起来简单,但问题在于,历史上不同的人、不同的组织、不同的国家,各自编了不同的字典。有的字典只收录了英文和基本符号,有的字典收录了西欧语言的特殊字母,有的字典专门收录中日韩文字。当一份用A字典编码的文件,被用B字典去解码时,就会出现"查不到"或者"查错了"的情况,这就是乱码的根源。
理解这一点非常关键,因为后面所有的编码问题,本质上都是"编码和解码用了不同的字典"导致的。你只要牢牢抓住这条主线,排查问题时就不会迷失方向。
1.2 ASCII的设计哲学:够用就好的极简主义
ASCII的全称是American Standard Code for Information Interchange,翻译过来就是"美国信息交换标准代码"。它诞生于上世纪60年代,那时候计算机主要在美国使用,只需要处理英文字母、数字、标点符号和一些控制字符,所以ASCII的设计目标非常明确:用最少的位数覆盖英文场景下的所有需求。
最终ASCII选择了7位二进制来表示一个字符,总共可以表示2的7次方等于128个字符。这128个字符被分成了几个区域:0到31是控制字符,32到126是可打印字符,127是删除控制符。为什么是7位而不是8位?因为在那个年代,存储和传输资源极其宝贵,每多一位都是成本。而且7位已经足够覆盖英文的所有需求了,没必要浪费。
这种"够用就好"的设计哲学,让ASCII在英文世界里畅通无阻,但也为后来的国际化埋下了隐患——它根本没有给其他语言留位置。
1.3 Unicode的野心:给全世界每个字符一个唯一编号
随着计算机在全球普及,ASCII的局限性越来越明显。欧洲人需要处理带重音符号的字母,中国人需要处理汉字,日本人需要处理假名,阿拉伯人需要处理从右往左书写的文字。于是各种扩展编码方案层出不穷,比如ISO-8859系列、GB2312、GBK、Shift_JIS等等。这些编码各自为政,互不兼容,导致跨语言、跨平台的文本交换变成了一场噩梦。
Unicode的出现就是为了终结这种混乱局面。它的核心思想非常朴素:给全世界每一个字符分配一个唯一的编号,这个编号叫做码点(Code Point)。不管你是英文、中文、阿拉伯文还是emoji,在Unicode里都有一个确定的数字编号。目前Unicode已经收录了超过14万个字符,覆盖了世界上绝大多数书写系统。
但Unicode本身只是一个"编号表",它并没有规定这些编号在计算机里怎么存储。比如码点U+4E2D(汉字"中")这个编号,你可以用2个字节存,也可以用3个字节存,还可以用4个字节存。具体怎么存,是由UTF-8、UTF-16、UTF-32这些实现方案来决定的。这是很多人容易混淆的地方:Unicode是字符集,UTF-8是编码方案,两者不是一回事。
1.4 方案选型的现实考量:为什么UTF-8成了事实标准
在UTF-8、UTF-16、UTF-32这三种实现方案中,UTF-8最终胜出,成为了互联网上的绝对主流。这不是偶然的,而是由它的设计特点决定的。
UTF-8最大的优势是向后兼容ASCII。在UTF-8中,0到127的字符编码方式和ASCII完全一致,都只占一个字节。这意味着一个纯英文的ASCII文件,用UTF-8解码完全没问题,不需要做任何转换。这个特性让UTF-8在推广时几乎没有阻力。
其次,UTF-8是变长编码,英文字符占1个字节,常用汉字占3个字节,emoji等特殊字符占4个字节。这种设计在存储和传输英文内容时非常节省空间,而互联网上英文内容占比很高,所以整体效率很高。
相比之下,UTF-16虽然对中文等东亚文字更友好(大部分常用汉字占2个字节),但它不兼容ASCII,而且存在字节序问题(大端序和小端序),处理起来更麻烦。UTF-32虽然定长编码处理简单,但每个字符都占4个字节,空间浪费严重,几乎没人用它来存储文件。
我在实际项目中的经验是:除非有特殊需求,否则一律用UTF-8。数据库连接、文件读写、网络传输、HTML页面声明,全部统一成UTF-8,能避免90%以上的乱码问题。
2. ASCII码对照表与核心细节全解析
2.1 ASCII控制字符:那些看不见但很重要的角色
ASCII码的0到31,加上127,一共33个字符,被称为控制字符。它们不对应任何可见的图形,而是用来控制设备行为的。比如换行、回车、制表符、响铃等等。这些字符在终端和文本处理中扮演着重要角色,虽然平时看不见,但少了它们整个文本系统就乱套了。
下面这张表列出了最常用的控制字符及其含义,这些是我在日常开发中经常打交道的:
| 十进制 | 十六进制 | 缩写 | 名称 | 说明 |
|---|---|---|---|---|
| 0 | 0x00 | NUL | 空字符 | 字符串结束标志,C语言中字符串的终止符 |
| 7 | 0x07 | BEL | 响铃 | 让终端发出提示音,现在很少用了 |
| 8 | 0x08 | BS | 退格 | 光标左移一格 |
| 9 | 0x09 | HT | 水平制表 | 就是Tab键,常用于对齐 |
| 10 | 0x0A | LF | 换行 | Unix/Linux系统的行结束符 |
| 13 | 0x0D | CR | 回车 | 光标回到行首,Windows行结束符的一部分 |
| 27 | 0x1B | ESC | 转义 | 终端控制序列的起始字符 |
| 127 | 0x7F | DEL | 删除 | 删除当前字符 |
这里有一个非常经典的坑:不同操作系统的换行符不一样。Unix/Linux用LF(0x0A),Windows用CRLF(0x0D 0x0A),老式Mac用CR(0x0D)。当你把一个Windows上创建的文本文件传到Linux服务器上处理时,如果代码里没有处理CRLF,就可能出现每行末尾多一个不可见字符的情况,导致字符串比较失败、正则匹配异常等问题。我踩过这个坑,当时排查了很久才发现是换行符的问题。
注意:在处理跨平台文本文件时,建议统一用二进制模式读取,然后手动处理换行符,或者使用能自动识别换行符的库。Python中的
open()函数用newline=''参数可以保留原始换行符,方便后续处理。
2.2 可打印字符:从空格到波浪号的完整对照
ASCII码的32到126是可打印字符,共95个。这部分是我们日常最常接触的,包括空格、标点符号、数字、大小写字母。下面按类别整理一下关键区间的对照关系:
| 十进制范围 | 十六进制范围 | 字符内容 | 说明 |
|---|---|---|---|
| 32 | 0x20 | 空格 | 最常用的分隔符 |
| 33-47 | 0x21-0x2F | ! " # $ % & ' ( ) * + , - . / | 标点符号和运算符号 |
| 48-57 | 0x30-0x39 | 0-9 | 数字字符 |
| 58-64 | 0x3A-0x40 | : ; < = > ? @ | 更多标点符号 |
| 65-90 | 0x41-0x5A | A-Z | 大写英文字母 |
| 91-96 | 0x5B-0x60 | [ \ ] ^ _ ` | 方括号、反斜杠等 |
| 97-122 | 0x61-0x7A | a-z | 小写英文字母 |
| 123-126 | 0x7B-0x7E | { | } ~ | 花括号、竖线、波浪号 |
这张表里有一个非常实用的规律:大写字母A的ASCII码是65,小写字母a的ASCII码是97,两者相差32。这意味着大写转小写只需要加32,小写转大写只需要减32。这个规律在很多底层代码优化中会用到,比如某些语言中大小写转换的快速实现。
另一个规律是数字字符'0'到'9'的ASCII码是48到57,所以字符'5'的ASCII码是53,要把它转成数字5,只需要用53减去48即可。这个操作在解析数字字符串时非常常见。
2.3 ASCII码的查询与转换实操
在实际开发中,我们经常需要在字符和ASCII码之间来回转换。不同语言提供了不同的方法,我整理了几个常用语言的写法:
Python中的转换:
# 字符转ASCII码 print(ord('A')) # 输出 65 print(ord('中')) # 输出 20013(这是Unicode码点,不是ASCII) # ASCII码转字符 print(chr(65)) # 输出 'A' print(chr(20013)) # 输出 '中' # 批量查看字符串的编码 text = "Hello" for char in text: print(f"{char} -> {ord(char)}")JavaScript中的转换:
// 字符转ASCII码 console.log('A'.charCodeAt(0)); // 输出 65 // ASCII码转字符 console.log(String.fromCharCode(65)); // 输出 'A' // 获取Unicode码点(支持emoji等) console.log('😀'.codePointAt(0)); // 输出 128512Java中的转换:
// 字符转ASCII码 int ascii = (int) 'A'; System.out.println(ascii); // 输出 65 // ASCII码转字符 char ch = (char) 65; System.out.println(ch); // 输出 'A'这里有一个容易踩的坑:Python的ord()函数返回的是Unicode码点,不是ASCII码。对于ASCII范围内的字符,两者是一样的;但对于中文、emoji等字符,返回的就是Unicode码点了。如果你需要获取UTF-8编码的字节值,需要用encode()方法:
text = "中" utf8_bytes = text.encode('utf-8') print(utf8_bytes) # 输出 b'\xe4\xb8\xad' print(list(utf8_bytes)) # 输出 [228, 184, 173]2.4 扩展ASCII与代码页:混乱时代的产物
ASCII只有128个字符,对于西欧语言来说不够用,因为法语有é、è、ê、ë,德语有ä、ö、ü、ß,西班牙语有ñ等等。于是各厂商和标准组织把ASCII扩展到8位,用128到255这另外128个位置来表示这些特殊字符。这就是所谓的扩展ASCII。
问题是,128到255这段空间,不同的人有不同的用法。IBM PC用的是代码页437,西欧用的是ISO-8859-1(也叫Latin-1),Windows西欧用的是代码页1252,中文用的是GB2312,日文用的是Shift_JIS……同一段字节,在不同的代码页下显示完全不同的字符。这就是为什么早期互联网上经常看到乱码——因为发送方和接收方用的代码页不一致。
这段历史虽然已经过去,但遗留问题至今仍在。比如Windows中文系统的默认代码页是GBK,如果你用Python在Windows上打开一个UTF-8编码的文件而不指定编码,就可能报错或者显示乱码。所以我现在写代码,凡是涉及文件读写、网络请求、数据库连接,一律显式指定encoding='utf-8',绝不依赖默认值。
3. Unicode编码体系与实操转换全流程
3.1 Unicode码点的表示方法与分区结构
Unicode给每个字符分配了一个唯一的码点,通常写成U+XXXX的形式,其中XXXX是十六进制数字。比如字母A的码点是U+0041,汉字"中"的码点是U+4E2D,emoji"😀"的码点是U+1F600。
Unicode的码点空间从U+0000到U+10FFFF,总共可以容纳超过100万个字符。目前实际使用的码点大约14万个,还有大量空间留给未来扩展。这些码点被划分成了多个区域,每个区域有特定的用途:
| 码点范围 | 名称 | 说明 |
|---|---|---|
| U+0000 - U+007F | 基本拉丁字母 | 完全兼容ASCII |
| U+0080 - U+00FF | 拉丁字母补充 | 西欧语言特殊字符 |
| U+0100 - U+017F | 拉丁字母扩展-A | 更多欧洲语言字符 |
| U+4E00 - U+9FFF | 中日韩统一表意文字 | 常用汉字主要在这个区间 |
| U+1F600 - U+1F64F | Emoji表情 | 各种表情符号 |
| U+20000 - U+2A6DF | 中日韩统一表意文字扩展B | 生僻汉字 |
了解这些分区有助于你在处理特定语言文本时快速定位问题。比如你发现某个汉字显示不出来,可以查一下它的码点是否在常用汉字区间内,如果不在,可能是字体不支持或者编码转换出了问题。
3.2 UTF-8编码规则:变长编码的精妙设计
UTF-8的编码规则是它最精妙的地方,理解了这套规则,你就能手动编码和解码UTF-8了。规则其实很简单:
- 单字节字符:最高位是0,后面7位是码点。这正好覆盖了ASCII的0到127。
- 双字节字符:第一个字节以110开头,第二个字节以10开头,总共11位有效位,可以表示2048个码点。
- 三字节字符:第一个字节以1110开头,后面两个字节都以10开头,总共16位有效位,可以表示65536个码点。
- 四字节字符:第一个字节以11110开头,后面三个字节都以10开头,总共21位有效位,可以表示2097152个码点。
这套规则的好处是:解码时只要看第一个字节的开头几位,就能知道这个字符占几个字节,不需要额外的分隔符。而且由于ASCII字符的最高位是0,永远不会和后续字节的10开头混淆,所以向后兼容性完美。
我拿汉字"中"来演示一下手动编码的过程。首先查表知道"中"的Unicode码点是U+4E2D,二进制是0100 1110 0010 1101。这个码点落在三字节的范围内(U+0800到U+FFFF),所以需要三个字节来编码。
按照三字节的格式,把16位有效位填入模板:
模板:1110xxxx 10xxxxxx 10xxxxxx 码点:0100 1110 0010 1101 填入:1110[0100] 10[1110 00] 10[10 1101] 结果:11100100 10111000 10101101 十六进制:E4 B8 AD所以"中"的UTF-8编码就是E4 B8 AD,和前面Python代码输出的结果一致。你可以用这个方法验证任何字符的UTF-8编码。
3.3 编码转换的实操流程与代码示例
在实际项目中,编码转换通常发生在几个场景:文件读写、网络传输、数据库存取、API交互。下面我用Python演示几个典型场景的处理方法。
场景一:读取不同编码的文件
# 读取UTF-8文件 with open('data_utf8.txt', 'r', encoding='utf-8') as f: content = f.read() # 读取GBK文件 with open('data_gbk.txt', 'r', encoding='gbk') as f: content = f.read() # 如果不确定编码,可以用chardet库检测 import chardet with open('unknown.txt', 'rb') as f: raw = f.read() result = chardet.detect(raw) print(result) # {'encoding': 'utf-8', 'confidence': 0.99} content = raw.decode(result['encoding'])场景二:编码转换
# 把GBK编码的字符串转成UTF-8 gbk_text = "中文内容" utf8_bytes = gbk_text.encode('gbk').decode('gbk').encode('utf-8') print(utf8_bytes) # b'\xe4\xb8\xad\xe6\x96\x87\xe5\x86\x85\xe5\xae\xb9' # 更常见的写法:先解码再编码 text = gbk_bytes.decode('gbk') # 先按GBK解码成字符串 utf8_bytes = text.encode('utf-8') # 再按UTF-8编码成字节场景三:处理编码错误
# 遇到无法解码的字节时,有几种处理策略 raw = b'\xe4\xb8\xad\xff\xfe' # 策略1:忽略错误字节 text = raw.decode('utf-8', errors='ignore') print(text) # 输出 '中' # 策略2:用占位符替换 text = raw.decode('utf-8', errors='replace') print(text) # 输出 '中��' # 策略3:用反斜杠转义 text = raw.decode('utf-8', errors='backslashreplace') print(text) # 输出 '中\\xff\\xfe'实操心得:在处理来源不明的文本数据时,我通常先用
chardet检测编码,然后用errors='replace'来避免程序崩溃,同时记录下替换发生的位置,后续人工检查。直接忽略错误字节可能会导致数据丢失,需要谨慎使用。
3.4 字节序问题:大端序与小端序的恩怨
当编码方案使用多个字节表示一个字符时,就涉及到字节的排列顺序问题。比如UTF-16中,汉字"中"的码点是U+4E2D,需要两个字节来存储。这两个字节是存成4E 2D还是2D 4E?这就是字节序问题。
- 大端序(Big Endian):高位字节在前,存成
4E 2D - 小端序(Little Endian):低位字节在前,存成
2D 4E
为了解决这个问题,Unicode引入了BOM(Byte Order Mark),也就是在文件开头加一个特殊标记。UTF-8的BOM是EF BB BF,UTF-16大端序的BOM是FE FF,小端序是FF FE。读取程序看到BOM就知道该用什么字节序来解码了。
但BOM本身也带来了新问题。有些程序不认识BOM,会把它当成普通字符处理,导致文件开头多出几个不可见字符。比如在Linux下用shell脚本处理带BOM的CSV文件,第一列的列名可能就变成了\xEF\xBB\xBF列名,导致匹配失败。所以现在很多规范建议UTF-8文件不要加BOM,这也是为什么Python的utf-8编码默认不加BOM,而utf-8-sig编码会加BOM。
# 不加BOM with open('no_bom.txt', 'w', encoding='utf-8') as f: f.write('内容') # 加BOM with open('with_bom.txt', 'w', encoding='utf-8-sig') as f: f.write('内容')我在处理Excel导出的CSV文件时经常遇到BOM问题,因为Excel默认导出的UTF-8 CSV是带BOM的。解决办法就是在读取时用utf-8-sig编码,它会自动处理BOM。
4. 常见编码问题与排查技巧实录
4.1 乱码问题的系统化排查思路
乱码是编码问题最直观的表现,但乱码的样子有很多种,不同的样子对应不同的原因。我总结了一个排查流程,基本上能覆盖大部分场景。
第一步,看乱码的形态。如果显示的是"文件"这种带重音符号的拉丁字母,通常是UTF-8字节被用Latin-1解码了。如果显示的是"锟斤拷"这种汉字,通常是GBK字节被用UTF-8解码了。如果显示的是"???",说明目标编码无法表示这些字符,被替换成了问号。如果显示的是"□"或"�",说明字体不支持这些字符或者解码失败。
第二步,确认原始编码。问自己几个问题:数据是从哪里来的?是文件、网络还是数据库?来源系统通常用什么编码?有没有BOM?如果实在不确定,用chardet检测一下。
第三步,确认目标编码。你的程序期望什么编码?你的终端、编辑器、浏览器用什么编码显示?两边的编码是否一致?
第四步,定位转换环节。数据在传输过程中经过了哪些环节?每个环节有没有做编码转换?有没有可能某个环节用了默认编码而不是显式指定的编码?
下面这张表整理了几种典型乱码的对照关系,方便快速定位:
| 乱码表现 | 原始编码 | 错误解码方式 | 解决方法 |
|---|---|---|---|
| 文件 | UTF-8 | Latin-1 | 用UTF-8重新解码 |
| 锟斤拷 | GBK | UTF-8 | 用GBK重新解码 |
| �� | UTF-8 | 多次错误转换 | 追溯原始数据重新处理 |
| ??? | 任意 | 目标编码不支持 | 换用支持该字符的编码 |
| \u4e2d\u6587 | Unicode转义 | 未做转义还原 | 用转义解码函数处理 |
4.2 典型乱码案例复盘与修复
案例一:CSV文件在服务器上乱码
前面提到的那个CSV文件问题,后来我复盘了一下。朋友在Windows上用Excel创建了CSV文件,Excel默认用GBK编码保存(中文Windows系统)。文件传到Linux服务器后,Python脚本用UTF-8去读取,就出现了乱码。
修复方法有两种:一是在读取时指定encoding='gbk';二是先用GBK读取,再转成UTF-8保存。我选择了第二种,因为后续处理流程都统一用UTF-8,转换一次后面就省心了。
# 读取GBK文件并转为UTF-8 with open('data.csv', 'r', encoding='gbk') as f: content = f.read() with open('data_utf8.csv', 'w', encoding='utf-8') as f: f.write(content)案例二:网页中文显示为问号
有一次帮人看一个网页,中文全部显示成问号。排查后发现是HTML页面的meta标签声明了charset=iso-8859-1,但实际内容是用UTF-8编码的。浏览器按照声明的ISO-8859-1去解码UTF-8字节,中文就变成了问号。
修复方法很简单,把meta标签改成charset=utf-8即可。但这里有一个细节:meta标签必须放在head的最前面,否则浏览器可能在读到meta之前就已经开始解码了,导致声明失效。
案例三:数据库中文乱码
数据库乱码通常涉及多个层面的编码设置:数据库本身的字符集、表的字符集、连接字符串的字符集、客户端程序的字符集。任何一个不一致都可能导致乱码。
我的排查顺序是:先确认数据库和表的字符集,再确认连接字符串有没有指定字符集,最后确认客户端程序读写时用的编码。以MySQL为例,推荐全部统一为utf8mb4,因为它支持emoji等4字节字符,而utf8只支持最多3字节的字符。
-- 查看数据库字符集 SHOW VARIABLES LIKE 'character_set%'; -- 创建数据库时指定字符集 CREATE DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 连接字符串指定字符集 -- jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf8mb44.3 编码问题速查表与避坑清单
基于我这些年踩过的坑,整理了一份速查表,遇到编码问题时可以按这个清单逐项检查:
| 检查项 | 常见问题 | 推荐做法 |
|---|---|---|
| 文件读写 | 未指定编码,依赖系统默认 | 显式指定encoding='utf-8' |
| 网络请求 | 响应头编码与实际不符 | 用chardet检测或从响应头获取 |
| 数据库连接 | 连接字符集与表字符集不一致 | 统一用utf8mb4 |
| HTML页面 | meta声明与实际编码不符 | meta放在head最前面,声明utf-8 |
| JSON解析 | 默认用UTF-8,但数据可能是其他编码 | 确保数据源是UTF-8 |
| 终端显示 | 终端编码与输出编码不一致 | 设置LANG=en_US.UTF-8或zh_CN.UTF-8 |
| 字符串截取 | 按字节截取导致多字节字符被截断 | 按字符截取,或用支持Unicode的库 |
| 正则匹配 | 编码不一致导致匹配失败 | 统一编码后再匹配 |
避坑技巧:在Python中,我习惯在文件开头加一行
# -*- coding: utf-8 -*-,虽然Python 3默认就是UTF-8,但显式声明能避免很多编辑器识别错误的问题。另外,处理字符串时尽量用str类型而不是bytes类型,需要转换时再显式编码解码,这样能减少很多隐式转换带来的问题。
还有一个容易被忽视的点:字符串长度计算。在Python 3中,len("中文")返回2,因为按字符计算。但在某些语言或某些场景下,长度是按字节计算的,"中文".encode('utf-8')的长度是6。如果你在做数据库字段长度限制或者接口参数校验,一定要搞清楚是按字符还是按字节计算,否则可能出现"明明没超长却报错"的情况。
4.4 编码转换的性能考量与优化建议
在大规模文本处理场景下,编码转换可能成为性能瓶颈。我做过一个测试,处理100万行中文文本,每行做一次GBK到UTF-8的转换,耗时大约在几秒到十几秒之间,具体取决于机器性能。如果数据量更大,就需要考虑优化了。
优化思路有几个:一是批量转换而不是逐行转换,减少函数调用开销;二是用C扩展或底层库来做转换,比如Python的codecs模块底层就是C实现的,比纯Python快很多;三是如果可能的话,在数据源头就统一编码,避免后续转换。
import codecs # 批量转换:读取GBK文件,写入UTF-8文件 with codecs.open('input_gbk.txt', 'r', 'gbk') as f_in: with codecs.open('output_utf8.txt', 'w', 'utf-8') as f_out: while True: chunk = f_in.read(1024 * 1024) # 每次读1MB if not chunk: break f_out.write(chunk)另外,如果你的应用需要频繁做编码转换,可以考虑用缓存。比如把常用的字符到字节的映射缓存起来,避免重复计算。不过在实际项目中,编码转换通常不是主要瓶颈,除非你的数据量特别大或者QPS特别高,否则不用过早优化。
我个人在实际操作中的体会是,编码问题最好的解决方案是"统一"和"显式":统一用UTF-8,显式指定编码,不要依赖任何默认值。做到这两点,基本上就不会再被编码问题困扰了。最后再分享一个小技巧:如果你不确定一段文本的编码,可以用Python的repr()函数打印出来看看,字节串会显示成b'\xe4\xb8\xad'的形式,字符串会显示成'中'的形式,一眼就能区分。