中学理科1630686 views
小学社会310495 views
数学講師2886596 views
ヒストリア290629 views
雑学1473627 views
英語613685 views
MathPython497416 views
りんご209693 views
小学理科719898 views
高校化学2924523 views
Help
Tools
NewsSpreadsheetCalendarBookkeepingMarkdown TablesLanguage Model NewsSlidesTier ListPen ToolIllustrationCrayonWatercolorPixel ArtASCII ArtPerspectiveEndless StairsGraphMind MapER DiagramFamily TreeMemeCurved TextImage EditorMosaicRetro FilterPencil SketchSwirl EffectLine ArtOCR/HighlighterMakeup EditorFaviconVideo TrimmerScrolling VideoVideo TitleColor PickerColor ExtractorBonfireFireworksCherry BlossomWater RippleWater SplashBreaking GlassGlass TextureFabric TextureWood GrainMarble TextureBrick Wall TextureMetal TextureWashi Paper TextureCardboard TextureCSS ButtonIcon MakerBar ChartGrouped Bar ChartStacked Bar ChartPie ChartLine ChartArea ChartStacked Area ChartScatter Plot3D Bar Chart3D Pie ChartBar Chart RaceBubble ChartPopulation PyramidPictogramEarningsCandlestick ChartInvestment RiskMortgage SimulatorCalculatorMatrix CalculatorFunction GraphPolynomial ExpansionVenn DiagramField VisualizerRubik's Cube Group TheoryTraveling SalesmanVoronoi and DelaunayFractalColumn ArithmeticDraw Math FiguresArithmetic AnimationArithmetic Word ProblemsCounting with Tree DiagramsCube NetsRolling DiceCross SectionsMotion PathMechanicsWavesElectromagnetic WavesCapacitorsLight and LensesThermodynamicsHow Semiconductors WorkMolecular StructuresAtomic OrbitalsElectrochemical CellsChemical EquilibriumCrystal LatticesBuffer pHOrganic Reaction MapPeriodic TableComplex IonsDNA Double HelixCell DivisionMembrane ChannelsNerve ImpulseMuscle ContractionHormones and HomeostasisRock ClassificationWeatherConstellationsSolar and Lunar Eclipses3D ModelingFloor PlanSeismic StructuresIntersection TurnMaglevCooking AnimationOrigamiLive Viewer CountGeoJSON MapRailway MapPopulation MapCrime MapLand Price MapSchool MapShrine and Castle MapHouse of Representatives MapWord MapSolitaireReversiHakoiri MusumeChessHamburgerRippleSlide Puzzle MakerNeon PinballNovel MakerJapanese Typing PracticePiano Score EditorMusic TheoryShogi StrategyPiano Rhythm Game

English

BOM(バイト順マーク)の正体 - UTF-8 に BOM は必要か

テキストファイルを開いたとき、先頭に見覚えのない文字化けが現れた経験はないだろうか。その正体は BOM(Byte Order Mark)かもしれない。文字エンコーディングの判定に使われるこの仕組みは、UTF-16 では不可欠だが UTF-8 では厄介な問題を引き起こすことがある。

BOM とは何か

BOM は Unicode テキストの先頭に置かれる特殊な文字で、コードポイントは U+FEFF である。もともとの名前は「Zero Width No-Break Space(ゼロ幅改行なしスペース)」だったが、現在はバイト順マークとしての用途に限定されている。

この文字がファイル先頭に置かれたとき、テキストを読むプログラムは 2 つの情報を得られる。1 つはそのファイルが Unicode であること、もう 1 つはバイト順(エンディアン)がどちらであるかということだ。

複数バイトで 1 つの値を表すとき、上位バイトを先に並べるか(ビッグエンディアン)、下位バイトを先に並べるか(リトルエンディアン)という順序の違い。

なぜバイト順が問題になるのか

コンピュータのプロセッサによって、メモリ上のバイトの並べ方が異なる。UTF-16 では 1 文字を 2 バイトで表すため、この並び順の違いがデータの解釈に直結する。

「A」(U+0041)を UTF-16 で表す場合を見てみよう。

