news 2026/1/15 13:24:51

字符编码知多少(一)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
字符编码知多少(一)

前言

曾经在一场面试中,问到过UTF-8与UTF-16的区别,我一脸懵逼,惨遭羞辱。
最近在使用rider这个IDE的过程中,发现在visual studio中好好的代码,在rider中是乱码。
故此深入了解一下字符编码的前世今生。

前世:编码的蛮荒时代

由于计算机只能存储0/1二进制数据,因此计算机字符编码的起点,本质是为了解决/* by 01022.hk - online tools website : 01022.hk/zh/calcarea.html */ 字符与二进制数据的映射问题

ASCII

计算机诞生于美国,因此最早的编码需求仅针对英文字符。1967年发布的ASCII(American Standard Code for Information Interchange)成为第一个通用编码:

  1. 特点
    使用一个字节(8位)中的低7位来表示字符,因此可以表示2^7=128个字符。
    其内容主要包括,英语字母的大小写,数字0-9,标点符号,以及一些控制字符(换行\n,回车\r等符号)。
  2. 优点
    简单,高效,仅占1个字节,完全满足英文场景下的计算机使用需求,成为计算机编码的底层基石。
  3. 缺点
    只能表示英语字符,对于其它语言捉襟见肘。

眼见为实

/* by 01022.hk - online tools website : 01022.hk/zh/calcarea.html */ var originalString = "C#编程"; Console.WriteLine($"originalString = {originalString}"); //ASCII编码 try { var asciiBytes=Encoding.ASCII.GetBytes(originalString); Console.WriteLine($"ASCII 编码 (字节数: {asciiBytes.Length}):"); Console.WriteLine($" 字节: {BitConverter.ToString(asciiBytes)}"); Console.WriteLine($" 解码回字符串: \"{Encoding.ASCII.GetString(asciiBytes)}\""); } catch (Exception) { Console.WriteLine("ASCII 解码失败"); }

可以看到,对于英文字符C,与特殊符号# 。 ASCII能够正常编码解码,但对中文就无能为力了。

拓展ASCII与多字节字符(MBCS)

鉴于ASCII的缺点,各国为了能在计算机中表示自己的语言,开始制定自己的编码标准。
同样是英文语系的西欧语言,选择利用ASCII的最高位,将编码范围拓展到2^8=256个,比如ISO 8859-1。

  1. 特点
    前128位与ASCII完全一致,拓展了后128位。补充了西欧重音字符以及特殊符号
  2. 缺点
    仅覆盖西欧,对亚洲,非洲,俄文依旧不支持。

由于ASCII与ISO-8859-1无法满足多语言需求,各国纷纷开始定制专属的本地化编码,只为适配本国文字,毫无兼容性可言,因此开始了编码最混乱的时代。
像中文这样拥有成千上万字符的语言,靠拓展ASCII是远远不够的,一个字节最多表示256个字符,所以人们发明了多个字节来表示一个字符的方法。

  1. GB2312
    作为中国的国家标准,大约收录了6700个常用汉字,随着汉字的不断收录,又引入了GBK与GB18030,作为超集。它们使用1-4个字节来表示一个字符。
  2. BIG5
    中国台湾/香港地区使用的繁体中文编码标准
  3. Shift_JIS
    日本使用的编码标准

这个时候的核心问题在于,大多数国家和地区都各自为政,互不兼容。比如一个GBK编码的文件,在使用Big5编码的程序中打开,就会显示为乱码。
编码之间的混乱,导致了软件开发,数据交互的国际化几乎是不可能的。

眼见为实

// 错误地使用UTF-8来解码GBK的数据 try { Encoding gbk = Encoding.GetEncoding("GBK"); var gbkData = gbk.GetBytes(originalString); string garbledText = Encoding.UTF8.GetString(gbkData); Console.WriteLine($"GBK原始字节: {BitConverter.ToString(gbkData)}"); Console.WriteLine($"用UTF-8错误解码的结果: \"{garbledText}\""); } catch (Exception) { Console.WriteLine("解码失败"); }

使用UTF-8来解码GBK,会乱码。

过渡:Unicode 字符集的诞生

本地化编码的乱象,催生出一个核心诉求:给全球所有的字符分配唯一的数字编号,彻底摆脱编码冲突。
所以Unicode诞生的应运而生,它作为字符=>数据的映射表,它为世界上所有的文字分配了一个唯一的数据编号,这个编号被称为码点,格式为U+XXXX(XXXX为16进制数字)。

  1. 码点范围
    U+0000 ~ U+10FFFF,共划分17个平面。
  2. 核心平面
    码点范围 U+0000 ~ U+FFFF,覆盖了全球 99% 的常用字符(中英文、数字、主流符号、日韩常用字等)。
  3. 拓展平面
    剩余 16 个平面,用于表示生僻字、古文字、Emoji、专业符号等。
