UTF-8 の誕生と仕組み — ASCII互換、バイト数、UTF-16との違い
DDEVELOPER
開発(更新: 2026-09-06)10 分で読める

UTF-8 の誕生と仕組み — ASCII互換、バイト数、UTF-16との違い

UTF-8はUnicodeの文字をバイト列にする符号化方式です。ASCIIをそのまま使えることと、文字によって1〜4バイトを使うことが両立します。バイト数、コードポイント数、見た目の文字数は同じではありません。

RFC 3629は、1992年9月にKen ThompsonがRob Pikeの設計条件に沿って考案したと記しています。Plan 9の既存処理を大きく壊さずにUnicodeへ移行するための判断を、実際のビット構造と結び付けて見ていきます。

#Unicode#UTF-8#文字コード#Ken Thompson#Rob Pike

前史 — ASCII の 128 文字では足りなかった

1960 年代に制定された ASCII (American Standard Code for Information Interchange) は 7 ビット = 128 文字。英語のアルファベット、数字、基本的な記号を網羅しますが、日本語はもちろん、フランス語のアクセント記号すら表現できません。

1980 年代には各国・各ベンダーが独自の拡張を乱立させました: Shift_JIS (日本)、EUC-KR (韓国)、Big5 (台湾)、ISO 8859 シリーズ (欧州)、Windows-1252 (Microsoft)。同じバイト列が文字コードの解釈次第で全く異なる文字に化ける「文字化け」は日常的な問題でした。

この混沌を解決するため、1980 年代後半に Apple と Xerox のエンジニアが中心となり「世界中のすべての文字に一意の番号 (コードポイント) を割り当てる」構想が始まりました。これが Unicode です。

Unicode の誕生 — 1991 年 1 月 3 日

Unicode Consortium は 1991 年 1 月 3 日にカリフォルニア州で法人として設立されました。同年 10 月に Unicode 1.0 (Volume 1) が出版。当初は 16 ビット固定長 (最大 65,536 文字) で十分と考えられていました。

しかし Unicode はあくまで「コードポイント (文字に割り当てる番号)」の体系であり、バイト列としてどう表現するかは別問題です。初期の16ビット符号化(後の可変長UTF-16とは区別する) には致命的な問題がありました:

  • ASCII 非互換: ASCII の 'A' = 0x41 が 0x00 0x41 (2 バイト) になり、既存の C 言語プログラムが動かない (NULL バイトが文字列終端と誤認される)
  • バイト順問題: 0x00 0x41 と 0x41 0x00 のどちらが正しいか (エンディアン) を指定する必要がある
  • ファイルサイズ倍増: 英語テキストのサイズが倍になる

Unix / Plan 9 の世界では、これらの問題は受け入れがたいものでした。

1992年の設計と標準化

RFC 3629によれば、Thompsonが設計し、Pikeが設計条件を提示しました。X/Openの国際化グループを通じて標準化が進み、FSS-UTF、UTF-2、UTF-8という名称が使われました。ダイナーの逸話の細部や曜日を、規格が裏付ける成立史と混同しないことが大切です。

目標は、ASCIIの区切り文字やファイル名を扱う既存処理との互換性です。Unicode対応のために既存のすべてのバイト処理を書き換える必要を減らす設計でした。

UTF-8 のビット構造 — なぜこの設計なのか

Thompson が設計した UTF-8 のビットパターンは、RFC 3629 で以下のように定義されています:

表が収まらない場合は、左右にスクロールできます。

コードポイント範囲バイト数ビットパターン用途
U+0000 - U+007F10xxxxxxxASCII (英数字・基本記号)
U+0080 - U+07FF2110xxxxx 10xxxxxxラテン拡張、ギリシャ、キリル等
U+0800 - U+FFFF(U+D800〜U+DFFFを除く)31110xxxx 10xxxxxx 10xxxxxx日本語、中国語、韓国語、他
U+10000 - U+10FFFF411110xxx 10xxxxxx 10xxxxxx 10xxxxxx絵文字、古代文字、数学記号

この設計には 4 つの重要な性質があります:

  • ASCII 完全互換: U+0000〜U+007F は ASCII と同一のバイト (先頭ビット 0)。ASCIIの区切り文字は保たれる。ただしバイト単位の処理が文字単位の処理に変わるわけではない
  • 自己同期 (self-synchronizing): 先頭バイトは 011 で始まり、継続バイトは 10 で始まる。有効なUTF-8列であれば、最大 3 バイト戻るだけで文字境界を特定できる
  • バイト順非依存: 1 バイト単位の処理なので、ビッグエンディアン/リトルエンディアンの問題がない (UTF-16 の BOM 問題を回避)
  • NULL バイトが出現しない: U+0000 以外の文字で 0x00 が現れないため、C 言語の strlen()strcmp() によるバイト処理と互換性がある。ただしstrlenは見た目の文字数を数えず、strcmpは言語別の照合順を実装しない

先頭ビットだけでは有効性を判定できません。過長表現C0 80、サロゲートを表すED A0 80、上限を超えるF4 90 80 80、不完全な列は無効です。UTF-8の先頭に付くEF BB BF(BOM)は符号化方式の目印として使われる場合がありますが、バイト順指定ではなく、必要性は受け手やプロトコルによります。

