CodeGym /课程 /C# SELF /损坏的文件和编码

损坏的文件和编码

C# SELF
第 38 级 , 课程 3
可用

1. 介绍

是时候聊聊那些恼人的情况了——文件操作突然变成了和(有时候还挺神秘的)错误的亲密接触。如果你曾经碰到过像 System.Text.DecoderFallbackException 的错误,那你已经对这个话题不陌生了!

本讲我们会看:

  • .NET 里常见的编码相关错误有哪些;
  • 损坏或不正确的文件会怎样表现;
  • 拦截并处理这类错误的实战例子;
  • 处理别人给你的文件时(或是从老旧磁盘里翻出来的“宝贝”)需要注意的点。

总之,如果 ASCII 太简单,而 Unicode 太“聪明”,有时候会碰到没人能读的文件。这时候就会抛出异常。

为啥会这样?

当你用 StreamReader 打开文件并指定编码(或者用默认编码)时,.NET 假设文件里的所有字节都能被这套编码正确转换成字符。但如果文件里有些字节在当前编码下根本没有对应字符,就会发生解码错误。

2. 使用错误编码读取文件时的异常

最常见的异常 — DecoderFallbackException

当字节序列无法映射到当前期望的编码字符时,.NET 会抛出这个异常。

一个简单例子,让问题更明显:


// 假设旧文件是 Windows-1251(西里尔字母)
string win1251File = "win1251_test.txt";
File.WriteAllText(win1251File, "你好,世界!", Encoding.GetEncoding("windows-1251"));

try
{
    // 尝试把这个文件当成 UTF-8 来读
    using var reader = new StreamReader(win1251File, Encoding.UTF8);
    string content = reader.ReadToEnd();
    Console.WriteLine(content); // …会打印乱码(或者抛出异常)
}
catch (DecoderFallbackException ex)
{
    Console.WriteLine("解码错误: " + ex.Message);
}

大多数情况下,把一个以 Windows-1251 保存的文件当成 UTF-8 读,会得到一堆“乱码”。默认情况下 StreamReader 不会抛异常,而是在无法理解的字节位置放上替代字符 "�"。但如果你显式用了会严格抛错的 DecoderExceptionFallback,或者流里出现特别“难以处理”的字节,就会抛出 DecoderFallbackException

DecoderFallbackException 详情

  • 什么时候会发生:尝试把一组字节按当前编码转换成字符,但该字节序列无法被映射时。
  • 怎么处理:用正确的编码去读文件!如果不知道文件是什么编码,试着猜(有时可以靠 BOM 或文件名),或者问下文件的来源。

3. 有明显损坏的文件示例

现在把情况加点难度。假设文件被损坏了:字节序列里出现了断裂的、半个字符的字节片段。常见原因是写入时被中断、网络传输错误、错误的转换,甚至是真人用剪刀……字面意思上把文件“剪断”了。

创建一个“坏掉”的文件


// 把一段合法字符串以 UTF-8 编成字节
byte[] valid = Encoding.UTF8.GetBytes("你好,世界!");
// 现在制造一个不完整的字节数组(把一个字符的一部分截掉)
byte[] corrupted = new byte[valid.Length - 1];
Array.Copy(valid, corrupted, valid.Length - 1); // 把最后一个字节截掉
        
// 保存文件
File.WriteAllBytes("corrupted.txt", corrupted);

try
{
    using var reader = new StreamReader("corrupted.txt", Encoding.UTF8);
    string s = reader.ReadToEnd();
    Console.WriteLine("读到的文本: " + s);
}
catch (DecoderFallbackException ex)
{
    Console.WriteLine("文件损坏! " + ex.Message);
}

结果: .NET 无法正确拼出最后一个字符。默认会把它替换成特殊替代字符 "�"(或 "?"),或者如果编码被配置为抛异常,就会抛出 DecoderFallbackException

4. Fallback 策略:能不能避免异常?

有时候遇到“无法识别”的字符,你不想程序崩溃,而是想把它换成“?” 或别的东西。为此,.NET 提供了所谓的 fallback 策略。

示例:用替代字符代替抛异常


// 一个对 UTF-8 来说不完整的字节序列
byte[] data = { 0xD0, 0x9F, 0xD1, 0x80, 0xD0, 0xB8, 0xD0, 0xB2, 0xD0, 0xB5, 0xD1, 0x82, 0xD1 }; // 最后一个字节被截断
File.WriteAllBytes("broken_utf8.txt", data);

// Fallback 策略:遇到问题就用问号替代
var encodingWithFallback = Encoding.GetEncoding(
    "UTF-8",
    new EncoderReplacementFallback("?"),
    new DecoderReplacementFallback("?")
);

using var reader = new StreamReader("broken_utf8.txt", encodingWithFallback);
string s = reader.ReadToEnd();
Console.WriteLine("文本(错误替换): " + s);

效果: 文件会被读取,无法识别的字符会被替换成 "?"。这样可以避免程序崩溃,但得到的文本并不完全“原汁原味”。

5. 关于 BOM 和不兼容问题

提醒一下,BOM(Byte Order Mark)是放在文件开头的一段字节,用来告诉“嗨,我是这种编码!”。

BOM 什么时候会惹麻烦

  • 如果文件有 BOM,而应用不能正确处理它,第一字符可能会很奇怪(比如 "" 或不可见字符)。
  • 有时候没有 BOM 会导致编码判断错误。

和 BOM 有关的异常

通常 C# 在读取时能处理 BOM,但如果指定了错误的编码或手动把 BOM 删掉了,你可能会遇到:

  • 开头出现意外字符(比如 "�");
  • 如果编码设置成遇到这类情况抛错,BOM 被当成非法字节序列时会抛异常。

实用建议:如果编码类型对你很重要,读写文件时尽量显式指定编码。

6. 其他有趣的异常和场景

写入时用错了编码

当你尝试把包含某些字符的字符串写入到不支持这些字符的编码时会出问题。比如,在 Encoding.ASCII 下保存包含表情符号 “😊” 的字符串:


try
{
    using var writer = new StreamWriter("ascii.txt", false, Encoding.ASCII);
    writer.WriteLine("这是测试 😊");
}
catch (EncoderFallbackException ex)
{
    Console.WriteLine("编码错误: " + ex.Message);
}

结果: 要么会抛出 EncoderFallbackException,要么不支持的字符会被替换成 "?"——取决于你选的 fallback 策略。

编码转换时的数据丢失问题

把文件从一种编码转到另一种编码时,如果目标编码不包含源编码里的所有字符,可能会丢数据(比如从 UTF-8 转成 Windows-1251,但文件里有日文)。

磁盘、网络或“手工编辑”导致的文件损坏

如果文件里混进了随机或损坏的字节(比如磁盘崩溃后,或者用文本编辑器打开二进制文件乱改),尝试读取通常会抛解码异常。

7. 实战:如何拦截并处理错误?

因为错误可以在文件处理的不同阶段出现,建议:

  • 使用 try-catch 块去捕获异常——主要是 DecoderFallbackExceptionEncoderFallbackException
  • 不要怕告诉用户真相:如果文件损坏或编码不对,告诉用户比展示一堆奇怪文本要好;
  • 尽量自动化检测编码(比如根据 BOM 或用类似 Ude 这种库),但在失败时总是给用户选择编码的机会。

典型代码结构:


try
{
    using var reader = new StreamReader("file.txt", Encoding.GetEncoding("windows-1251"));
    string s = reader.ReadToEnd();
    Console.WriteLine(s);
}
catch (DecoderFallbackException ex)
{
    Console.WriteLine($"无法读取文件: {ex.Message}");
    // 可以提示用户尝试其他编码
}
catch (IOException ex)
{
    Console.WriteLine($"输入/输出错误: {ex.Message}");
}

8. 关于几个“常见的坑”

UTF-8 文件当成 Windows-1251 读:最好的情况看到乱码,最坏的情况抛出异常(如果编码被配置为抛错)。

ASCII 下写入含俄语的文件:非英文字母的内容会被替换成 "?",或引发 EncoderFallbackException

把无 BOM 的文件当成 UTF-8 读,但它其实是 UTF-16你会读到乱码,甚至读不出东西。

来自不可信来源的无明确编码的文件:时刻保持警惕:即便文件能打开“不报错”,也不代表内容是正确的。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION