1. Введение
想象你是国际峰会上的外交官:每个人说不同的语言、用不同的书写系统。要互相理解就需要一个通用的翻译器——一套共同的规则把符号对应起来。在计算机里,这个角色就是编码。
计算机只懂一种“语言”——0 和 1 的序列。0 和 1 是它的“字母表”。所有信息都以字节形式存储和传输。一个字节是 8 位(例如,01000001)。
那我们如何把我们的字母和符号和字节联系起来呢?计算机如何知道字母 «А» 不是随便一串 0/1,而是在屏幕上显示的那个符号?
Кодировка
编码就是一套规则(对应表),定义了每个字符(字母、数字、标点、汉字、表情)如何被转换成字节序列,以及这些字节如何被解释回字符。
类比一下摩尔斯电码:你把文本翻成点和划,传输过去,接收方按同样的规则还原字符。在计算机里“字节 ↔ 字符”的约定就是编码。
2. Зачем же нам нужна эта головная боль с кодировками?
- 在人人和机器之间翻译:没有编码,文本只是字节;有了编码,字节才有意义,变成可读的字符。
- 通用性和兼容性:不同程序和操作系统需要就规则“达成协议”。如果一个文件声明是 UTF-8,那么读取时也应该用 UTF-8。
- 支持多种语言和符号:西里尔、阿拉伯文、汉字、数学符号、表情——字符集越广,编码就越复杂、越灵活。
3. ASCII – «первобытная» кодировка
最老也最基础的编码之一是 ASCII(American Standard Code for Information Interchange)。它用 7 位表示一个字符,也就是可以表示 128 个不同的符号:拉丁字母(A-Z, a-z)、数字(0-9)、标点和控制码(比如换行、制表)。
| 字符 | 十进制代码 (ASCII) | 二进制代码(7 位) |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 空格 | |
|
历史上第八位经常用于奇偶校验位,后来人们开始在“扩展” ASCII 时使用它——于是出现了针对不同语言区域的单字节编码,这就导致了混乱。
如果你写下 "Hello",在磁盘上大概就是这样(每个字符 1 字节;对 7 位 ASCII 来说最高位为 0):
H (01001000) e (01100101) l (01101100) l (01101100) o (01101111)
很简单。但俄语字母或汉字在哪儿?在 ASCII 里没有——它是个“单语言字典”,只适合最基本的拉丁集合。
4. «Кракозябры» — почему кодировка это не шутки
有时打开文件会看到像 Привет 这样的东西而不是 «Привет》。这就是所谓的“кракозябры”(或 Mojibake
假设你把 «Привет, мир!» 保存为 Windows-1251,在该编码中 «Привет» 的字节可能是这样的(简化表示):
- П → 207
- р → 240
- и → 232
- в → 226
- е → 229
- т → 242
然后你的同事用一个期望 ISO-8859-1(Latin-1) 的编辑器打开文件,或者你用了不带显式编码的 StreamReader,实际使用的编码和文件不匹配。结果:字节 207 被当成另一张表里的字符来解释——文本就被“搞坏”了。
| 原始符号(Windows-1251) | 字节表示(示例) | 按 ISO-8859-1 读取时的字符 |
|---|---|---|
| П | |
Ç |
| р | |
à |
| и | |
è |
| в | |
â |
| е | |
å |
| т | |
ò |
所以我们会得到 Çàèâåò 而不是 «Привет». 如果在目标编码里根本没有对应字符,你会看到方块或问号。
从实践角度的主要结论是:读写文本时一定要显式指定编码,尤其是当数据来源不受你控制时。在 .NET 里可以通过在 StreamReader/StreamWriter 中指定合适的 Encoding(比如 UTF-8 或 Encoding.GetEncoding("windows-1251"))来做到这一点。这能帮助避免“кракозябры”,并确保不同系统之间的数据交换正确。
在接下来的讲座里,我们会学会如何在处理文件和流时自信地选择和指定编码,让你的代码具有通用性、国际化和可靠性。
GO TO FULL VERSION