一个“鱼”字,在电脑里到底是怎么保存的?我们每天都会在电脑、手机里输入文字,但你有没有想过,当我们在记事本中输入一个“鱼”字,并点击保存时,硬盘里真的存进去一个“鱼”字吗?硬盘不认识汉字,也不认识英文字母。它最终保存的,只是一串二进制数据。例如,简体“鱼”和繁体“魚”,在 UTF-8 编码下分别是:那么,这些十六进制数字是怎么变成文字的?这就要从最早的 ASCII 编码说起。一、为什么字符“1”是 31?
早期计算机主要用于处理英文,因此产生了 ASCII 编码。ASCII 为英文字母、数字、标点和控制符分配了固定编号。例如: | | | |
|---|
0 | | 30 | 0011 0000 |
1 | | 31 | 0011 0001 |
2 | | 32 | 0011 0010 |
A | | 41 | 0100 0001 |
B | | 42 | 0100 0010 |
所以,当我们在文本文件中输入:1,文件中实际保存的字节是:31,这里的 31 是十六进制,换成十进制就是:0x31 = 49。字符“1”和数值 1,并不是一回事,字符“1”是用来显示的文字,其 ASCII 编码是:31。一个32位整数 1,在常见的小端系统中,内存里可能是:01 00 00 00,也就是说:数值 1 ≠ 字符“1”,一个用于数学计算,一个用于显示文本。二、ASCII 能表示多少字符?
标准 ASCII 使用7个二进制位,它一共可以表示:2⁷ = 128个编号,范围为:其中既有可见字符:A、B、a、b、0、1、!、@,也有一些控制字符:Windows 文本文件传统上使用:0D 0A,也就是:CR + LF 作为换行。这也是不同系统的文本文件,有时会出现换行显示异常的原因。三、ASCII 为什么无法保存中文?
ASCII 只能表示128个字符,可以保存:Fish,对应字节为:46 69 73 68,但它无法保存:鱼。因为“鱼”不在 ASCII 的字符表里。所以,随着计算机进入不同国家和地区,各地开始制定自己的字符编码,例如:这些编码通常会保留 ASCII 的 00~7F 区域,然后使用两个或更多字节表示本地文字,例如:字符串:1鱼,保存为 GBK 时,字节是:31 D3 E3字符串:1魚,保存为 Big5 时,字节是:31 B3 BD可以看到,无论使用 GBK 还是 Big5,字符“1”仍然是 31。四、Unicode 是什么?
各个国家和地区都有自己的编码,会带来一个很大的问题,就是同一组字节,在不同编码里,可能代表完全不同的字符,为了统一全世界的文字,Unicode 出现了。Unicode 为字符分配一个统一编号,这个编号叫作码点,例如:可以把 Unicode 码点理解为字符的“身份证号码”。其中:U+,表示这是一个 Unicode 码点,后面的数字采用十六进制表示,例如:简体“鱼”和繁体“魚”不是同一个字符换了一种字体,而是两个不同的 Unicode 字符。它们含义相关,但码点不同。
五、Unicode 码点不等于文件字节
但文件和网络最终只能保存、传输字节,因此,还需要一套规则,把 Unicode 码点转换成字节。常见的 Unicode 编码方式包括:UTF-8、UTF-16、UTF-32,例如:
六、UTF-8、GBK 和 Big5 都是 Unicode 吗?
当然不是。UTF-8 是 Unicode 的编码方式,UTF-8 专门用于把 Unicode 码点转换成字节,例如:因此可以简单理解为:Unicode 规定字符编号,UTF-8 规定编号如何存成字节,GBK 不是 Unicode,GBK 是传统中文编码,有自己独立的字符映射表。例如:GBK 和 Unicode 之间可以转换,但 GBK 本身不是 Unicode 编码。Big5 也不是 Unicode,Big5 是传统繁体中文编码,同样拥有自己的字符表。例如: | | | | |
|---|
1 | U+0031 | 31 | 31 | 31 |
| U+9C7C | E9 B1 BC | D3 E3 | |
| U+9B5A | E9 AD 9A | F4 7E | B3 BD |
七、GBK 和 UTF-8 是怎么转换的?
GBK 和 UTF-8 一般不会直接互相“硬算”,转换通常要经过 Unicode 字符这一层,例如,简体“鱼”从 GBK 转换为 UTF-8:因此,现代程序内部通常先把外部字节解码成 Unicode 字符,再根据需要编码为另一种格式。
八、为什么会出现乱码?
字节本身并不天然代表某个字符,它必须配合正确的编码规则,才能被解释为文字。例如,简体“鱼”的 UTF-8 字节是:但如果软件错误地把它当成 GBK 字节读取,就可能显示成别的汉字、特殊符号,甚至出现无法识别的内容。可以把它理解为,实际语言:日语,使用词典:英语词典,结果:翻译失败。所谓乱码,很多时候并不是文件损坏,而是保存时用了一套编码,打开时却用了另一套编码。
九、UTF-8 为什么成为主流?
UTF-8 已经成为互联网、程序开发和跨平台文本交换中最常见的编码。它有几个明显优势:1. 可以编码全部 Unicode 字符,UTF-8 可以表示 Unicode 中定义的字符,包括:有些生僻字虽然可以正常保存在 UTF-8 文件中,但如果当前字体不支持,可能显示成:ASCII 范围内的字符,在 UTF-8 中保持不变,例如:字符“1” ASCII:31 Unicode:U+0031 UTF-8:31字符“A” ASCII:41 Unicode:U+0041 UTF-8:41这意味着原有的英文文本、程序代码和网络协议,可以自然兼容 UTF-8。3. UTF-8 是变长编码,UTF-8 根据字符范围使用不同数量的字节。
十、一个文件里实际存了什么?
假设在记事本中输入:1鱼魚,并使用 UTF-8 无 BOM 保存,文件中的实际字节是:如果保存成带 BOM 的 UTF-8,文件开头会增加三个字节:EF BB BF 31 E9 B1 BC E9 AD 9A这里也能看出,文本文件里并没有真正保存“鱼”的图形,而是保存了一串字节。
十一、UTF-8 和带 BOM 的 UTF-8 有什么区别?
BOM 是 Byte Order Mark,中文常译为“字节顺序标记”。对于 UTF-8 来说,它不负责解决大小端问题,更多是作为一个文件签名,用于告诉软件:这个文件采用 UTF-8 编码。UTF-8 BOM 的固定字节是:EF BB BF。
十二、记事本里的 ANSI、UTF-16 和 GB18030 是什么?
在简体中文 Windows 中,它通常对应当前系统代码页,常见的是 CP936,大体相当于 GBK。但在英文、日文或其他语言系统中,“ANSI”可能代表不同编码。因此,ANSI 文件跨系统使用时比较容易乱码。UTF-16 LE,LE 是 Little Endian,也就是小端字节序。例如简体“鱼”:UTF-16 LE:7C 9CUTF-16 BE,BE 是 Big Endian,也就是大端字节序。简体“鱼”:UTF-16 BE:9C 7CGB18030,GB18030 是我国的国家标准字符编码,也是 GBK 的扩展。(GBK是GB2132的扩展)它能够表示比 GBK 更多的字符,并与 Unicode 建立映射关系。不过在普通程序开发、网页和跨平台文本交换中,UTF-8 仍然更加常见。
十三、那个需要 MagicWin 的年代
经历过 Windows 95、Windows 98 的用户,可能还记得一个软件:如果在简体中文 Windows 中运行一个使用 Big5 编码的繁体游戏,系统可能会错误地按照 GBK 解释文字。于是菜单、按钮和剧情就会变成乱码。帮助旧版 Windows 和应用程序识别、转换或显示不同的内码。大致过程可以理解为:这串字节究竟应该按照哪一种编码解释
在 Unicode 和 UTF-8 尚未全面普及的年代,MagicWin、南极星等软件,承担了多语言兼容层的角色。当年打开繁体游戏之前先开 MagicWin,就像现在运行老游戏之前先设置兼容模式。不是游戏不会说中文,而是电脑听不懂它说的那套“方言”。😂
十四、C# 中的字符串是什么编码?
在 C#中,string text="1鱼魚";.NET 的 string 在逻辑上保存的是 Unicode 字符序列,底层使用 UTF-16 代码单元表示。当字符串需要写入文件、数据库字段或网络时,才要决定输出编码。转换成 UTF-8:byte[] bytes=Encoding.UTF8.GetBytes("1鱼魚");Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);Encodinggbk=Encoding.GetEncoding("GBK");byte[] bytes=gbk.GetBytes("1鱼魚");所以,程序中的字符串和文件中的字节,并不是完全相同的概念。十五、现在保存文件应该选什么编码?
默认使用 UTF-8,只有对接旧系统时才使用其他编码。 | |
|---|
| |
| C#、Java、Python、JavaScript | |
| |
| |
| |
| |
| |
| |
| |
尤其是在工业软件、旧数据库、串口设备和历史接口中,不能因为 UTF-8 更现代,就直接修改编码。编码选择最重要的原则是:总结
计算机看到的始终只是数字,真正把这些数字解释成“1”“鱼”和“魚”的,是人们共同约定的字符编码规则。所以,电脑里并没有真正养着一条“鱼”。它保存下来的,只是:比如早期的《三国志》《轩辕剑》《大富翁》等繁体中文版的游戏,另外当年的日文游戏或软件经常用Shift-JIS,但是Windows 错误按照GBK来计算,所以也容易出现乱码,比如:
假设游戏菜单中有日文:
也就是“开始”。
按照日文 CP932 编码,它的字节是:
日文 Windows 按照 CP932 读取:
但是,如果简体中文 Windows 错误地按照 GBK 解码:
于是游戏菜单中原本应该显示的:
就可能变成:
这就是非常典型的乱码。
“鱼”的保存,简单介绍完了,
下期可以聊聊通信协议……