平面编号平面名称码点范围核心用途日常使用频率
0号基本多文种平面(BMP)U+0000~U+FFFF英文/中文/数字/主流符号✅✅✅ 100%
1号多文种补充平面(SMP)U+10000~U+1FFFFEmoji/古文字/音乐符号✅✅ 偶尔
2~3号CJK扩展平面A/BU+20000~U+3FFFF中文生僻字/古汉字✅ 极少
4~13号CJK扩展C~G+其他扩展U+40000~U+DFFFF极生冷僻字/专业符号❌ 几乎无
14号补充特殊用途平面(SSP)U+E0000~U+EFFFF专业自定义字符❌ 无
15~16号私用使用区(PUA)U+F0000~U+10FFFF完全自定义字符/保留区❌ 无

今生:UTF系列编码

虽然Unicode解决了编码的乱象,却没有规定这个数字编码应该怎么存储.

比如汉字”中“的码点是U+4E2D,这个十六进制的数字可以存为2字节,4字节。传输过程中还会遇到大端法/小端法的顺序问题。

而UTF(Unicode Transformation Format)就是这么一组编码方案。简单来说Unicode 是字符集(定义码点),而 UTF-8/16/32 是”Unicode编码方案“(将码点转换为字节序列的规则)。

UTF-32

UTF-32是Unicode最早的实现方案,思路极其简单。固定使用4个固定字节来表示一个Unicode码点,无脑将码点转换成一个32位的二进制来存储。

优点

  1. 编码/解码逻辑最简单:固定4字节表示1个Unicode码点,直接映射码点数值,无需判断长度/代理对/字节序;
  2. 完美随机访问:字符位置与字节位置一一对应(字符数=字节数/4),极致适合字符精准定位的底层场景。

缺点

  1. 空间浪费极致严重:所有字符均占4字节,纯英文文本比ASCII浪费300%,纯中文比UTF-8浪费33%,比UTF-16浪费100%;
  2. 生态支持极少:几乎无主流协议/数据库/编程语言支持,仅部分底层系统/学术场景偶尔使用;
  3. 传输/存储成本过高:完全不适合网络传输、文件存储,仅能用于内存临时处理的极特殊场景。

眼见为实

//UTF32编码 try { var utf32Bytes = Encoding.UTF32.GetBytes(originalString); Console.WriteLine($"UTF-32 (Unicode) 编码 (字节数: {utf32Bytes.Length}):"); Console.WriteLine($" 字节: {BitConverter.ToString(utf32Bytes)}"); Console.WriteLine($" 解码回字符串: \"{Encoding.Unicode.GetString(utf32Bytes)}\""); Console.WriteLine(); } catch (Exception) { Console.WriteLine("UTF-32 解码失败"); }

可以看到,空间浪费极其严重。总共用了 12 个字节。每个字符都占 4 个字节。

UTF-16

于1996年推出,是对UTF-32的优化,也是可变长度编码的首次尝试
针对Unicode的BMP字符,用2字节存储,超出BMP的拓展字符,用2个2字节(代理对)拼接存储(共4字节)

优点

  1. BMP平面字符固定2字节:常用中文(U+4E00~U+9FFF)、英文、主流符号均占2字节,纯中文场景空间效率远高于UTF-8
  2. 字符处理更高效:BMP内可随机访问字符,计算长度、截取字符串无需遍历,直接按字节数/2即可,适合编程语言内部存储;
  3. 原生适配主流语言运行时:.NET的string类型、Java的char类型、JavaScript的String类型、Windows内核均原生基于UTF-16实现,无编码转换损耗。

缺点

  1. 不兼容ASCII:英文/数字也占2字节,纯英文文本空间浪费100%,混合文本效率低于UTF-8;
  2. 存在字节序问题:分UTF-16BE(大端)、UTF-16LE(小端),跨平台传输必须加BOM标记,否则必乱码;
  3. 非BMP字符需代理对:生僻字/Emoji(扩展平面)需用「2个2字节」拼接(代理对),占4字节,解码时需额外处理代理对逻辑,易出bug;
  4. 生态兼容差:互联网协议/数据库/前端均不默认支持,仅适用于编程语言内部/Windows系统,跨端传输需转UTF-8。

眼见为实

//UTF16编码 try { var utf16Bytes = Encoding.Unicode.GetBytes(originalString); Console.WriteLine($"UTF-16 (Unicode) 编码 (字节数: {utf16Bytes.Length}):"); Console.WriteLine($" 字节: {BitConverter.ToString(utf16Bytes)}"); Console.WriteLine($" 解码回字符串: \"{Encoding.Unicode.GetString(utf16Bytes)}\""); Console.WriteLine(); } catch (Exception) { Console.WriteLine("UTF-16 解码失败"); }

相对UTF-32,存储空间占用有了极大改善,总共用了 10 个字节。每个字符都占 2 个字节(因为 "编程" 在 BMP 范围内)。

UTF-8

当前版本答案,于1992年推出。是目前最主流,最通用的Unicode编码方案。
根据字符的码点大小,动态分配字节数。
✔ 英文字母,数字占用1字节
✔ 欧洲,西亚文字占用2字节
✔ 中/日/韩文占用3字节
✔ 生僻字,Emoji,拓展符号占用4字节

