BOM (バイトオーダーマーク)
ファイル先頭に付与されるエンコーディング識別用のバイト列。UTF-8 では EF BB BF、UTF-16 では FF FE または FE FF。
BOM (Byte Order Mark) は、テキストファイルの先頭に付与される特殊なバイト列で、エンコーディングの種類やバイト順 (エンディアン) を示す目印です。Unicode の文字 U+FEFF (ZERO WIDTH NO-BREAK SPACE) をエンコードしたもので、ファイルを開くアプリケーションがエンコーディングを自動判定するための手がかりとして機能します。BOM はファイルの内容そのものではなく、メタ情報としての役割を担っています。
BOM の具体的なバイト列はエンコーディングごとに異なります。UTF-8 では EF BB BF の 3 バイト、UTF-16 ビッグエンディアン (BE) では FE FF の 2 バイト、UTF-16 リトルエンディアン (LE) では FF FE の 2 バイト、UTF-32 BE では 00 00 FE FF、UTF-32 LE では FF FE 00 00 の 4 バイトです。UTF-16 や UTF-32 ではマルチバイトの数値をどちらのバイト順で格納するかが問題になるため、BOM によるエンディアン判別が不可欠です。一方、UTF-8 はバイト順の概念を持たないため、BOM は純粋にエンコーディング識別の目的で使われます。
自分で BOM を判定するコードを書くときは、バイト列の長い方から順に照合するのが鉄則です。UTF-32 LE の BOM (FF FE 00 00) は先頭 2 バイトが UTF-16 LE の BOM (FF FE) と完全に一致するため、短い方から先に比べると UTF-32 LE のファイルを UTF-16 LE と誤判定し、以降の本文がすべて NUL 混じりの文字列として読めてしまいます。同様に UTF-8 の EF BB BF は 3 バイト揃って初めて BOM であり、途中で切れたバイト列は不正なシーケンスです。判定順序は UTF-32 (4 バイト) → UTF-8 (3 バイト) → UTF-16 (2 バイト) の順に組みます。
実務で最も頻繁に問題になるのが UTF-8 BOM の扱いです。UTF-8 BOM (EF BB BF) はファイル先頭の 3 バイトとして存在しますが、多くのプログラムやツールがこれを不要なバイトとして扱います。たとえば、シェルスクリプトの先頭に BOM があると shebang 行 (#!/bin/bash) が正しく認識されず実行に失敗します。PHP ファイルでは BOM が HTML 出力の前に送信され、headers already sent エラーの原因になります。CSV ファイルでは、BOM の有無によって Excel での文字化けが発生するかどうかが変わります。
付けるべきか付けないべきかは、規格の側でも UTF-8 に限っては答えが出ています。Unicode 標準は UTF-8 での BOM を容認はするものの、必須でも推奨でもないという立場です。HTML5 の側は「ブラウザは UTF-8 の BOM を認識してページのエンコーディング判定に使うこと」を求めており、BOM は HTTP ヘッダーのエンコーディング宣言より優先されます。つまり BOM を付ければ判定は通りますが、それはあくまでブラウザ側の救済であって、推奨される書き方ではありません。<meta charset="utf-8"> を書いて BOM なしで保存するのが Web では素直な選択です。JSON はさらに明確で、RFC 8259 はネットワークで送る JSON テキストの先頭に BOM を付けてはならないと定めています (パーサー側が読み飛ばすことは許容)。
Windows のメモ帳は長年にわたり UTF-8 保存時に BOM を自動付与していましたが、Windows 10 のバージョン 1903 以降では BOM なし UTF-8 がデフォルトに変更されました。古いファイルを開いたときに BOM が残っていることは今もあるため、既存ファイルの保存し直しでは BOM の有無を毎回確認します。一方、日本語環境の Excel で UTF-8 の CSV をダブルクリックして開く場合は、BOM が無いとシステムの既定文字コードとして解釈されて文字化けすることがあり、あえて BOM を付けて出力するのが定石です。Web サイトからダウンロードさせる CSV は BOM 付き、プログラム間で受け渡すデータは BOM なし、と出口ごとに方針を分けるのが実務的な落としどころです。Visual Studio Code や Sublime Text などのモダンなエディタでは、ステータスバーでエンコーディングと BOM の有無を確認・変更できます。
文字数カウントにおいて、BOM は見落としやすい落とし穴です。BOM はゼロ幅の不可視文字であるため画面上には表示されませんが、ファイルサイズには影響します。UTF-8 BOM 付きファイルは BOM なしファイルより 3 バイト大きくなります。また、テキストを文字列として読み込んだ際に BOM が文字列の先頭に残ると、文字数が 1 文字多くカウントされたり、文字列比較で不一致が生じたりする原因になります。{"a":1} という 7 文字の JSON を BOM 付き UTF-8 で保存すると、バイト数は 7 バイトから 10 バイトへ、単純に読み込んだ文字列の長さは 7 文字から 8 文字へ増えます。増えた 1 文字は U+FEFF であり、画面では空白すら見えません。
この 1 文字は、比較や解析の失敗として遅れて表面化します。BOM 付きファイルを普通に UTF-8 として読んで JSON を解析すると、Python では「Unexpected UTF-8 BOM」というエラーで落ちます。設定ファイルのキー名が一致しない、CSV の 1 列目のヘッダー名だけ照合に失敗する、といった症状も同じ原因です。読み込み側では、BOM を認識して取り除くエンコーディング指定 (Python なら utf-8-sig) を使うか、先頭の U+FEFF を明示的に除去してから処理に渡します。文字数を数える機能を作る場合は、上限判定や差分比較の前に BOM を落とすのが安全です。逆に、Excel 向けの CSV を書き出す処理では BOM を意図的に付けるので、除去と付与のどちらが正しいかは入口か出口かで決まります。