🌟 UTF-8 编解码可视化分析
引用:
- openppp2/ppp/text/Encoding.cpp
- C# UTF-8字符集长度获取及判断二进制块是否UTF-8编码字符集算法的实现
- 严格的C风格字符串 Unicode To UTF-8 的实现(C#、JavaScript)
- nsjsdotnet/NSJSString.cs
📐 1. UTF-8 字节对齐原理 (GetUtf8Alignment)

📊 UTF-8 字符编码结构
字节数 | 首字节范围 | 编码模板 |
---|---|---|
1 |
0x00-0x7F | 0xxxxxxx |
2 |
0xC0-0xDF | 110xxxxx 10xxxxxx |
3 |
0xE0-0xEF | 1110xxxx 10xxxxxx 10xxxxxx |
4 |
0xF0-0xF7 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
🔢 2. 字符计数流程 (GetUtf8BufferCount)
否 是 1 2 3 4 是 否 起始指针 是否可读内存? 返回 0 获取首字节 计算对齐字节数 对齐数 读取单字节 读取双字节 读取三字节 读取四字节 字符=0? 计算长度 移动指针
✅ 3. UTF-8 验证流程 (IsUTF8Buffer)
是 否 是 是 否 否 否 是 是 否 结束 是 否 初始化计数器=1 遍历字节 计数器=1? 字节>=0x80? 计算连续1个数 个数=1 OR >4? 返回 false 设置计数器 后两位=10? 计数器-1 计数器>0? 计数器重置为1 计数器=1? 返回 true
⚙️ C++ 实现代码 (带详细注释)
cpp
#pragma execution_character_set("utf-8") // 指示编译器使用UTF-8编码执行字符集
#include <Windows.h> // 包含Windows API头文件
#include <string> // 包含C++字符串库
#include <cstring> // 包含C风格字符串操作函数
#include <iostream> // 包含输入输出流对象
#include <codecvt> // 包含字符编码转换工具
// 定义位掩码常量用于UTF-8编码检测
constexpr BYTE kFirstBitMask = 0x80; // 10000000b - 检测首字节最高位
constexpr BYTE kSecondBitMask = 0x40; // 01000000b - 检测首字节次高位
constexpr BYTE kThirdBitMask = 0x20; // 00100000b - 检测首字节第三位
constexpr BYTE kFourthBitMask = 0x10; // 00010000b - 检测首字节第四位
// 计算UTF-8字符的字节长度
int GetUtf8Alignment(BYTE character) {
int alignment = 1; // 默认ASCII字符占1字节
// 检查首字节最高位是否为1(表示多字节字符)
if ((character & kFirstBitMask) > 0) {
// 检查首字节第三位是否为1(可能为3字节字符)
if ((character & kThirdBitMask) > 0) {
// 检查首字节第四位是否为1(可能为4字节字符)
if ((character & kFourthBitMask) > 0) {
alignment = 4; // 确定为4字节字符
}
else {
alignment = 3; // 确定为3字节字符
}
}
else {
alignment = 2; // 确定为2字节字符
}
}
return alignment; // 返回字符占用的字节数
}
// 计算UTF-8字符串的字节长度(包含空终止符)
int GetUtf8BufferCount(BYTE* s) {
if (NULL == s) { // 空指针检查
return 0;
}
BYTE* i = s; // 创建指针用于遍历字符串
// 遍历直到遇到空终止符
while (*i != '\x0') {
int alignment = GetUtf8Alignment(*i); // 获取当前字符的字节长度
int character = 0; // 存储字符值(实际未使用)
// 根据字符长度处理不同情况
if (alignment == 1) {
character = *i++; // 单字节字符直接读取
}
else if (alignment == 2) {
character = *(short*)i; // 双字节字符(存在字节序问题)
i += 2; // 指针前进2字节
}
else if (alignment == 3) {
// 拼接三字节字符(小端序处理)
character = (*i++ | *i++ << 8 | *i++ << 16);
}
else if (alignment == 4) {
character = *(int*)i; // 四字节字符(存在字节序问题)
i += 4; // 指针前进4字节
}
else {
return 0; // 无效字符长度处理
}
}
int len = (int)(i - s); // 计算从开始到结束的字节数(含空终止符)
return len < 0 ? 0 : len; // 处理长度小于0的情况
}
// 验证缓冲区是否为有效的UTF-8编码
bool IsUTF8Buffer(BYTE* buffer, int count) {
// 空指针或无效长度检查
if (NULL == buffer || count <= 0) {
return false;
}
int counter = 1; // 当前字符的剩余字节数(初始为1)
BYTE key = 0; // 当前处理的字节
for (int i = 0; i < count; i++) {
key = buffer[i]; // 获取当前字节
if (counter == 1) { // 处理新字符的首字节
if (key >= 0x80) { // 检查是否多字节字符
// 计算后续字节数(通过左移检测连续高位1)
while (((key <<= 1) & 0x80) != 0) {
counter++; // 每多一个高位1则增加计数
}
// 验证字节数在有效范围(2-4字节)
if (counter == 1 || counter > 4) {
return false; // 无效的首字节格式
}
}
}
else { // 处理连续字节
// 检查是否为有效的连续字节(格式应为10xxxxxx)
if ((key & 0xC0) != 0x80) {
return false; // 无效的连续字节格式
}
counter--; // 减少剩余字节计数
}
}
return !(counter > 1); // 检查是否完整读取最后一个字符
}
// 将宽字符串转换为UTF-8编码的字符串
std::string wstring_to_utf8(const std::wstring& s) noexcept {
// 创建宽字符到UTF-8的转换器
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
// 执行转换并返回结果
return converter.to_bytes(s);
}
// 程序主入口
int main(int argc, const char** argv) noexcept {
const char str[] = "中国"; // UTF-8编码的中文字符串
// 验证字符串是否为有效UTF-8编码
std::cout << "Is utf8 string: "
<< IsUTF8Buffer((BYTE*)str, sizeof(str))
<< std::endl;
// 计算字符串的字节长度(包含空终止符)
std::cout << "Utf8 string length: "
<< GetUtf8BufferCount((BYTE*)str)
<< std::endl;
return 0; // 程序正常退出
}
⚡ C# 实现说明 (优化版)
csharp
// 导入kernel32.dll中的IsBadReadPtr函数,用于检测内存指针是否可读
[DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall)]
private extern static bool IsBadReadPtr(void* lp, uint ucb);
// 定义UTF-8编码检测用的位掩码常量
private const byte kFirstBitMask = 0x80; // 10000000b - 检测首字节最高位
private const byte kSecondBitMask = 0x40; // 01000000b - 检测首字节次高位
private const byte kThirdBitMask = 0x20; // 00100000b - 检测首字节第三位
private const byte kFourthBitMask = 0x10; // 00010000b - 检测首字节第四位
private const byte kFifthBitMask = 0x08; // 00001000b - 检测首字节第五位
// 计算UTF-8字符的字节长度
public static int GetUtf8Alignment(byte character)
{
int alignment = 1; // 默认ASCII字符占1字节
// 检查首字节最高位是否为1(表示多字节字符)
if ((character & kFirstBitMask) > 0)
{
// 检查首字节第三位是否为1(可能为3字节字符)
if ((character & kThirdBitMask) > 0)
{
// 检查首字节第四位是否为1(可能为4字节字符)
if ((character & kFourthBitMask) > 0)
{
alignment = 4; // 确定为4字节字符
}
else
{
alignment = 3; // 确定为3字节字符
}
}
else
{
alignment = 2; // 确定为2字节字符
}
}
return alignment; // 返回字符占用的字节数
}
// 计算UTF-8字符串的字节长度(不安全代码)
public static unsafe int GetUtf8BufferCount(byte* s)
{
if (s == null) // 空指针检查
{
return 0;
}
byte* i = s; // 创建指针用于遍历字符串
// 循环直到遇到无效内存或空字符
while (!IsBadReadPtr(i, 1)) // 检查当前指针是否可读
{
int alignment = GetUtf8Alignment(*i); // 获取当前字符的字节长度
int character = 0; // 存储字符值(实际未使用)
// 根据字符长度处理不同情况
if (alignment == 1)
{
character = *i++; // 单字节字符直接读取
}
if (alignment == 2) // 注意:此处应为else if,否则逻辑错误
{
character = *(short*)i; // 双字节字符(存在字节序问题)
i += 2; // 指针前进2字节
}
else if (alignment == 3)
{
// 拼接三字节字符(小端序处理)
character = (*i++ | *i++ << 8 | *i++ << 16);
}
else if (alignment == 4)
{
character = *(int*)i; // 四字节字符(存在字节序问题)
i += 4; // 指针前进4字节
}
// 检测到空终止符时结束遍历
if (character == 0)
{
// 计算从开始到结束的字节数(含空终止符)
int len = unchecked((int)(i - (s + 1)));
return len < 0 ? 0 : len; // 处理长度小于0的情况
}
}
return 0; // 遇到无效内存时返回0
}
// UTF-8验证方法(字节数组重载)
public static unsafe bool IsUTF8Buffer(byte[] buffer)
{
if (buffer == null) // 空数组检查
{
return false;
}
// 固定字节数组内存地址
fixed(byte * pinned = buffer)
{
// 调用指针版本验证方法
return IsUTF8Buffer(pinned, buffer.Length);
}
}
// UTF-8验证方法(指针版本)
public static unsafe bool IsUTF8Buffer(byte* buffer, int count)
{
// 空指针或无效长度检查
if (buffer == null || count <= 0)
{
return false;
}
int counter = 1; // 当前字符的剩余字节数(初始为1)
byte key = 0; // 当前处理的字节
for (int i = 0; i < count; i++)
{
key = buffer[i]; // 获取当前字节
if (counter == 1) // 处理新字符的首字节
{
if (key >= 0x80) // 检查是否多字节字符
{
// 计算后续字节数(通过左移检测连续高位1)
while (((key <<= 1) & 0x80) != 0)
{
counter++; // 每多一个高位1则增加计数
}
// 验证字节数在有效范围(2-4字节)
if (counter == 1 || counter > 4)
{
return false; // 无效的首字节格式
}
}
}
else // 处理连续字节
{
// 检查是否为有效的连续字节(格式应为10xxxxxx)
if ((key & 0xC0) != 0x80)
{
return false; // 无效的连续字节格式
}
counter--; // 减少剩余字节计数
}
}
return !(counter > 1); // 检查是否完整读取最后一个字符
}
📌 关键原理总结
- 变长编码:UTF-8 使用 1-4 字节动态编码 Unicode
- 首字节标识:高位 1 的数量表示总字节数
- 续字节格式 :后续字节必须以
10
开头 - 内存安全 :通过
IsBadReadPtr
防止非法内存访问 - 边界校验:严格验证多字节序列完整性
流程图使用 Mermaid 语法绘制,可在支持 Mermaid 的环境(如Obsidian、VSCode插件)中查看动态效果。