优点

  1. 完全兼容ASCII:ASCII字符(英文/数字/基础标点)占1字节,与传统ASCII文件无缝互通,无历史兼容成本;
  2. 无字节序问题:无需BOM标记,跨平台(Linux/Mac/Windows)、跨系统传输零乱码风险
  3. 空间效率均衡:混合文本(英文+中文+符号)场景下,整体空间利用率最高(英文1字节抵消中文3字节的损耗);
  4. 生态兼容无敌:HTML/JSON/HTTP/数据库/主流编程语言/文件系统均默认支持,是互联网、后端开发的唯一标准
  5. 错误容错性强:自同步编码规则,解码时可快速识别字符边界,局部错误不会导致整段乱码。

缺点

  1. 中文/日韩字符占3字节:纯中文文本场景,比UTF-16多占用1字节/字符;
  2. 变长解码稍复杂:1-4字节可变长度,需通过首字节判断字符长度,无法随机访问字符(计算字符串长度需遍历,而非直接取字节数/2);
  3. 纯东亚文本空间稍浪费:无任何英文的纯中文/日文文本,空间占用比UTF-16高50%。

眼见为实

//UTF8编码 try { var utf8Bytes = Encoding.UTF8.GetBytes(originalString); Console.WriteLine($"UTF-8 编码 (字节数: {utf8Bytes.Length}):"); Console.WriteLine($" 字节: {BitConverter.ToString(utf8Bytes)}"); Console.WriteLine($" 解码回字符串: \"{Encoding.UTF8.GetString(utf8Bytes)}\""); Console.WriteLine(); } catch (Exception) { Console.WriteLine("UTF-8 解码失败"); }

总共用了 8 个字节。C(43), #(23) 各占 1 个字节,“编”(E7-BC-96) 和 “程”(E7-A8-8B) 各占 3 个字节。

挖坑待埋

  1. 为什么中文在UTF-16下占用2字节,反而在UTF-8中占用3字节了?
  2. UTF-16/UTF-8都是不定长编码规则,它们是如何解析的?
  3. UTF-16的BOM标记长什么样?
  4. 未完待续......
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/8 0:54:53

C++函数与string对象、array对象及递归详解

C函数与string对象、array对象及递归详解 一、string对象的数组操作 string对象比C风格字符串更灵活&#xff0c;可以像结构体一样进行赋值和传递。以下示例展示了string数组的用法&#xff1a; #include <iostream> #include <string> using namespace std;const …

作者头像 李华
网站建设 2026/1/14 15:42:28

Miniconda-Python3.9环境下实现PyTorch模型冷启动优化

Miniconda-Python3.9环境下实现PyTorch模型冷启动优化 在部署深度学习服务时&#xff0c;你是否遇到过这样的场景&#xff1a;系统重启后第一个用户请求响应特别慢&#xff0c;甚至超时&#xff1f;日志显示&#xff0c;并非代码逻辑问题&#xff0c;而是模型加载、依赖初始化等…

作者头像 李华
网站建设 2026/1/14 19:21:28

硬核对决:TruthfulRAG如何运用知识图谱化解RAG知识冲突?

&#x1f4cc; RAG系统的困境 问题的根源&#xff1a;知识冲突 RAG&#xff08;检索增强生成&#xff09;系统中&#xff1a;当外部检索到的知识与模型内部参数化知识不一致时&#xff0c;LLM往往会陷入不知所措。 知识冲突示意图 Figure 1: 知识冲突示意图。现有方法在toke…

作者头像 李华
网站建设 2026/1/11 23:16:58

SpringBoot代码集

一、获取Spring容器对象1.1 实现BeanFactoryAware接口实现BeanFactoryAware接口&#xff0c;然后重写setBeanFactory方法&#xff0c;就能从该方法中获取到Spring容器对象。Service public class PersonService implements BeanFactoryAware {private BeanFactory beanFactory;…

作者头像 李华
网站建设 2026/1/14 11:11:36

2025最新!8个AI论文平台测评:本科生写论文还能这么快?

2025最新&#xff01;8个AI论文平台测评&#xff1a;本科生写论文还能这么快&#xff1f; 2025年AI论文平台测评&#xff1a;为何需要这份榜单&#xff1f; 随着人工智能技术的不断进步&#xff0c;越来越多的本科生开始借助AI工具提升论文写作效率。然而&#xff0c;面对市场上…

作者头像 李华
网站建设 2026/1/15 11:15:32

PyTorch Federated Learning项目环境搭建:Miniconda-Python3.9实测

PyTorch Federated Learning项目环境搭建&#xff1a;Miniconda-Python3.9实测 在联邦学习研究中&#xff0c;最让人头疼的往往不是模型收敛问题&#xff0c;而是“在我机器上明明能跑”的环境灾难。你有没有经历过这样的场景&#xff1a;论文复现时突然报错 ImportError: can…

作者头像 李华