1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C# 文字化けした時はここだけ見る|先頭だけ変=BOM、全部崩れる=文字コード違い、桁数ずれ=byte数【外伝G21】

1
Posted at

連載Index(読む順・公開済リンクが最新): S00_門前の誓い_総合Index

先頭だけ変なら BOM、全部崩れるなら文字コード違い、桁数だけ外れるなら byte 数を見る。

  • CSV の 1 列目だけ合わない
  • 日本語全体が ��� や別の文字になる
  • 文字数は合っているのに上限判定だけ外れる

先に分ける表

見え方 まず見る場所 典型例 最初の確認
先頭だけ変 BOM Id だけ見つからない 先頭 3 byte を見る
全部崩れる 文字コード違い 日本語全体が別の文字になる 同じファイルを UTF-8 と Shift_JIS の両方で読む
桁数だけ外れる byte 数 固定長、切り詰め、上限判定だけずれる GetByteCount を見る

1 列目だけ合わない時は BOM を見る

見た目は Id でも、先頭に \uFEFF が付いていることがある。
CSV のヘッダ比較、辞書キー検索、列名一致で最初だけ外れる時は、ここから見る。

まずは最小の例。

using System;

var actual = "\uFEFFId";
var expected = "Id";

Console.WriteLine(actual == expected);
Console.WriteLine(actual.TrimStart('\uFEFF') == expected);
Console.WriteLine($"[{actual}]");
Console.WriteLine(actual.Length);

出力はこうなる。

False
True
[Id]
3

見た目は Id でも、比較結果は変わる。
先頭だけ外れる時は、文字コード全体より先に BOM を見る方が早い。

ファイルで確認するなら、先頭 3 byte をそのまま出す。

using System;
using System.IO;
using System.Linq;

var path = args[0];
var bytes = File.ReadAllBytes(path);

Console.WriteLine(string.Join(" ", bytes.Take(8).Select(x => x.ToString("X2"))));

var hasUtf8Bom =
    bytes.Length >= 3 &&
    bytes[0] == 0xEF &&
    bytes[1] == 0xBB &&
    bytes[2] == 0xBF;

Console.WriteLine($"HasUtf8Bom: {hasUtf8Bom}");
EF BB BF 49 64 2C 4E 61
HasUtf8Bom: True

EF BB BF なら UTF-8 の BOM あり。

自アプリ出力なら BOM あり / なしを決める

ここが曖昧だと、同じ話が戻りやすい。

using System.IO;
using System.Text;

var utf8WithBom = new UTF8Encoding(true);
var utf8WithoutBom = new UTF8Encoding(false);

File.WriteAllText("with-bom.csv", "Id,Name", utf8WithBom);
File.WriteAllText("without-bom.csv", "Id,Name", utf8WithoutBom);

CSV の 1 列目だけ外れる話が何度も出るなら、BOM あり / なしまで仕様へ書いておく方が早い。

日本語全体が崩れる時は文字コード違いを見る

ここは BOM ではなく、同じ byte 列を別の文字コードとして読んでいる 状態が多い。

まずは最小の例。

using System;
using System.Text;

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

var text = "商品名";
var utf8Bytes = Encoding.UTF8.GetBytes(text);
var broken = Encoding.GetEncoding("shift_jis").GetString(utf8Bytes);

Console.WriteLine(text);
Console.WriteLine(broken);

2 行目は崩れる。
UTF-8 で作った byte 列を Shift_JIS として読んでいるため。

ファイルでも見方は同じ。
既定値のまま読むより、UTF-8 と Shift_JIS の両方で読んだ方が差を追いやすい。

using System;
using System.IO;
using System.Text;

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

var path = args[0];

var utf8Text = File.ReadAllText(path, Encoding.UTF8);
var shiftJis = Encoding.GetEncoding("shift_jis");
var sjisText = File.ReadAllText(path, shiftJis);

Console.WriteLine("----- UTF-8 -----");
Console.WriteLine(utf8Text.Replace("\r\n", "\n").Split('\n')[0]);

Console.WriteLine("----- Shift_JIS -----");
Console.WriteLine(sjisText.Replace("\r\n", "\n").Split('\n')[0]);

既定値のまま読まない

差が出るのはこの 2 行。

using System.IO;
using System.Text;

// NG
var ngText = File.ReadAllText(path);

// OK
var okText = File.ReadAllText(path, Encoding.UTF8);

ReadAllText(path) だけだと、実行環境ごとの差を追いにくい。

.NET 8 で Shift_JIS を使う時の 1 行

.NET 8 で Shift_JIS を使う時は、先にこれが要る。

using System.Text;

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

これがないと Encoding.GetEncoding("shift_jis") が取れない環境がある。

