絵文字の文字数カウント|1 文字に見えて複数文字?仕組みを解説

約 7 分で読めます

SNS やメッセージアプリで絵文字を使ったとき、文字数が予想以上に増えた経験はありませんか。全角と半角の文字数の違いと同様に、絵文字の文字数カウントにも独特の仕組みがあります。Unicode や文字コードの入門書と合わせて読むと、理解がより深まります。

絵文字の歴史と Unicode バージョン別の推移

世界初の絵文字は 1999 年、NTT ドコモの栗田穣崇氏が i-mode 向けに作成した 176 種類のセットです。12×12 ピクセルのドット絵として誕生した絵文字は、当初は日本の携帯キャリア各社 (ドコモ・au・ソフトバンク) が独自に実装しており、キャリア間で絵文字が文字化けする問題が日常的に発生していました。この互換性問題を解決するため、Google と Apple が Unicode Consortium に絵文字の標準化を提案し、2010 年の Unicode 6.0 で 722 種類が正式採用されました。

以降、Unicode のメジャーバージョンごとに絵文字は増加を続けています。

Unicode バージョンリリース年追加された絵文字数累計絵文字数 (推定)
6.02010722722
7.02014250約 1,000
8.0201541約 1,050
9.0201672約 1,100
11.02018157約 1,600
13.02020117約 3,300
15.0202231約 3,600
16.020248約 3,790

近年は新規追加数が減少傾向にあります。Unicode Consortium は絵文字の提案に対して「既存の絵文字の組み合わせで表現できないか」を厳しく審査しており、ZWJ シーケンスによる合成で対応可能なケースは新規コードポイントの割り当てを見送る方針を取っています。提案から正式採用までには約 2 年のプロセスが必要で、使用頻度の予測データや他の絵文字との差別化の根拠を提出しなければなりません。

エンコーディング別のバイトサイズ実測データ

絵文字は Unicode の基本と文字コードに基づいてエンコードされますが、エンコーディング方式によってバイトサイズが大きく異なります。以下は代表的な絵文字の実測データです。

絵文字見た目コードポイント数UTF-8 バイト数UTF-16 バイト数UTF-32 バイト数
😀 (U+1F600)1 文字1444
👍🏻 (U+1F44D U+1F3FB)1 文字2888
👨‍👩‍👧‍👦1 文字7252228
🇯🇵 (U+1F1EF U+1F1F5)1 文字2888
1️⃣ (U+0031 U+FE0F U+20E3)1 文字37612
🏳️‍🌈1 文字4141216

UTF-8 では基本多言語面 (BMP) 外のコードポイントは 1 つあたり 4 バイトを消費します。UTF-16 ではサロゲートペア (2 つの 16 ビットユニット) で表現するため同じく 4 バイトです。UTF-32 は固定長で 1 コードポイント = 4 バイトのため計算は単純ですが、メモリ効率は最も悪くなります。家族絵文字 👨‍👩‍👧‍👦 の場合、UTF-8 で 25 バイトを消費する点は、データベースのカラムサイズ設計で特に注意が必要です。

ZWJ・Variation Selector・サロゲートペアの仕組み

Unicode では、複数のコードポイントを ZWJ (Zero Width Joiner: ゼロ幅接合子、U+200D) で結合して 1 つの絵文字を表現する仕組みがあります。家族の絵文字 👨‍👩‍👧‍👦 は「男性 (U+1F468) + ZWJ + 女性 (U+1F469) + ZWJ + 女の子 (U+1F467) + ZWJ + 男の子 (U+1F466)」の 7 コードポイントで構成されています。

この設計が採用された背景には、絵文字の組み合わせの爆発的な増加があります。肌色 5 種類 × 性別 × 職業を個別に登録すると数万種類が必要になりますが、ZWJ による合成方式なら基本パーツの組み合わせで表現できます。Unicode Consortium はこの方式により、コードポイントの枯渇を防ぎつつ多様性を実現しました。

ZWJ 以外にも、絵文字の表示を制御する不可視のコードポイントが存在します。

