コードポイントは、Unicodeがそれぞれの文字に割り当てた一意の番号です。あ なら U+3042。この番号を実際のバイト列に変換する規則が UTF-8 や UTF-16 で、両者は別の概念です。本記事ではコードポイントの意味、UTF-8で1〜4バイトになる仕組み、絵文字がサロゲートペアになる理由を、実際に確かめる方法つきで整理します。
1. コードポイントとは
Unicodeは世界中の文字に通し番号を振った巨大な文字集合です。この各文字に対応する番号がコードポイントで、慣習として U+ のあとに16進数で書きます。
A= U+0041(10進で 65)あ= U+3042(10進で 12354)漢= U+6F22😀= U+1F600(10進で 128512)
ここまでは「番号を決めた」だけで、コンピュータに保存する形(バイト)はまだ決まっていません。それを担うのが符号化方式です。
2. UTF-8 — 1〜4バイトへの符号化
UTF-8は、コードポイントの大きさに応じて1〜4バイトで表す方式です。ASCIIと互換で、Webでもっとも広く使われています。
| 範囲 | UTF-8のバイト数 | 例 |
|---|---|---|
| U+0000〜U+007F | 1バイト | A → 41 |
| U+0080〜U+07FF | 2バイト | é → C3 A9 |
| U+0800〜U+FFFF | 3バイト | あ → E3 81 82 |
| U+10000〜U+10FFFF | 4バイト | 😀 → F0 9F 98 80 |
つまり「日本語1文字は3バイト」というのは、多くの文字がこの3バイト範囲に入るためです。文字数とバイト数が一致しないのはこのためで、データベースの桁数設計などで意識する必要があります。
3. UTF-16 と \uXXXX
UTF-16は16ビット(2バイト)単位で表す方式で、JavaScriptの文字列が内部で採用しています。U+FFFF以下は1単位、それを超える文字はサロゲートペアという2単位で表します。ソースコードやJSON文字列でよく見る \uXXXX は、このUTF-16の1単位を16進で書いたものです。
あ(U+3042)→あ(1単位)😀(U+1F600)→😀(上位・下位の2単位)
4. 絵文字がサロゲートペアになる理由
UTF-16は元々16ビットで表せる範囲(U+FFFFまで)しか想定していませんでした。それを超える文字を表すため、予約された範囲(D800〜DFFF)の2単位を組み合わせて1つのコードポイントを表す仕組みがサロゲートペアです。😀 は上位 D83D・下位 DE00 のペアで表されます。
この結果、JavaScriptの "😀".length は 2 になります。コードポイントは1つでも、UTF-16単位では2つだからです。
5. 実際に試せる方法(JavaScript)
コンソールやNode.jsで、コードポイント・UTF-8バイト・UTF-16単位をそれぞれ確認できます。
const ch = "😀";
// コードポイント(U+)
console.log(ch.codePointAt(0).toString(16).toUpperCase()); // 1F600
// UTF-8 バイト列
console.log([...new TextEncoder().encode(ch)]
.map(b => b.toString(16).toUpperCase())); // ["F0","9F","98","80"]
// UTF-16 単位(\uXXXX)
console.log(ch.length); // 2(サロゲートペア)
console.log([...ch].length); // 1(コードポイント単位で数える)
複数の文字をまとめて表で確認したいときは、下記のコードポイント確認ツールに文字を貼り付けてください。1文字ごとに U+・10進・UTF-8・UTF-16 を一覧表示します。
Free Tool コードポイント確認ツールで調べる 文字を入力すると、各文字の U+ コードポイント・10進・UTF-8バイト・UTF-16(\uXXXX)を一覧表示します。すべてブラウザ内で完結します。よくある質問(FAQ)
コードポイントと文字コードは同じ意味ですか?
厳密には違います。コードポイントはUnicodeが各文字に割り当てた抽象的な番号(例:あ は U+3042)で、それを実際のバイト列に変換する規則がUTF-8やUTF-16といった符号化方式です。日常会話ではまとめて文字コードと呼ぶことも多いですが、番号そのものと、バイトへの符号化方式は分けて考えると理解しやすくなります。
1文字が何バイトになるかはどう決まりますか?
符号化方式とコードポイントの大きさで決まります。UTF-8ではU+0000〜U+007F(ASCII)が1バイト、日本語の多くが3バイト、絵文字などが4バイトです。UTF-16ではU+FFFF以下が2バイト、それを超える文字は4バイト(サロゲートペア)になります。同じ文字でも方式が違えばバイト数は変わります。
JavaScriptのlengthで絵文字が2と数えられるのはなぜですか?
JavaScriptの文字列はUTF-16で管理され、lengthは16ビット単位の個数を返すためです。U+FFFFを超える絵文字はサロゲートペアで2単位になるので、length は2になります。コードポイント単位で数えたいときは、for...of や Array.from、[...str] を使うと1文字として扱えます。