從網絡/文件讀取的數據可能包含不合法的 UTF-8 字節序列,直接輸出會導致亂碼或解析錯誤。需要一個函數將非法字節替換為安全字符(如 '#'),保證輸出為合法 UTF-8。
0xxxxxxx (0x00-0x7F)110xxxxx 10xxxxxx (0xC2-0xDF 開頭)1110xxxx 10xxxxxx 10xxxxxx (0xE0-0xEF 開頭)11110xxx 10xxxxxx 10xxxxxx 10xxxxxx (0xF0-0xF4 開頭)10xxxxxx (0x80-0xBF)遍歷字節流,維護「預期續文字節數」expect:
expect == 0,判斷當前字節類型,設置 expect 為對應續字節數(0~3)。expect > 0,檢查是否為合法續字節(0x80-0xBF),合法則 expect--,否則標記從序列起始到當前為非法。'#'。\t\n\r 及可見字符 0x20-0x7E)。bool filter_utf8(unsigned char* data, size_t len) {
if (!data) return false;
size_t expect = 0, seq_start = 0;
for (size_t i = 0; i < len; ++i) {
unsigned char c = data[i];
if (expect == 0) {
if (c <= 0x7F) { // ASCII
if (!(c == '\t' || c == '\n' || c == '\r' || (c >= 0x20 && c <= 0x7E)))
data[i] = '#';
} else if ((c & 0xE0) == 0xC0) expect = 1; // 2-byte
else if ((c & 0xF0) == 0xE0) expect = 2; // 3-byte
else if ((c & 0xF8) == 0xF0) expect = 3; // 4-byte
else { data[i] = '#'; continue; } // 非法開頭
seq_start = i;
} else {
if ((c & 0xC0) == 0x80) { --expect; } // 合法續字節
else { // 非法續字節,整段標記
for (size_t j = seq_start; j <= i; ++j) data[j] = '#';
expect = 0;
--i; // 重新檢查當前字節作為新序列開頭
}
}
}
// 末尾截斷序列
if (expect > 0) for (size_t j = seq_start; j < len; ++j) data[j] = '#';
return true;
}
std::string s = "\xE4\xB8\xAD\xFF\xE6\x96\x87"; // "中" + 非法 + "文"
filter_utf8((unsigned char*)s.data(), s.size());
// s 變為 "中#文"
單遍歷狀態機即可高效完成 UTF-8 合法性檢查與清洗。關鍵在於正確處理「非法續字節導致整段失效」與「末尾截斷」兩類邊界情況。