日本語と UTF-8 — 3 バイトの代償と引き換えの統一

UTF-8では、多くのひらがな・カタカナ・CJK統合漢字のcode pointが3byteです。ただし補助平面の漢字・絵文字、結合文字などがあるため、見た目の「1文字」が常に3byteとは限りません。Shift_JISやEUC-JPで表現できる典型的な日本語文字との単純比較では、UTF-8側が3byte、legacy側が2byteになる例があります。

しかし Web の文脈では、HTML タグ、CSS、JavaScript のコードはほぼ ASCII であり、日本語はコンテンツ部分に集中します。全体の増加率やgzip/Brotli圧縮後の差は、実際の内容と設定で測定します。

送信側と受信側がUTF-8へ統一し、不正byte列を厳格に検証すれば、legacy encoding間の取り違えによる文字化けを大幅に減らせます。ただしUTF-8のbyteを別文字コードとして読む、または不正byteを置換して再保存すれば、UTF-8でも文字化けや情報損失は起こります。Shift_JISの「5C問題」のようにtrail byteをASCII記号として誤処理する構造とは分けて考える必要があります。

RFC 3629 (2003) — 4 バイト上限の確定

UTF-8 のRFCの改訂では、次の2文書を区別します:

  • RFC 2279 (1998 年 1 月): François Yergeau 著。最大 6 バイトのシーケンスを許容 (U+7FFFFFFF まで)
  • RFC 3629 (2003 年 11 月): 同じ Yergeau 著。RFC 2279 を廃止し、最大 4 バイト (U+10FFFF まで) に制限

なぜ 4 バイトに制限したのか? Unicode Consortium と ISO 10646 が協議し、コードポイント空間を U+10FFFF まで に限定することで合意したためです。これにより UTF-16 のサロゲートペア方式と範囲が一致し、有効なUnicodeスカラー値の列について、UTF-8、UTF-16、UTF-32間の無損失変換が可能です。

未割当てのコードポイントもあるため、符号化範囲の大きさを登録済み文字数と同一視しません。版ごとの文字数や将来の余裕を、UTF-8の仕組みを説明する条件にする必要はありません。

Webで使うときの確認点

HTMLの文字コード宣言やHTTPのcharsetと実ファイルのバイト列をそろえます。宣言をUTF-8へ変えるだけではファイルは変換されません。バイト単位の様子はバイナリエディターで確認できます。

Webサイトの文字コード採用率とWebトラフィックの割合は別の指標です。ここでは測定対象の不明な普及率を根拠にせず、互換性と実データの確認を判断材料にします。

UTF-8 と UTF-16 と UTF-32 — 使い分け

表が収まらない場合は、左右にスクロールできます。

エンコーディング単位ASCIIU+3042の例主な用途
UTF-81-4 バイト1 バイト3 バイトWeb、ファイル、API、Linux/macOS
UTF-162 or 4 バイト2 バイト2 バイトWindows API、Java String、JavaScript String
UTF-324 バイト固定4 バイト4 バイト内部処理 (コードポイント単位の処理に使う場合)

Unicode 2.0 (1996) ではサロゲートとUTF-16の仕組みが導入されました。これは補助平面への文字割当てとは別で、補助文字が初めて追加されたのはUnicode 3.1 (2001)です。UTF-16は1個または2個の16ビットコード単位でUnicodeスカラー値を表し、サロゲートペアは補助文字を表す2個のコード単位の組です。

JavaScript の String.length が絵文字で「2」を返す問題も、内部が UTF-16 であることに起因します。"😀".length === 2 は UTF-16 のサロゲートペア (2 つの 16 ビット単位) をカウントしているためです。

表のサイズはBOMを含みません。補助平面の漢字・絵文字はUTF-8でもUTF-16でも4バイトを使います。結合列は複数コードポイントなので、UTF-32でも見た目の1文字のサイズは固定されません。new TextEncoder().encode("😀").length === 4はUTF-8のバイト数を求める例です。

まとめ

  • UTF-8はASCII互換の可変長符号化で、Unicodeスカラー値を1〜4バイトで表す。
  • 1992年の設計と2003年のRFC 3629による範囲の整理を区別する。
  • サロゲート、過長表現、上限超過、不完全な列は有効なUTF-8ではない。
  • バイト数、コード単位数、コードポイント数、見た目の文字数を区別する。
  • charset・BOM・不正列の扱いを受け手と合意し、変換後のデータを確認する。

参考文献・ソース

記事作成に関する注記

本記事は AI(大規模言語モデル)を編集補助として活用して作成しています。 公開前に編集者が内容を確認していますが、事実誤認・仕様の解釈ミス・最新情報との齟齬が含まれる可能性があります。 重要な判断を行う際は、本文中の一次ソースや公式ドキュメントを必ずご自身でご確認ください。 誤りにお気づきの場合は、お問い合わせフォームよりご連絡いただけると助かります。

🔧 関連ツール

📚 関連記事