CodeGym /课程 /C# SELF /文件编码转换

文件编码转换

C# SELF
第 37 级 , 课程 4
可用

1. 介绍

先说最重要的问题 — 为啥要折腾编码?看起来一个文件、一个编码,不就完事了。但实际情况比这复杂得多。你会很快发现文件可能来自任何地方,编码完全出乎意料。而你的应用当然期望的是另一种编码。

有时候你需要对接某个 API 或系统,它严格要求:只能用某种编码。也可能你翻到一个十年前的老文件,用新编辑器打开却看到乱码。或者你在保存 CSV 报表,希望确保同事们都能正常打开 — 无论是 Windows、Mac 还是 Excel。

总之,编码转换不是稀有或小众的事。相反,这是非常常见的问题:导出数据库、处理归档、对接财务系统或自动化脚本时都会遇到。越早搞清楚,后面惊喜越少。

从旧编码到新编码

要转换文件编码,需要两步:

  1. 读取 源文件,使用正确的源编码,得到字符串(或字符)。
  2. 写入 字符串到新文件,明确指定目标编码。

类比:把一本书从法语翻成俄语。先得会看法语(读出文本),然后把意思用俄语表达出来(写成俄语)。

在 C#(和 .NET)里,通过在 StreamReader(读)和 StreamWriter(写)的构造函数里传入相应的 Encoding 对象来实现。

编码概览与 Encoding

所有“魔法”字符转换都由 System.Text.Encoding 类完成。

下面表格列出常用编码以及在 C# 中如何获取它们:

编码 说明 C# 常量
UTF-8 通用,默认无 BOM
Encoding.UTF8
UTF-8 带 BOM 同上,但带 BOM 签名
new UTF8Encoding(true)
UTF-16 (LE/BE) “宽字符”,little-endian Encoding.Unicode (LE),
Encoding.BigEndianUnicode (BE)
ASCII 7 位经典编码
Encoding.ASCII
Windows-1251 常用于西里尔字母(俄语)
Encoding.GetEncoding(1251)
ISO-8859-1 拉丁字母(欧洲)
Encoding.GetEncoding("ISO-8859-1")

注意: 对于旧编码需要使用 Encoding.GetEncoding,有时用编号(比如 1251),有时用字符串(比如 "windows-1251")。

2. 转码:C# 中的逐步策略

来看看如何在实践中实现转码。

算法

  1. StreamReader 打开源文件,明确指定源编码。
  2. 读取文本(全部或逐行 — 取决于文件大小)。
  3. StreamWriter 打开目标文件,指定想要的目标编码。
  4. 把文本写入目标文件。
  5. 别忘了关闭流 — 用 using 来保证安全!

示例:从 Windows-1251 转到 UTF-8

假设有一个源文件 "input-1251.txt",它是 Windows-1251 编码,我们想要得到一个“纯净”的 UTF-8(无 BOM)的 "output-utf8.txt"

// 指定带原始编码的源
using var reader = new StreamReader("input-1251.txt", Encoding.GetEncoding(1251));
using var writer = new StreamWriter("output-utf8.txt", false, Encoding.UTF8);

string line;
// 按行读取 — 对大文件更友好
while ((line = reader.ReadLine()) != null)
{
    writer.WriteLine(line);
}

Console.WriteLine("文件已成功从 Windows-1251 转码为 UTF-8!");

这段代码保证每一行都能正确地从一种编码转换到另一种编码。

示意图大概是这样:


┌───────────────────────────────┐     ┌───────────────────┐     ┌───────────────────────────────┐
│ 文件 "input-1251.txt" (1251)   │ --> │  StreamReader     │ --> │  内存中的 string 行           │
└───────────────────────────────┘     │ (Encoding 1251)   │     └───────────────────────────────┘
                                      └───────────────────┘
                                              │
                                              ▼
                                  ┌────────────────────────┐
                                  │   StreamWriter         │
                                  │   (Encoding UTF-8)     │
                                  └───────────┬────────────┘
                                            │
                                            ▼
                              ┌──────────────────────────────┐
                              │ "output-utf8.txt" (UTF-8)    │
                              └──────────────────────────────┘

3. 实战示例:编码转换器

我们把情景复杂化一点:写一个简单的转换程序,允许用户自己选择源文件和目标文件,以及编码。

Console.WriteLine("请输入源文件路径:");
string inputPath = Console.ReadLine();

Console.WriteLine("源文件编码(例如:1251, utf-8):");
string sourceEncodingName = Console.ReadLine();

Console.WriteLine("请输入目标文件路径:");
string outputPath = Console.ReadLine();

Console.WriteLine("保存时使用的编码(例如:utf-8, 1251):");
string destEncodingName = Console.ReadLine();

Encoding sourceEncoding = Encoding.GetEncoding(sourceEncodingName);
Encoding destEncoding = Encoding.GetEncoding(destEncodingName);

