PAYATHON 2026

VS Code 中 Markdown 文件内容变成问号后如何恢复?

排障赵师傅

结论

如果原字符在保存时已经被替换成普通问号 ?,切换编码也无法恢复。文件里保存的就是问号,原字符的字节信息已经丢失。

恢复内容还有两种可能:

  1. 文件只是显示乱码,原始字节仍然存在。这种情况可以用正确的编码重新打开或转换。
  2. 从 VS Code 撤销记录、本地历史、Git 提交、GitHub 历史、系统备份或云盘版本中找回旧副本。

查明情况之前,不要再对原文件执行 Save with Encoding。

切换编码为什么会造成数据丢失

VS Code 中的两个编码命令用途不同:

  • Reopen with Encoding:使用指定编码重新解释现有字节,通常不会立即改写文件。
  • Save with Encoding:把编辑器中的字符转换成指定编码,然后写回文件。

如果目标编码无法表示某些中文、阿拉伯文或其他字符,编码器可能会用 ? 代替它们。保存后,多个不同的字符都会变成同一个字节 0x3F,无法再判断每个问号原来是什么。

例如,原内容可能是:

中文 / العربية

错误保存后变成:

?? / ???????

这时再选择 UTF-8,只会把这些问号按 UTF-8 保存,原字符不会重新出现。

另一种常见结果是替换字符 �,也就是 Unicode 字符 U+FFFD。如果这个字符已经写入文件,通常说明原始字符信息在解码或保存过程中丢失了。

第一步:保留当前文件

先复制文件,后续恢复操作都在副本上进行:

cp "document.md" "document.md.damaged-backup"

Windows PowerShell 使用:

Copy-Item "document.md" "document.md.damaged-backup"

如果文件还在 VS Code 中打开,先尝试 Ctrl+Z,macOS 使用 Cmd+Z。即使文件已经保存,当前编辑器会话的撤销栈有时仍能回到保存前。暂时不要关闭窗口,关闭后可能无法继续使用撤销记录。

判断是显示乱码还是字符已被替换

先查看文件的原始字节:

xxd "document.md" | less

如果出问题的位置连续出现 3f,说明文件实际保存的是 ASCII 问号:

3f 3f 3f 3f

如果出现以下 UTF-8 字节序列,说明文件中保存的是 �:

ef bf bd

这两种情况都不能靠重新选择编码还原。

也可以用 Python 统计问号和替换字符的数量:

from pathlib import Path

data = Path("document.md").read_bytes()

print("0x3F question marks:", data.count(b"\x3f"))
print("UTF-8 replacement characters:", data.count(b"\xef\xbf\xbd"))

0x3F 也可能是原文正常使用的英文问号,需要结合它出现的位置和连续数量来判断。

如果十六进制数据中还有大量非 ASCII 字节,而 VS Code 只是显示乱码,原始内容可能仍在。此时应在副本上使用 Reopen with Encoding,逐一尝试文件可能采用的编码,如 UTF-8、GB18030、Big5 或 Windows-1256。尝试期间不要保存文件。

从 VS Code 本地历史恢复

在 VS Code 的资源管理器中选中文件,打开 Timeline(时间线)视图,查看是否有 Local History 条目。找到编码操作之前的版本后,先打开并核对内容,再恢复或另存为新文件。

能否找到历史版本,取决于当时的 VS Code 配置、文件类型,以及历史记录是否已被清理。

如果当前窗口还没有关闭,可以继续检查:

  1. 使用 Undo 回退到编码转换之前。
  2. 查看当前文件所在目录中是否有未保存的副本、临时文件或名称相近的备份。
  3. 如果 VS Code 曾异常退出,重新启动后检查是否恢复了未保存的编辑器内容。

临时文件和崩溃恢复数据并不可靠。一旦找到可用内容,应立即复制到单独的位置。

从 Git 恢复

如果文件曾提交到本地 Git 仓库,可以查看它的完整历史:

git log --all -- "path/to/document.md"

查看某次提交中的文件内容:

git show <commit>:path/to/document.md

确认内容无误后,将其输出到新文件,以免覆盖当前版本:

git show <commit>:path/to/document.md > document.recovered.md

还可以检查所有分支和引用:

git branch --all
git reflog

如果提交已经删除或分支被重写,可以查找尚未被 Git 清理的对象:

git fsck --full --no-reflogs --unreachable

找到疑似 blob 后查看内容:

git show <object-id>

git fsck 只能找到曾进入 Git 对象数据库、目前无法通过正常引用访问的对象。如果文件从未执行过 git add,也没有提交,通常无法通过 Git 找回。

从 GitHub 查找旧副本

如果文件曾上传到 GitHub,可以检查以下位置:

  • 仓库的提交历史和文件历史;
  • 其他分支、标签和发布附件;
  • 已删除分支对应的 Pull Request;
  • 仓库的 Fork;
  • 本机以前克隆过的仓库;
  • 浏览器下载目录、最近访问记录和本地缓存;
  • GitHub 账户中的其他仓库或 Gist。

如果仓库还在,但当前版本已经损坏,可以在文件页面打开 History,查找编码操作之前的提交。没有提交到 GitHub 的本地修改无法从 GitHub 恢复。

使用正确的编码转换

只有确认原始字节仍然存在,编码转换才有意义。例如,文件实际采用 GB18030,只是被当成其他编码显示,可以先生成一个 UTF-8 副本:

iconv -f GB18030 -t UTF-8 "document.md" > "document.utf8.md"

转换前必须保留原文件。如果 iconv 报告非法字节,不要使用忽略错误的参数直接覆盖源文件。跳过无法转换的字节可能造成更多数据丢失。

无法确定编码时,可以分别生成多个候选文件:

iconv -f GB18030 -t UTF-8 "document.md" > "candidate-gb18030.md"
iconv -f BIG5 -t UTF-8 "document.md" > "candidate-big5.md"

再人工检查哪个版本的内容正确。自动编码检测只能给出猜测,处理短文本或多语言文本时尤其容易出错,不能根据检测结果直接覆盖文件。

其他备份来源

如果 VS Code、Git 和 GitHub 中都找不到旧版本,还可以检查:

  • Windows 文件历史记录或”以前的版本”;
  • macOS Time Machine;
  • OneDrive、Dropbox、iCloud Drive 等同步服务的版本历史;
  • NAS 快照和公司终端备份;
  • 邮件、聊天附件、压缩包或其他设备中的副本;
  • 构建产物、已发布网页、搜索引擎缓存等只读副本。

有时可以从已发布的 HTML 或构建结果中复制回正文,但 Markdown 标记、注释和未发布内容可能已经缺失。

避免再次发生

多语言 Markdown 文件通常应统一保存为 UTF-8。可以在 VS Code 设置中指定默认编码:

{
  "files.encoding": "utf8",
  "files.autoGuessEncoding": false
}

如果经常处理来源不明的旧文件,可以启用自动猜测,但保存前仍要确认识别结果:

{
  "files.encoding": "utf8",
  "files.autoGuessEncoding": true
}

自动猜测并不总是准确。比较稳妥的操作顺序是先复制文件,再使用 Reopen with Encoding 检查显示。确认所有文字都正常后,另存为 UTF-8。重要文件还应及时提交到 Git,或使用带版本历史的备份服务。

备注:内容仅供参考。