UTF-8 バイトカウンター | VARCHAR と String.length の食い違いを 1 画面で照合
テキストを貼り付けると UTF-8 のバイト数、Unicode のコードポイント数 (文字数)、UTF-16 のコード単位数 (= JavaScript の string.length)、行数を同時に表示する。ASCII / 拡張ラテン / BMP CJK / 絵文字の 4 区分でバイト内訳まで見えるので、データベースのバイト制限と JavaScript の文字数が食い違う原因をその場で切り分けできる。
💡 このツールについて
「MySQL の VARCHAR(255) に収まると思った文字列が Data too long で蹴られる」「JavaScript で str.length を見ても DB のバイト見積もりが合わない」── 日本語アプリで定番の罠は、見た目の文字数とストレージ上のバイト数が一致しないことに起因する。
UTF-8 は ASCII を 1 バイト、拡張ラテン (À〜ÿ など) を 2 バイト、ひらがな・カタカナ・常用漢字を含む BMP CJK を 3 バイト、絵文字や JIS 第三・第四水準漢字を 4 バイトで表現する可変長エンコーディングである。一方 JavaScript の文字列は UTF-16 を内部表現にしているため、BMP 内の CJK は 1 単位、絵文字や補助文字面の漢字はサロゲートペアで 2 単位として数える。バイト制限と「.length」が一致しないのはこの仕様差が原因である。
このツールは 4 メトリックを同時表示するだけでなく、文字 1 つ 1 つを < 0x80 / < 0x800 / < 0x10000 / それ以上の境界で振り分けて 4 区分の内訳を出す。「ASCII 100 文字より日本語 100 文字が 3 倍重い」「絵文字 1 つ追加で 4 バイト + UTF-16 単位 2 つ」という関係が数字で読み取れるので、Twitter (X) の 280 字制限や MySQL VARCHAR のバイト上限を実データで詰めるときに役立つ。
🧐 よくある質問
Q. MySQL の VARCHAR(255) は 255 文字ですか 255 バイトですか?
A. MySQL 5.0.3 以降は文字数指定だが、行サイズ上限は 65,535 バイトで計算される。文字セットが utf8mb4 の場合、VARCHAR(N) は最悪 N×4 バイトを占めるとして上限が計算されるので、VARCHAR(16383) 1 列で行上限に到達する。バイト境界を把握したいときは本ツールの「UTF-8 バイト」を直接見るのが早い。
Q. '😀'.length が 2 になるのはなぜですか?
A. JavaScript の String.prototype.length は UTF-16 の code units を返す。U+1F600 (😀) は BMP 外なので、'😀' のサロゲートペア 2 単位で格納される。コードポイントを数えたいときは [...str].length か Array.from(str).length、本ツールでは「文字数」欄を参照する。
Q. utf8 と utf8mb4 で何が違いますか?
A. MySQL の utf8 は 3 バイトまでしか格納しないレガシー実装で、絵文字や一部漢字 (𠮷など) が ? に化けて保存される。utf8mb4 は RFC 3629 準拠の 4 バイト UTF-8 で、本ツールの「4 バイト」内訳に出る文字を扱える。新規プロジェクトは utf8mb4 一択である。
Q. なぜ「文字数」と「UTF-16 単位」が違うのですか?
A. 「文字数」はコードポイント数 (for..of の反復回数) で、絵文字 1 つは 1 として数える。「UTF-16 単位」は string.length 相当で、BMP 外のコードポイントをサロゲートペアの 2 単位で数える。Twitter の文字数判定や React の key 重複、Intl.Segmenter による grapheme クラスタ計測など、各 API がどちらの基準を採用するかを知っておくと事故が減る。
Q. 改行コードはどう数えますか?
A. LF (\n)・CRLF (\r\n)・CR (\r) のいずれも 1 行区切りとして同一視する。空文字列は 0 行、それ以外は区切りの数 + 1 で返す。CRLF はバイト数では 2 バイト、文字数では 2 として数える。
📚 豆知識
- CJK 3 バイトの内訳: U+4E00〜U+9FFF (CJK 統合漢字)、U+3040〜U+309F (ひらがな)、U+30A0〜U+30FF (カタカナ)、U+AC00〜U+D7A3 (ハングル音節) はすべて BMP 内の 3 バイト領域に収まる。一方 𠮷野家の「𠮷」(U+20BB7) は SIP (補助漢字面) で 4 バイト扱いになる。日本人の名前で 4 バイト文字を含むケースは想像より多く、
utf8mb4必須の理由になっている。 - Twitter の 140 / 280 の根拠: 2006 年の SMS の 160 文字上限 (GSM-7 で 140 オクテット) を踏襲していたのが 140 字時代の名残。2017 年に英語圏を 280 字、CJK 圏を 140 字に据え置いたのは「日本語は 1 文字あたり情報密度が約 2 倍」という統計に基づく公式判断で、UTF-8 で見ると日本語 140 字 ≒ 英語 280 字 ≒ 420〜500 バイトと、おおむねバイト数で揃う設計になっている。
- emoji 4 バイト + ZWJ シーケンス: 👨👩👧👦 (家族) のような ZWJ (U+200D) で連結された絵文字は、見た目 1 文字でも UTF-8 で 25 バイト・UTF-16 単位 11 個・コードポイント 7 個になる。本ツールで貼り付けて数えてみると Unicode の「文字」概念がいかに複数レイヤーに分かれているか直感的に分かる。