JavaScript の内部文字列表現は UTF-16 を採用しているため、BMP 外の絵文字 (U+10000 以上) はサロゲートペアとして 2 つの 16 ビットユニットで表現されます。これが "😀".length が 1 ではなく 2 を返す根本的な理由です。サロゲートペアの上位 (U+D800〜U+DBFF) と下位 (U+DC00〜U+DFFF) を分割してしまうと、不正な文字列が生成されるため、文字列の切り詰め処理では特に注意が必要です。

プラットフォーム別の絵文字カウントと表示差異

同じ絵文字でも、Apple・Google・Samsung・Microsoft の各プラットフォームでデザインが大きく異なる点にも注意が必要です。例えば 🔫 (ピストル) は Apple がおもちゃの水鉄砲デザインに変更した後、他社も追随しましたが、変更のタイミングにはばらつきがありました。マーケティングや UI デザインで絵文字の見た目に依存する場合は、主要プラットフォームでの表示を事前に確認することを推奨します。

プログラミング言語別の文字数カウントの違い

同じ絵文字でも、プログラミング言語によって length の返す値が異なります。これは各言語の内部文字列表現の違いに起因します。プログラミングの文字列処理に関する技術書でも、この違いは頻出のトピックです。

言語 / メソッド"😀""👍🏻""👨‍👩‍👧‍👦"カウント単位
JavaScript .length2411UTF-16 コードユニット
JavaScript [...str].length127Unicode コードポイント
Python 3 len()127Unicode コードポイント
Rust .len()4825UTF-8 バイト数
Rust .chars().count()127Unicode コードポイント
Swift .count111書記素クラスタ
Go len()4825UTF-8 バイト数
Java .length()2411UTF-16 コードユニット

Swift だけが書記素クラスタ単位でカウントするため、どの絵文字でも見た目どおり 1 を返します。JavaScript や Java は UTF-16 ベースのため、BMP 外の絵文字はサロゲートペアで 2 としてカウントされます。Rust と Go はバイト数を返すため、絵文字の文字数カウントには不向きです。開発者は自分が使う言語の length が何を返すのかを正確に把握しておく必要があります。

よくある失敗パターンと対策

開発者向け: 絵文字を正確にカウントする方法

  1. Intl.Segmenter による書記素クラスタ分割: ES2022 で導入された Intl.Segmenter は、書記素クラスタ (ユーザーが「1 文字」と認識する単位) で文字列を分割できます。[...new Intl.Segmenter().segment(str)].length で、絵文字を含む文字列の「見た目の文字数」を正確に取得できます。Node.js 16 以降、Chrome 87 以降、Safari 15.4 以降で利用可能です。
  2. 正規表現による絵文字検出と除去: Unicode プロパティエスケープ /\p{Emoji_Presentation}/u を使えば、文字列中の絵文字を検出・除去できます。ただし \p{Emoji} は数字 (0-9) や # なども含むため、絵文字のみを対象にする場合は \p{Emoji_Presentation} または \p{Extended_Pictographic} を使い分ける必要があります。
  3. テスト用の絵文字セット: 開発時のテストには、以下の 5 カテゴリを最低限カバーすると主要なエッジケースを網羅できます。(1) 基本絵文字 (😀)、(2) 肌色修飾子付き (👍🏻)、(3) ZWJ シーケンス (👨‍👩‍👧‍👦)、(4) 国旗 (🇯🇵)、(5) キーキャップシーケンス (1️⃣)。
  4. データベース設計のベストプラクティス: 絵文字を含むテキストを保存するカラムは、見た目の文字数ではなくバイト数ベースでサイズを設計します。MySQL では utf8mb4 を指定し、VARCHAR の長さは「想定最大文字数 × 4」を目安に設定します。絵文字を多用するチャットアプリなどでは TEXT 型の使用も検討してください。

まとめ

絵文字の文字数カウントは見た目ほど単純ではありません。文字数カウントスでは絵文字を含むテキストも正確にカウントできるので、SNS 投稿前の確認にご活用ください。