Excel 保存の CSV で見る場所

Excel 経由の CSV は、同じ「CSV 保存」でも中身が変わることがある。
見るのは次の 3 つだけ。

確認項目 起こりやすい見え方
UTF-8 UTF-8 前提の処理ならそのまま読める
UTF-8 BOM 付き 1 列目だけ外れることがある
Shift_JIS 日本語全体が崩れることがある

「Excel で保存した」だけでは足りない。
UTF-8UTF-8 BOM 付きShift_JIS かまで見る。

桁数だけ外れる時は byte 数を見る

ここは文字化けではなく、長さの見方が違う 状態。
固定長ファイル、外部連携の上限、DB の長さ判定で出やすい。

using System;
using System.Text;

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

var text = "Aあ";
var utf8 = Encoding.UTF8;
var shiftJis = Encoding.GetEncoding("shift_jis");

Console.WriteLine($"Text: {text}");
Console.WriteLine($"Length: {text.Length}");
Console.WriteLine($"UTF-8 ByteCount: {utf8.GetByteCount(text)}");
Console.WriteLine($"Shift_JIS ByteCount: {shiftJis.GetByteCount(text)}");

出力はこうなる。

Text: Aあ
Length: 2
UTF-8 ByteCount: 4
Shift_JIS ByteCount: 3

Length は 2 でも、byte 数は同じではない。
上限が byte 単位なら、Length ではなく GetByteCount を見る。

差が出る形だとこの 2 行。

using System.Text;

// NG
var isOver = text.Length > 10;

// OK
var isOver = Encoding.UTF8.GetByteCount(text) > 10;

見直す場所はこのあたり。

  • 固定長レコードの組み立て
  • CSV 出力前の上限判定
  • DB 登録前の長さ判定
  • byte 単位で切る処理

1 本で確認する検査コード

BOM、UTF-8、Shift_JIS、byte 数をまとめて見る時は、これ 1 本で足りる。

using System;
using System.IO;
using System.Linq;
using System.Text;

internal static class Program
{
    private static void Main(string[] args)
    {
        if (args.Length == 0)
        {
            Console.WriteLine("Usage: TextInspect <filePath>");
            return;
        }

        Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

        var path = args[0];
        var bytes = File.ReadAllBytes(path);

        Console.WriteLine($"Path: {path}");
        Console.WriteLine($"Length: {bytes.Length}");
        Console.WriteLine($"FirstBytes: {string.Join(" ", bytes.Take(8).Select(x => x.ToString("X2")))}");
        Console.WriteLine($"HasUtf8Bom: {HasUtf8Bom(bytes)}");
        Console.WriteLine();

        PrintAs("UTF-8", path, Encoding.UTF8);
        Console.WriteLine();
        PrintAs("Shift_JIS", path, Encoding.GetEncoding("shift_jis"));
    }

    private static void PrintAs(string name, string path, Encoding encoding)
    {
        Console.WriteLine($"----- {name} -----");

        try
        {
            var text = File.ReadAllText(path, encoding);
            var firstLine = text.Replace("\r\n", "\n").Split('\n').FirstOrDefault() ?? string.Empty;

            Console.WriteLine($"Encoding: {encoding.WebName}");
            Console.WriteLine($"FirstLine: {firstLine}");
            Console.WriteLine($"ByteCountOfFirstLine: {encoding.GetByteCount(firstLine)}");
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.GetType().Name);
            Console.WriteLine(ex.Message);
        }
    }

    private static bool HasUtf8Bom(byte[] bytes)
    {
        return bytes.Length >= 3
            && bytes[0] == 0xEF
            && bytes[1] == 0xBB
            && bytes[2] == 0xBF;
    }
}
dotnet run -- sample.csv

ここで見るのは 4 つで十分。

出力 何を見るか
FirstBytes EF BB BF があるか
HasUtf8Bom BOM ありか
FirstLine 先頭だけ変か、全部変か
ByteCountOfFirstLine byte 数で見た長さ

仕様へ書く 4 項目

毎回「同じ CSV のはずなのに違う」となるなら、出力条件が曖昧なことが多い。

項目
文字コード UTF-8
BOM なし
改行 CRLF
byte 数で判定する列 顧客名、住所、備考

文字コードだけでは足りない。
BOM、改行、byte 数で見る列まで書いておくと、確認の戻りが減る。

.NET Framework 4.8.1 の補足

主役は .NET 8 だが、見方そのものは 4.8.1 でもほぼ同じ。
先頭だけ変なら BOM、全部崩れるなら文字コード違い、桁数だけ外れるなら byte 数を見る。

文字化けは広く探すより、見え方で先に分けた方が早い。

連載Index(読む順・公開済リンクが最新): S00_門前の誓い_総合Index

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?