文字数とバイト数はなぜ違うのか
公開日: · 最終更新日:
同じ文章を入れたのに、あるプログラムは5と表示し、別のプログラムは15と表示します。どちらかが間違っているのではなく、数えている単位が違うだけです。この記事では、テキストの長さを測る四つの単位と、いま見ている数字がどの単位なのかを見分ける方法を説明します。
数える単位は四つあります
- 文字:画面上で1文字に見える単位です。Unicode標準では書記素クラスタ(grapheme cluster)と呼びます。人が「何文字」と言うときの単位です。
- コードポイント:Unicodeが文字ごとに割り当てた番号です。
AはU+0041、€はU+20ACです。1文字が複数のコードポイントでできていることもあります。 - UTF-16コードユニット:JavaScriptをはじめ多くの環境が文字列を内部で保持するときの16ビット単位です。ほとんどのコードポイントは1個、U+10000以上は2個を使います。
- バイト:ファイルに保存したりネットワークで送ったりするときに実際に占める大きさです。どの文字コードを使うかで変わります。
| 文字 | 文字数 | コードポイント | UTF-16コードユニット | UTF-8バイト |
|---|---|---|---|---|
A |
1 | 1 | 1 | 1 |
é |
1 | 1 | 1 | 2 |
€ |
1 | 1 | 1 | 3 |
😀 |
1 | 1 | 2 | 4 |
UTF-8での文字の種類ごとのバイト数
UTF-8は、コードポイントの番号の範囲に応じて1〜4バイトを使います。
| コードポイントの範囲 | バイト | 該当する文字 | 例 |
|---|---|---|---|
| U+0000〜U+007F | 1 | 英字、数字、基本的な記号(ASCII) | A 7 ! |
| U+0080〜U+07FF | 2 | アクセント付きのラテン文字、ギリシャ文字、キリル文字など | é ß α Ж |
| U+0800〜U+FFFF | 3 | ハングル、漢字、かな、多くの記号 | 한 字 あ € |
| U+10000〜U+10FFFF | 4 | 絵文字の大部分、使用頻度の低い漢字など | 😀 |
そのため、文字数が同じでも文字の種類によってバイト数が変わります。
| 文字列 | 文字数 | UTF-8バイト | 計算 |
|---|---|---|---|
Hello |
5 | 5 | 5 × 1 |
café |
4 | 5 | 3 × 1 + 2 |
Größe |
5 | 7 | 3 × 1 + 2 × 2 |
Привет |
6 | 12 | 6 × 2 |
안녕하세요 |
5 | 15 | 5 × 3 |
こんにちは |
5 | 15 | 5 × 3 |
Hi 😀 |
4 | 7 | 3 × 1 + 4 |
1文字に見える絵文字が何バイトにもなる理由
絵文字の中には、複数のコードポイントをつなげて一つの絵として表示するものがあります。
| 絵文字 | 文字数 | コードポイント | UTF-16コードユニット | UTF-8バイト | 構成 |
|---|---|---|---|---|---|
👍🏽 |
1 | 2 | 4 | 8 | 親指 U+1F44D + 肌の色 U+1F3FD |
🇺🇳 |
1 | 2 | 4 | 8 | 地域指示記号 U+1F1FA + U+1F1F3 |
👨👩👧 |
1 | 5 | 8 | 18 | 人物3人 + その間をつなぐZWJ 2個 |
家族の絵文字は、👨 👩 👧 の三つの絵文字の間にゼロ幅接合子(ZWJ、U+200D)を挟んで作られています。絵文字一つが4バイト、ZWJが3バイトなので、3 × 4 + 2 × 3 = 18バイトです。文字単位で数えるプログラムは1を、コードポイントで数えるプログラムは5を、UTF-16コードユニットで数えるプログラムは8を表示します。
絵文字だけの話ではありません。é は一つのコードポイント(U+00E9、2バイト)でも、e の後ろに結合アクセント記号(U+0301)を付けた2つのコードポイント(3バイト)でも書けます。画面では同じに見えても、バイト数は違います。
同じ文字でも文字コードによってバイト数が違う理由
バイト数を決めるのは文字ではなく文字コードです。UTF-8が広く使われるようになる前は、言語圏ごとに別々の文字コードが使われ、それぞれが自分の言語の文字を2バイト以下に収めていました。
| 文字 | UTF-8 | UTF-16 | 従来の文字コード |
|---|---|---|---|
A |
1 | 2 | 1 |
é |
2 | 2 | 1(windows-1252) |
한 |
3 | 2 | 2(EUC-KR) |
あ |
3 | 2 | 2(Shift_JIS) |
字 |
3 | 2 | 2(EUC-KR、Shift_JIS) |
😀 |
4 | 4 | 表現できません |
同じ文字について「2バイト」という説明と「3バイト」という説明の両方を見かけるのは、このためです。前者はEUC-KRやShift_JISのような従来の文字コード、後者はUTF-8を基準にしています。한글 abc はUTF-8で10バイト、EUC-KRで8バイトで、Tokyo 東京 はUTF-8で12バイト、Shift_JISで10バイトです。最近作られたシステムのほとんどはUTF-8を使いますが、古いシステムやそのルールを引き継いだフォームは、いまも2バイト基準で数えます。
実務では何を数えるのか
制限がある場所ごとに、数える単位が違います。数字を合わせる前に、何を数えているのかを確かめる必要があります。
- データベースのカラム:長さが文字数の製品もあれば、バイト数の製品もあります。PostgreSQLの
varchar(n)は、nがバイト数ではなく文字数であるとドキュメントに明記されています。使っている製品と設定のドキュメントを確認してください。 - Webの入力欄:HTMLの
maxlength属性とJavaScriptのlengthは、UTF-16コードユニットで数えます。maxlengthが10の入力欄には、😀は5個までしか入りません。 - URLとBase64:どちらもUTF-8のバイトを変換するので、結果の長さは文字数ではなくバイト数に従います。
€の1文字は、URLエンコードでは%E2%82%AC(9文字)、Base64では4oKs(4文字)になります。 - テキストメッセージやバイト制限のあるフォーム:内部で使う文字コードによって、一通に収まる量が変わります。案内に書かれた単位が文字なのかバイトなのかを確認してください。
単位が書かれていない場合は、文字数表示のある入力欄に文字を一つずつ入れて、数字がどれだけ増えるかを見れば分かります。
A / 字 / 😀 を入れたときに増える数 |
数えている単位 |
|---|---|
| 1 / 1 / 1 | 文字またはコードポイント |
| 1 / 1 / 2 | UTF-16コードユニット |
| 1 / 3 / 4 | UTF-8バイト |
| 1 / 2 / それ以外の値 | 2バイト基準の従来方式 |
文字とコードポイントは 👍🏽 で見分けます。1増えれば文字、2増えればコードポイントです。
自分で数えてみる方法
ブラウザの開発者ツールのコンソールに下のコードを貼り付けると、四つの値を自分で確かめられます。変えるのは最初の行の文字列だけです。
const s = '👨👩👧';
[...new Intl.Segmenter().segment(s)].length; // 1: 文字
[...s].length; // 5: コードポイント
s.length; // 8: UTF-16コードユニット
new TextEncoder().encode(s).length; // 18: UTF-8バイト
コードを使わずに確かめるには、下の関連ツールに文章を貼り付けてください。文字数、UTF-8のバイト数、2バイト基準のバイト数がまとめて表示されます。
参考資料
- RFC 3629: UTF-8, a transformation format of ISO 10646 — (英語)
- Unicode Standard Annex #29: Unicode Text Segmentation — (英語)
- Unicode Technical Standard #51: Unicode Emoji — (英語)
- String: length – MDN Web Docs — (英語)
- Intl.Segmenter – MDN Web Docs — (英語)
- HTML attribute maxlength – MDN Web Docs — (英語)
- Encoding Standard – WHATWG — (英語)
- PostgreSQL Documentation: Character Types — (英語)