合法UTF-8需按字节模式验证:单字节0xxxxxxx、双字节110xxxxx 10xxxxxx、三字节1110xxxx 10xxxxxx 10xxxxxx、四字节11110xxx 10xxxxxx 10xxxxxx 10xxxxxx;非法字节(如孤立10xxxxxx、11111xxx等)应跳过,推荐用状态机逐字节过滤。

如何判断一个字节序列是否为合法UTF-8
UTF-8不是逐字节可验证的,必须按字节模式组合识别:单字节0xxxxxxx、双字节110xxxxx 10xxxxxx、三字节1110xxxx 10xxxxxx 10xxxxxx、四字节11110xxx 10xxxxxx 10xxxxxx 10xxxxxx。任意位置出现10xxxxxx但前面没匹配到对应起始字节(如110xxxxx),或出现11111xxx/10xxxxxx开头的孤立字节,都算非法。
别用std::string::erase()边遍历边删——迭代器失效风险高;也别依赖std::codecvt_utf8(C++17已弃用且实现常有bug)。
用状态机逐字节扫描并过滤非法字节
写一个轻量状态机,输入字节流,输出合法UTF-8子序列。状态包括:start(等待新字符)、expect1(期待1个后续字节)、expect2(期待2个)、expect3(期待3个)。遇到非法字节直接跳过,不计入输出。
- 遇到
0xxxxxxx→ 输出该字节,状态回start - 遇到
110xxxxx→ 记录需再读1字节,状态变expect1 - 遇到
1110xxxx→ 状态变expect2 - 遇到
11110xxx→ 状态变expect3 - 遇到
10xxxxxx→ 仅当处于expect*状态时才接受并推进计数;否则丢弃 - 遇到
11111xxx、11110000~11110100之外的四字节首字节、超范围码点(如U+D800~U+DFFF)也应丢弃
示例核心逻辑:
0xFF
- 用
std::regex匹配"\x80-\xFF"然后删——这会干掉所有多字节字符的首字节 - 调用
iconv(..., "UTF-8", "ISO-8859-1", ...)再转回——可能静默替换或截断,不可控 - 依赖第三方库如
utf8cpp的is_valid()但只检查整体字符串——它返回false却不告诉你哪部分错,无法做局部清理
实际使用时要注意BOM和嵌入NUL
UTF-8 BOM()是合法字节序列,不应删除;但若原始字符串含C风格

