Unicode 字符串转 UTF-8 编码算法剖析
📊 Unicode 字符串转 UTF-8 编码算法剖析
——从 C# char
到 C++ wchar_t
的编码转换原理
引用:UTF-8 编解码可视化分析
🔍 1. 算法功能概述
该函数将 Unicode 字符串(C# string
)转换为 UTF-8 编码的字节数组,同时输出有效字节数 count
。
核心流程:
- 计算 UTF-8 编码所需字节数
- 分配缓冲区(额外预留 3 字节)
- 逐字符编码为 UTF-8 字节序列
🧠 2. 算法原理与 UTF-8 编码规则
UTF-8 是变长编码,规则如下:
Unicode 码点范围 | UTF-8 字节序列结构 | 字节数 | 位模式图示 |
---|---|---|---|
U+0000 ~ U+007F | 0xxxxxxx | 1 | [0xxx xxxx] |
U+0080 ~ U+07FF | 110xxxxx 10xxxxxx | 2 | [110x xxxx] [10xx xxxx] |
U+0800 ~ U+FFFF | 1110xxxx 10xxxxxx 10xxxxxx | 3 | [1110 xxxx] [10xx xxxx] [10xx xxxx] |
💡 注:C#
char
为 16 位 Unicode,C++wchar_t
在 Windows 中与之等价(Linux 为 32 位)。
🔄 3. 算法流程详解
步骤 1:计算 UTF-8 字节数
int k = 0;
while (i < s.Length) {char ch = s[i++];if (ch < 0x80) k++; // ASCII 字符(1 字节)else if (ch < 0x800) k += 2; // 2 字节字符else if (ch < 0x10000) k += 3; // 3 字节字符(含 CJK 字符)
}
count = k; // 输出有效字节数
- 时间复杂度:O(n),需遍历整个字符串。
步骤 2:分配缓冲区
buf = new byte[count + 3]; // 额外分配 3 字节(安全填充)
- 为什么 +3?
预留空间防止溢出(UTF-8 单字符最多占 3 字节),确保写入安全。
步骤 3:编码转换(核心)
fixed (byte* p = buf) { // 固定内存地址(避免 GC 移动)while (i < s.Length) {char ch = s[i++];// 1 字节编码(ASCII)if (ch < 0x80) p[k++] = (byte)(ch & 0xFF); // 2 字节编码(拉丁字母扩展)else if (ch < 0x800) {p[k++] = (byte)(((ch >> 6) & 0x1F) | 0xC0); // 110xxxxxp[k++] = (byte)((ch & 0x3F) | 0x80); // 10xxxxxx}// 3 字节编码(中/日/韩文字符)else if (ch < 0x10000) {p[k++] = (byte)(((ch >> 12) & 0x0F) | 0xE0); // 1110xxxxp[k++] = (byte)(((ch >> 6) & 0x3F) | 0x80); // 10xxxxxxp[k++] = (byte)((ch & 0x3F) | 0x80); // 10xxxxxx}}
}
- 位操作解析:
⚠️ 4. 算法局限性
- 不支持代理对(Surrogate Pairs):
无法处理U+10000
以上的字符(如 emoji 𠮷),需补充 4 字节编码逻辑。 - 缓冲区浪费:多分配 3 字节(可通过精确计算优化)。
📝 5. 完整代码(含逐行注释)
public static byte[] GetUTF8StringBuffer(string s, out int count)
{count = 0; // 初始化输出参数if (s == null){throw new ArgumentNullException("s"); // 空字符串检查}byte[] buf = null; // 存储结果的字节数组int k = 0; // 临时计数器(字节数/写入位置)int i = 0; // 字符串遍历索引// === 步骤 1:计算 UTF-8 编码所需字节数 ===while (i < s.Length){char ch = s[i++]; // 逐个读取字符if (ch < 0x80) // ASCII 字符(0x00 ~ 0x7F){k++; // 占 1 字节}else if (ch < 0x800) // 0x80 ~ 0x7FF(拉丁字母扩展){k += 2; // 占 2 字节}else if (ch < 0x10000) // 0x800 ~ 0xFFFF(CJK 字符等){k += 3; // 占 3 字节}}// === 步骤 2:分配缓冲区(额外 +3 字节) ===buf = new byte[(count = k) + 3]; // count 赋值为 k,总长度 = k+3// === 步骤 3:编码转换(不安全代码块) ===unsafe // 启用不安全代码{fixed (byte* p = buf) // 固定缓冲区内存地址{i = 0; // 重置字符串索引k = 0; // 重置字节数组索引while (i < s.Length){char ch = s[i++]; // 读取下一个字符// --- 1 字节编码 ---if (ch < 0x80){p[k++] = (byte)(ch & 0xFF); // 直接存储低 8 位}// --- 2 字节编码 ---else if (ch < 0x800){// 构造首字节:110xxxxx (0xC0 | 高 5 位)p[k++] = (byte)(((ch >> 6) & 0x1F) | 0xC0);// 构造次字节:10xxxxxx (0x80 | 低 6 位)p[k++] = (byte)((ch & 0x3F) | 0x80);}// --- 3 字节编码 ---else if (ch < 0x10000){// 构造首字节:1110xxxx (0xE0 | 高 4 位)p[k++] = (byte)(((ch >> 12) & 0x0F) | 0xE0);// 构造次字节:10xxxxxx (0x80 | 中 6 位)p[k++] = (byte)(((ch >> 6) & 0x3F) | 0x80);// 构造尾字节:10xxxxxx (0x80 | 低 6 位)p[k++] = (byte)((ch & 0x3F) | 0x80);}}}}return buf; // 返回 UTF-8 字节数组
}
🎯 6. 总结
- 核心价值:高效实现 Unicode 到 UTF-8 的转换,适用于网络传输或跨语言交互。
- 优化方向:
- 支持 4 字节编码(代理对)
- 移除多余缓冲区分配
- 使用
Span<byte>
提升性能