RunToolz iconRunToolz
Welcome to RunToolz!
编码Unicode开发者工具

字符编码详解:从ASCII到UTF-8

为什么你的文本有时会变成问号和乱码。一份关于字符编码的实用指南。

RunToolz Team2026年1月16日6 min read

你打开一个文件,看到的是ü而不是u。或者数据库在应该显示名字的地方返回????。又或者收到一封邮件,主题里散布着=?UTF-8?B?

欢迎来到字符编码问题的精彩世界。

简短的历史

计算机存储的是数字,不是字母。所以必须有人决定哪个数字代表哪个字母。1960年代,ASCII将0-127的编号分配给英文字母、数字和基本符号。字母"A"是65。空格是32。很简单。

但ASCII只覆盖128个字符。对英文够用。对德语变音符号、日语汉字、阿拉伯文字,或者人类实际使用的成千上万其他字符,就不够了。

Unicode之前的混乱

不同地区发明了各自的编码。西欧有ISO-8859-1。日本有Shift-JIS。俄罗斯有KOI8-R。中国有GB2312。每种在自己的生态系统内都运行良好。一旦混用,一切都崩溃了。

用一种编码保存、用另一种编码打开的文件会产生乱码——那种你可能见过的错误字符大杂烩。UTF-8文件被当作ISO-8859-1读取时,cafe会变成café

Unicode解决了映射问题

Unicode给每个字符一个唯一的编号(称为"码位")。拉丁字母A是U+0041。雪人是U+2603。每个表情符号、每种文字系统、每个数学符号都有自己的码位。超过15万个字符,还在持续增长。

但Unicode只是映射。它不说明如何将这些数字存储为字节。那是编码的工作。

想亲自试试吗?统计字符和字节数

UTF-8:胜出的编码

UTF-8是互联网大部分存储Unicode文本的方式。关键技巧:每个字符使用可变数量的字节。

  • ASCII字符(英文字母、数字):每个1字节
  • 欧洲带重音字符:每个2字节
  • 亚洲字符(CJK):每个3字节
  • 表情符号和稀有符号:每个4字节

这意味着UTF-8中的英文文本与ASCII完全相同。旧系统不会出问题。但你仍然可以表示世界上任何字符。

目前超过98%的网站使用UTF-8。编码之战已经结束,UTF-8获胜了。

UTF-8 vs UTF-16 vs UTF-32

UTF-8: 可变宽度(1-4字节)。对英文为主的文本高效。Web标准。

UTF-16: 可变宽度(2或4字节)。JavaScript、Java和Windows内部使用。每个字符至少2字节,所以对ASCII文本效率较低。

UTF-32: 固定宽度(每字符4字节)。简单但浪费。很少用于存储或传输。

JavaScript的string.length计算的是UTF-16代码单元,不是字符。所以"😀".length返回2,不是1。

当编码出问题时

用错误的编码读取文件。 字节本身没问题,但被错误地解释了。解决方案:打开时指定正确的编码。

数据库字符集不匹配。 应用发送UTF-8,但数据库列设置为latin1。ASCII范围外的字符被损坏。解决方案:将数据库设为utf8mb4(MySQL中不是utf8,那个只处理3字节字符)。

HTTP缺少charset头。 如果服务器不发送Content-Type: text/html; charset=utf-8,浏览器只能猜。有时猜错。

想亲自试试吗?URL编码/解码

实用建议

始终使用UTF-8。 除非有非常具体的理由不用,UTF-8是所有场景的正确选择。

明确声明编码。 HTML中:<meta charset="utf-8">。HTTP中:Content-Type: text/html; charset=utf-8。不要让系统去猜。

注意字符串长度。 在JavaScript中,字符计数遇到表情符号和组合字符时会变得复杂。使用Array.from(str).lengthIntl.Segmenter API来获取准确计数。

注意BOM。 字节顺序标记(U+FEFF)有时出现在UTF-8文件开头。它不可见但可能导致解析器出错。某些编辑器会默默添加它。


字符编码不是什么令人兴奋的话题,但理解它能省下数小时的调试时间。到处使用UTF-8,明确声明它,当你看到乱码时,就会准确知道该去哪里找原因。