ビッグエンディアン(BE)

上位バイトが先に来る。0x00 0x41 の順で格納される。ネットワーク通信の標準バイト順でもある。

リトルエンディアン(LE)

下位バイトが先に来る。0x41 0x00 の順で格納される。Intel 系プロセッサが採用しており、Windows 環境で多い。

BOM なしのファイルを受け取ったプログラムは、0x00 0x41 が「A」なのか 0x41 0x00 が「A」なのか判断できない。そこで先頭に U+FEFF を書いておけば、そのバイト列の並び順からエンディアンを特定できる。

エンディアンBOM のバイト列意味
BE0xFE 0xFFビッグエンディアン
LE0xFF 0xFEリトルエンディアン

0xFE 0xFF と読めればビッグエンディアン、0xFF 0xFE と読めればリトルエンディアンだとわかる。逆の並びである U+FFFE は Unicode で永久に文字を割り当てないと定められているため、誤認の心配がない。

UTF-8 の BOM

UTF-8 にはバイト順の問題が存在しない。UTF-8 は 1 バイト単位でエンコードするため、エンディアンの概念がそもそも不要だ。それにもかかわらず、UTF-8 にも BOM が付くことがある。

UTF-8 で U+FEFF をエンコードすると 0xEF 0xBB 0xBF という 3 バイトになる。これがファイル先頭にあると「このファイルは UTF-8 である」という目印になる。

# ファイル先頭のバイトを確認する
xxd -l 4 file.txt

# BOM ありの場合
# 00000000: efbb bf48    ...H
# → 先頭 3 バイトが EF BB BF = UTF-8 BOM

# BOM なしの場合
# 00000000: 4865 6c6c    Hell
# → いきなりテキスト内容が始まる

Windows のメモ帳は長らく UTF-8 で保存するときに BOM を自動付加していた。この挙動は多くのトラブルの原因となってきた。

BOM が引き起こす問題

UTF-8 の BOM は「あると便利」どころか「あると壊れる」場面が少なくない。

PHP のヘッダー送信エラー

PHP でファイル先頭に BOM があると、header() や session_start() の前に出力が発生したとみなされ、“Headers already sent” エラーになる。BOM の 3 バイトがブラウザへの出力として扱われるためだ。

JSON パースの失敗

JSON の仕様(RFC 8259)は先頭に BOM を付けることを禁止していないが、パーサーによっては BOM を不正なトークンとして拒否する。Node.js の JSON.parse() に BOM 付き文字列を渡すとエラーになる。

シェルスクリプトの shebang 破壊

#!/bin/bash の前に BOM があると、OS はシバン行を正しく認識できず、スクリプトの実行に失敗する。

CSV の文字化け

Excel は UTF-8 BOM 付きの CSV を正しく開けるが、BOM なしの UTF-8 CSV は文字化けすることがある。これが「BOM を付けるべき」という誤解を生む一因にもなっている。

実際のバイト列を確認する

BOM の有無でファイル先頭のバイト列がどう変わるか、JavaScript で可視化してみよう。

HTML
CSS
JavaScript
<div id="bom-demo">
    <button id="btn-with">BOM あり</button>
    <button id="btn-without">BOM なし</button>
    <div id="output"></div>
</div>
#bom-demo {
    font-family: monospace;
    font-size: 14px;
}
button {
    padding: 6px 14px;
    margin: 0 6px 12px 0;
    cursor: pointer;
    border: 1px solid #999;
    background: #f5f5f5;
    border-radius: 4px;
}
button:hover {
    background: #e8e8e8;
}
#output {
    white-space: pre;
    background: #1e1e1e;
    color: #d4d4d4;
    padding: 12px;
    border-radius: 4px;
    min-height: 60px;
    line-height: 1.6;
}
.bom-byte {
    color: #f44;
    font-weight: bold;
}
.text-byte {
    color: #9cdcfe;
}
function toHex(arr) {
    return Array.from(arr).map(function(b) { return b.toString(16).padStart(2, '0').toUpperCase(); });
}

function display(bytes, bomLen) {
    var hex = toHex(bytes);
    var lines = [];
    lines.push('バイト列:');
    var parts = hex.map(function(h, i) {
        if (i < bomLen) return '<span class="bom-byte">' + h + '</span>';
        return '<span class="text-byte">' + h + '</span>';
    });
    lines.push('  ' + parts.join(' '));
    lines.push('');
    if (bomLen > 0) {
        lines.push('<span class="bom-byte">赤 = BOM (EF BB BF)</span>');
    } else {
        lines.push('BOM なし: テキストが先頭から始まる');
    }
    lines.push('<span class="text-byte">青 = テキスト "Hello"</span>');
    document.getElementById('output').innerHTML = lines.join('\n');
}

document.getElementById('btn-with').addEventListener('click', function() {
    var text = new TextEncoder().encode('Hello');
    var bom = new Uint8Array([0xEF, 0xBB, 0xBF]);
    var combined = new Uint8Array(bom.length + text.length);
    combined.set(bom);
    combined.set(text, bom.length);
    display(combined, 3);
});

document.getElementById('btn-without').addEventListener('click', function() {
    var text = new TextEncoder().encode('Hello');
    display(text, 0);
});

document.getElementById('btn-with').click()

各仕様の BOM に対するスタンス

BOM を付けるべきかどうかは仕様ごとに方針が異なる。

仕様BOM の扱い
Unicode 規格UTF-8 では BOM は任意
HTML(WHATWG)BOM を認識するが非推奨
JSON(RFC 8259)先頭に BOM を付けてはならない
XML 1.0BOM を認識する

Unicode 規格自体は UTF-8 の BOM を禁止していないが、WHATWG の Encoding Standard では UTF-8 に BOM を付けないことが推奨されている。

WHATWG はブラウザベンダーが主導する Web 標準化団体で、HTML や DOM、Encoding などの Living Standard を策定している。

W3C の HTML 仕様でも、meta タグの charset 指定と BOM が矛盾した場合は BOM が優先されると定められている。この挙動は意図しないエンコーディングの誤認を引き起こす可能性があるため、BOM に頼らず charset を正しく指定するほうが安全だ。

BOM の検出と除去

既存のファイルに BOM が含まれているかを確認し、必要に応じて除去する方法を見ておこう。

# BOM の検出(Linux / macOS)
file --mime-encoding target.txt
# UTF-8 Unicode (with BOM) と表示されれば BOM あり

# BOM の除去
sed -i '1s/^\xEF\xBB\xBF//' target.txt

# ディレクトリ内の全ファイルから BOM を除去
find . -name "*.txt" -exec sed -i '1s/^\xEF\xBB\xBF//' {} \;

JavaScript でも BOM を除去できる。

function removeBOM(str) {
    if (str.charCodeAt(0) === 0xFEFF) {
        return str.slice(1);
    }
    return str;
}

// fetch で取得したテキストから BOM を除去
fetch('data.json')
    .then(res => res.text())
    .then(text => {
        const clean = removeBOM(text);
        const data = JSON.parse(clean);
    });

Node.js で JSON ファイルを読み込むときに BOM 付きファイルでエラーが出た場合は、この除去処理を挟むことで解決する。

結局 UTF-8 に BOM は必要か

結論としては、UTF-8 のファイルには BOM を付けないのが現在の標準的な慣行だ。

UTF-8 で保存するファイルに BOM を付けるべき場面はどれですか?

  • PHP のソースファイル
  • JSON データファイル
  • Excel で開く CSV ファイル
  • シェルスクリプト
__RESULT__

Excel は BOM なしの UTF-8 CSV を正しく認識できないことがあるため、Excel での閲覧を想定する CSV には BOM を付けるのが実用的な対処法です。それ以外の場面では BOM を付けない方が安全です。

UTF-16 では BOM がバイト順の判定に不可欠だが、UTF-8 ではエンディアンの問題がないため本来不要な存在である。歴史的経緯から BOM が付いているファイルは今でも流通しているが、新規に作成するファイルには付けないほうがトラブルが少ない。エディタの設定を確認して、UTF-8 保存時に BOM を自動付加しない設定にしておくことを勧める。