首字节决定长度
UTF-8 把长度信息编码进了首字节:首字节以 0 开头说明是单字节 ASCII;以 110 开头说明还有 1 个续字节;1110 开头还有 2 个;11110 开头还有 3 个。续字节统一以 10 开头,因此看到 10 开头的字节就说明它属于前一个字符。这套规则让解码器无需从头扫描就能定位边界,也让校验器能识别非法序列(孤立续字节、过长编码、超出 U+10FFFF 的取值)。
基本信息
| 判定依据 | 只看首字节的高位模式,不必读完整序列 |
|---|---|
| 首字节合法区间 | 0x00-0x7F、0xC2-0xDF、0xE0-0xEF、0xF0-0xF4 |
| 续字节区间 | 0x80-0xBF(一律以 10 开头) |
| 非法首字节 | 0x80-0xBF 单独出现、0xC0-0xC1、0xF5-0xFF |
| 长度上限 | 4 字节,对应码位上限 U+10FFFF |
| 衍生校验 | 读取长度后必须验证后续字节都在续字节区间内 |
典型使用场景
- 流式解析:从字节流中判断当前字符占几个字节,避免半包时误判
- 输入校验:快速拒绝非法 UTF-8 输入(畸形序列常被用于绕过安全过滤)
- 固定长度字段裁剪:按客户端的字节上限裁剪前先回退到合法字符边界
- 协议实现:自写解析器或做协议兼容测试时验证编码合法性
- 性能优化:按字节扫描合法 ASCII 快速路径,遇到高位字节才进入多字节分支
位模式观察示例
位模式观察示例
python -c "b='中'.encode('utf-8'); print([hex(x) for x in b])" # 三字节:E4 B8 AD
python -c "print([bin(x) for x in '中'.encode('utf-8')])" # 11100100 / 10111000 / 10101101
python -c "print(len('𠀀'.encode('utf-8')))" # 增补平面字符为 4 字节
python - <<'EOF'
# 按首字节判断长度(简化演示,不含完整合法性校验)
for c in 'A中':
b = c.encode('utf-8')[0]
n = 1 if b < 0x80 else 2 if b < 0xE0 else 3 if b < 0xF0 else 4
print(c, hex(b), n)
EOF
iconv -f UTF-8 -t UTF-16LE file.txt > /dev/null && echo sequence-ok # 非法序列会失败首字节区间判定表
| 首字节十六进制 | 字节数 | 说明 |
|---|---|---|
| 0x00-0x7F | 1 | ASCII,最高位为 0 |
| 0x80-0xBF | 非法 | 续字节,不能出现在字符首位 |
| 0xC0-0xDF | 2 | 其中 0xC0-0xC1 会产生过长编码,非法 |
| 0xE0-0xEF | 3 | 覆盖 U+0800-U+FFFF,含常用汉字 |
| 0xF0-0xF4 | 4 | 覆盖增补平面,0xF5-0xFF 超出合法范围 |
| 0xF5-0xFF | 非法 | 超出 U+10FFFF,任何序列中都非法 |
过长编码(overlong)必须拒绝:用多余的字节写出一个本可用更少字节表示的码位属于非法序列。历史上攻击者利用它与过滤器的解码差异绕过安全校验,因此自己做校验时不要只看「字节数是否正确」,还要检查码位是否落在该长度对应的最小范围内。
不同语言对非法字节的默认处理不同:有的抛异常(严格模式),有的替换为 U+FFFD,有的原样保留字节。跨系统传递数据时,替换策略会让错误被静默吞掉,调试阶段建议先开启严格校验,确认数据干净后再决定线上策略。
长度不等于显示宽度:一个 3 字节汉字在终端通常占两个字符宽,一个 4 字节 emoji 可能占两格或按字形簇整体显示。做表格对齐与截断显示时要按显示宽度计算,而不是按字节数或码位数。