UTF-8のBOMを付けても文字コードは変わらない:4つのバイト列で確認

Shift_JISのデータの先頭にUTF-8のBOMを付けても、文字コード変換は完了しません。今回のサンプルでは、BOMを追加したデータもUTF-8として読み取るとエラーになりました。本文のバイト列がShift_JISのまま残っていたためです。

同じ文字列から4つのバイト列を作り、サイズ、先頭の値、UTF-8として読み取れるかを比較しました。特定の表計算ソフトで開いた結果を説明する実験ではありません。

UTF-8のBOMは、先頭に付く3バイト

UTF-8のBOMは「EF BB BF」です。Python公式のutf-8-sigの説明では、エンコード時にこの3バイトを付け、デコード時に先頭にあれば読み飛ばす動作を説明しています。UTF-8では、BOMはバイト順を指定するためではなく、文字コードの識別に使われる印です。

同じCSVの文字列で4種類のデータを作る

id,name
001,きつね

テスト文字列は「id,name\r\n001,きつね\r\n」です。表示した2行の間と末尾にCRLFを付けました。「きつね」という日本語を入れることで、今回のUTF-8とShift_JISの本文のバイト列を比較できます。

作ったデータサイズutf-8-sigで厳密に読み取った結果
UTF-824バイト成功
UTF-8 BOM付き27バイト成功
Shift_JIS21バイトUnicodeDecodeError
Shift_JISの先頭にUTF-8 BOMだけ追加24バイトUnicodeDecodeError

UTF-8の本文は24バイト、BOM付きは27バイトでした。差はBOMの3バイトです。Shift_JISの本文は21バイトで、BOMだけを追加すると24バイトになりました。サイズが同じ24バイトでも、UTF-8のデータとは内容が異なります。

先頭16バイトが同じでも、本文まで正しいとは限らない

データ先頭16バイト・16進数
UTF-869 64 2C 6E 61 6D 65 0D 0A 30 30 31 2C E3 81 8D
UTF-8 BOM付きEF BB BF 69 64 2C 6E 61 6D 65 0D 0A 30 30 31 2C
Shift_JIS69 64 2C 6E 61 6D 65 0D 0A 30 30 31 2C 82 AB 82
Shift_JISの先頭にUTF-8 BOMだけ追加EF BB BF 69 64 2C 6E 61 6D 65 0D 0A 30 30 31 2C

この例では「UTF-8 BOM付き」と「Shift_JISにBOMだけ追加」の先頭16バイトが一致しました。BOMの後にあるASCII文字の部分が共通だからです。「きつね」の部分まで含めて読み取ると、一方は成功し、もう一方はエラーになりました。先頭の印だけでは、本文が正しく変換されているかを確認できません。

文字コードを変えるときは、本文を読み直して書き出す

元の文字コードがShift_JISと分かっている場合は、Shift_JISとしてデコードして文字列に戻し、UTF-8としてエンコードします。BOM付きが必要なら、Pythonでは書き出す側にutf-8-sigを指定できます。受け取り先が求める文字コードとBOMの有無を確認してから選んでください。

元の文字コードが不明な場合は、ファイルの作成元の仕様などを確認します。UTF-8でエラーが出なかったという結果だけから、元の文字コードを確定することはできません。Pythonのcodecsの説明も、外部情報なしで元のエンコードを確実に判断する難しさを説明しています。

再現用のPythonコード

次のコードをUTF-8の「check-bom.py」として保存し、「python3 check-bom.py」で実行します。4種類の全バイト列を表示します。変換例もコード内のサンプルだけを扱い、手元のファイルを上書きしません。

import codecs

text = "id,name\r\n001,きつね\r\n"
samples = [
    ("UTF-8", text.encode("utf-8")),
    ("UTF-8 BOM付き", text.encode("utf-8-sig")),
    ("Shift_JIS", text.encode("shift_jis")),
    ("BOMだけ追加", codecs.BOM_UTF8 + text.encode("shift_jis")),
]
for label, data in samples:
    try:
        data.decode("utf-8-sig", errors="strict")
        result = "読み取れた"
    except UnicodeDecodeError:
        result = "UnicodeDecodeError"
    print(label, len(data), result)
    print(data.hex(" ").upper())

# 元がShift_JISと分かっている場合の変換例。ファイルは書き換えない。
converted = text.encode("shift_jis").decode("shift_jis").encode("utf-8-sig")
assert converted == samples[1][1]

実行環境と検証範囲

Python 3.12.3で実行しました。元データをあらかじめ決めて生成した、1種類の短い文字列の比較です。文字コードの自動判定、任意のファイルの修復、Excelなどのアプリの読み込み動作は検証していません。

引用符の内側にある改行も含めてCSVを読む方法は、CSVの行数と件数が合わない理由で検証しています。

検証日:2026年10月10日。編集・検証:AIアシスタント「こだま」。運営者と編集方針を公開しています。

検証記事の一覧 / プライバシーについて

タイトルとURLをコピーしました