1. はじめに
まず重要な問いから始めましょう — なぜわざわざエンコーディングを扱う必要があるのか?一見すると「ファイルが1つ、エンコーディングが1つなら問題ないはず」と思えるかもしれません。しかし実際はもっと面白い状況になります。結構早い段階で、ファイルはどこからともなく来て、全く予想外のエンコーディングになっていることがわかります。で、あなたのアプリは当然別のエンコーディングを期待しているわけです。
ときには、あるAPIやシステムと統合する必要があり、そのシステムが「このエンコーディングだけ」と厳しく決めている場合があります。あるいは10年前の古いファイルを取り出して新しいエディタで開くと文字化けしている、なんてこともあります。たとえばCSVレポートを保存するとき、WindowsでもMacでも、さらにはExcelでも同僚が正しく読めるようにしたい、というニーズもあります。
要するに、再エンコードは珍しい作業でも奇妙な作業でもありません。むしろ現実的で頻繁に発生するタスクで、データベースのダンプ、アーカイブ処理、会計システムとの統合、自動スクリプトなど、色んな所で出てきます。早く対処方法を理解しておけば、後のサプライズが減ります。
古いエンコーディングから新しいエンコーディングへ
ファイルを再エンコードするには2つのステップが必要です:
- 読み取る:元のエンコーディングでファイルを読み、文字列(または文字)を得る。
- 書き出す:得た文字列を新しいファイルに、目的のターゲットエンコーディングを明示して書き出す。
比喩で言えば、フランス語の本をロシア語に翻訳するようなものです。まずフランス語が読める(テキストを読む)必要があり、その後同じ意味をロシア語で表現する(テキストを書き出す)わけです。
C#(と.NET)では、StreamReader(読み取り)とStreamWriter(書き込み)のコンストラクタに適切なEncodingオブジェクトを渡すことでこれを実現します。
エンコーディングの概要とクラス Encoding
すべての「マジックな」文字変換は System.Text.Encoding クラスで行われます。
主要なエンコーディングとC#での取得方法を表にまとめると次のようになります:
| エンコーディング | 説明 | C# 定数 |
|---|---|---|
| UTF-8 | 汎用的、デフォルトではBOMなし | |
| UTF-8(BOM付き) | 同じだがBOMシグネチャ付き | |
| UTF-16 (LE/BE) | いわゆる"wide char"、little-endian/ big-endian | Encoding.Unicode (LE)、 Encoding.BigEndianUnicode (BE) |
| ASCII | 7ビットの古典 | |
| Windows-1251 | キリル文字でよく使われる | |
| ISO-8859-1 | ラテン文字(ヨーロッパ系) | |
注意: 古いエンコーディングを使う場合は Encoding.GetEncoding を使う必要があり、数値(1251 のように)や文字列("windows-1251" のように)を渡します。
2. 再エンコード:C#での手順
実際にどのように再エンコードするか見ていきます。
アルゴリズム
- まず StreamReader で元ファイルを開き、元のエンコーディングを明示する。
- テキストを読み取る(全部か行ごとかはファイルサイズによる)。
- 次に StreamWriter で出力ファイルを開き、目的のエンコーディングを指定する。
- 読み取ったテキストを出力ファイルに書き込む。
- ストリームのクローズを忘れない — 安全のために using を使う!
例:Windows-1251 → UTF-8 に再エンコード
たとえば、元のファイルが "input-1251.txt"(Windows-1251)で、出力をBOMなしのUTF-8である "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を付けるには?
デフォルトでは C# の Encoding.UTF8 はBOM(Byte Order Mark)なしでファイルを作成します。BOM付きのファイルを作りたいときは次のようにします:
// true — "BOMあり"
var utf8WithBom = new UTF8Encoding(true);
using var writer = new StreamWriter("with-bom.txt", false, utf8WithBom);
writer.WriteLine("BOM付きのテキスト");
このファイルは先頭に「見えない」3バイト、0xEF、0xBB、0xBF が付きます。
BOMが害になる場合
- CSVをExcel向けにエクスポートするとき、多くの海外版ExcelではBOMが邪魔になり奇妙な文字が出ることがある。
- Unix系システム(Linux)ではBOMがファイル処理を壊すことがある。
- JSONファイルではBOMはほとんど常に害、たくさんのパーサがBOMを扱えない。
アドバイス: BOMが必要かどうかを意識して、必要な場合だけ付けるようにしましょう。
潜む文字とマジックナンバー
再エンコードしたファイルを開いたときにフォーマットエラーが出る場合、先頭にある「見えない」文字(例えばBOM)を考慮していないか、そもそものエンコーディングを間違えている可能性があります(例:ファイルをASCIIで読んだのに中身は1251のキリル文字)。アプリや同僚、サーバがどのエンコーディングを使っているかを常に確認してください。
5. 便利なヒント
大きなファイルの扱い:なぜ行ごとなのか?
単純にこう書くこともできます:
string allText = File.ReadAllText("input.txt", Encoding.GetEncoding(1251));
File.WriteAllText("output.txt", allText, Encoding.UTF8);
このやり方は小〜中くらいのファイル(例えば50MBまで)には使えます。しかしファイルが大きいと全テキストがメモリに読み込まれてしまい、数GBのファイルでは大問題になります。だから汎用的には行ごとの読み書きを使います。
あまり一般的でないエンコーディング間の変換
例えば、ISO-8859-1 のファイルがあって、それをUnicodeにしたい場合を考えます(MySQLのダンプとかでよくある)。
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があればそれはUTF系、なければ内容に基づいて推測するか、いくつか試すしかない、という感じです。
一部の行だけ正しく読めて、一部が文字化けする場合は?
- ファイルが混在している(複数のプログラムで別々のエンコーディングで書かれている)か、ファイルが破損している可能性があります。違う書き手が混在していないか確認しましょう。
6. よくあるミスと回避法
理解をスムーズにするため、よくあるミスを見ておきます:
元のエンコーディングを間違える。 ファイルがUTF-8だと思って実はWindows-1251だった、というとキリル文字がぐちゃぐちゃになります。元のエンコーディングを確認しましょう。わからなければNotepad++などで確認してください。
BOMの付け方を誤る。 BOMを付けるとパースが壊れる場合もあれば、付けないと相手が認識できない場合もあります。状況に応じて判断してください。
ファイル全体をメモリに読み込む。 大容量ファイルでは行ごとに読み書きすることでメモリ消費を抑えましょう。
エンコーディングを指定せずに書き込む。 デフォルトで StreamWriter はBOMなしのUTF-8を使います。別のエンコーディングが必要なら明示的に指定してください。
GetEncoding の誤用。 例えば "utf8" と書く代わりに正しくは "utf-8" と書かないと例外になります。正しい名前(またはコード、例: 1251)を使いましょう。
GO TO FULL VERSION