using var reader = new StreamReader(inputPath, sourceEncoding);
using var writer = new StreamWriter(outputPath, false, destEncoding);
string line;
while ((line = reader.ReadLine()) != null)
{
    writer.WriteLine(line);
}

Console.WriteLine("完成!请检查结果。");

建议: 如果不确定该填哪个编码,查文档或者试几个常见的。很多时候只有试验才能确认,尤其是没人留下 README 的情况下。

4. 重要细节:BOM、“不可见”字符和特殊情况

如何在保存时添加 UTF-8 的 BOM?

默认情况下,Encoding.UTF8 在 C# 中会创建 不带 BOM 的文件。如果需要带 BOM,可以用:

// true — 表示 "带 BOM"
var utf8WithBom = new UTF8Encoding(true);

using var writer = new StreamWriter("with-bom.txt", false, utf8WithBom);
writer.WriteLine("带 BOM 的文本");

文件现在会以三字节的“不可见”序列开头:0xEF, 0xBB, 0xBF

什么时候 BOM 会惹麻烦?

  • 如果你导出 CSV 给 Excel,大多数非本地化版本里 BOM 反而会导致怪字符出现。
  • 在类 Unix 系统(Linux)下 BOM 有时会干扰文件处理。
  • 对于 JSON 文件,BOM 几乎总是有害的,很多解析器不能正确处理!

建议: 有意识地选择是否需要 BOM,仅在必要时添加。

潜伏的不可见字符和魔法数字

如果打开“转码后”的文件时程序报格式错误,可能是你没考虑到文件开头的“不可见”字符(比如 BOM),或者源编码本身就选错了(比如把 1251 的文件当成 ASCII 读了)。务必确认应用/同事/服务器使用的到底是什么编码。

5. 实用小贴士

处理大文件:为啥按行读而不是一次性读入?

你也可以这样做:

string allText = File.ReadAllText("input.txt", Encoding.GetEncoding(1251));
File.WriteAllText("output.txt", allText, Encoding.UTF8);

这个方式适合小文件或中等大小文件(比如不超过 50MB)。但如果文件很大,整个文本会被加载到内存 —— 如果文件是几个 GB,后果自不待言。因此为通用性我们推荐按行读写。

在不太常见的编码之间转换

比如你突然收到了一个 ISO-8859-1 的文件(MySQL 导出时常见),想把它变成 Unicode:

var sourceEnc = Encoding.GetEncoding("iso-8859-1");
var destEnc = Encoding.UTF8;

using var reader = new StreamReader("data-latin.txt", sourceEnc);
using var writer = new StreamWriter("data-unicode.txt", false, destEnc);
string line;
while ((line = reader.ReadLine()) != null)
{
    writer.WriteLine(line);
}

只要 .NET 支持该编码,这种方法对任何编码都有效。

不要对二进制文件做转码!

上面描述的程序仅适用于 文本文件。如果文件是二进制(比如图片、音频、归档),尝试把它当文本读写会破坏字节流,导致乱码或文件损坏。二进制文件没有“编码”的概念,不能这么转换。

对应表:哪些编码适合什么场景

编码 何时使用
UTF-8 通用文件、Web、现代应用
UTF-8 带 BOM 为了和 Notepad、Excel 兼容
Windows-1251 “老派”应用、本地俄语程序
ASCII 只有英文内容的文件
UTF-16 特殊情况,某些非主流应用

辅助技巧和建议

怎么判断文件编码?

  • 专用编辑器(Notepad++、Visual Studio Code)通常会识别并显示编码。
  • 如果没有 BOM,但看起来内容“正确”而字母不对 —— 很可能编码不匹配。

能自动识别编码吗?

  • .NET 没有“万能钥匙”能 100% 判断任意文件编码。一般规则是:有 BOM 就能确定是带 BOM 的 UTF,没 BOM 时只能根据内容猜测或试错。

如果部分行看起来正常而部分出现乱码怎么办?

  • 可能文件是混合的或被损坏。检查是否有多个程序以不同编码写入过同一个文件。

6. 常见错误及如何避免

为了更好理解,来看常见错误:

源编码弄错。 如果你以为文件是 UTF-8,但其实是 Windows-1251 —— 会得到一堆“楼梯”而不是西里尔字母。确认源编码,若不确定,用 Notepad++ 等工具查看。

BOM 放错地方。 有时加上 BOM 会破坏解析,有时不加 BOM 又会导致对方误判编码。

一次性把整个文件读入内存。 文件太大时请改用按行读取,避免程序把所有 RAM 都吃光。

写文件时不指定编码。 StreamWriter 默认使用 UTF-8 不带 BOM。如果你需要别的,请显式传入编码。

错误使用 GetEncoding 如果字符串写错了,比如写成 "utf8" 而不是 "utf-8",会抛异常。使用正确的名字或编号(例如 1251)。

1
调查/小测验
基本编码类型第 37 级,课程 4
不可用
基本编码类型
文件编码处理
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION