为什么合法 UTF-8 会被误判为二进制?

用 Doraemon 中真实的 agenthub/SKILL.md 理解“字节切片”和“字符边界”的区别。

1. 先建立一个关键模型

文件存储的是字节,界面显示的是字符。UTF-8 负责把两者互相转换,但一个字符不一定只占一个字节。

汉字“答”: E7AD94 共 3 字节

如果程序只拿到 E7 AD,它看到的不是一个错误字符,而是一个尚未结束的字符

2. 代码做了什么

const sample = buffer.subarray(0, 4096);
new TextDecoder("utf-8", { fatal: true }).decode(sample);

subarray(0, 4096) 按第 4096 个字节切开。它不知道那里是不是字符边界。

… E5 9B 9E E7 AD | 94 ← “答”被切开

3. 为什么 decoder 会报错

fatal: true 表示遇到无效输入直接抛出异常,而不是用替代字符继续。并且这里没有设置流式解码,所以 decoder 会认为 4096 字节已经是全部输入;末尾缺少 94,自然判定为无效。

完整的 7004 字节文件可以严格解码;4096 字节样本不能严格解码。错误发生在采样方式,不在原文件。

4. 异常如何一路变成 UI 提示

完整 Markdown
4096 字节处截断
严格解码抛错
is_binary=1
禁止在线预览

前端没有错误地分析 Markdown。它只是相信了导入阶段存进数据库的 is_binary 字段。

5. 一句话根因

程序把“被截断的 UTF-8 样本无法独立解码”错误地等同于“完整文件是二进制”。

快速小测

如果把采样长度从 4096 改成 8192,这个问题是否被真正解决?

没有。它可能避开当前文件的边界,但另一个文件仍可能恰好在 8192 字节处切断多字节字符。正确修复必须处理字符边界或使用流式解码语义。

资料

有任何一步不清楚,直接把那一步的问题发给我,我们继续拆解。