URL エンコード対象文字数

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

URL エンコード対象文字数

このレッスンで分かること

  • 文字列を for (let i = 0; i < s.length; i++) で回すと、絵文字2 文字として扱われる ことがあります
  • これらに当てはまる文字は 安全 で、当てはまらない文字は エンコード対象 として 1 加算します
  • ブラウザのアドレスバーに ?q=hello world&lang=ja のような URL を貼ると、半角スペースなどが %20 にエンコードされます

URL エンコード対象文字数 とは

URL に直接書けない文字 (%XX エスケープが必要な文字) を数える関数を実装します。本レッスンでは、URL エンコード対象文字数 の基本から実際の使いどころまでを整理し、現場で迷わず使える形に落とし込みます。

ブラウザのアドレスバーに ?q=hello world&lang=ja のような URL を貼ると、半角スペースが %20 (または +) に置き換わります。これが URL エンコード (パーセントエンコーディング) です。本レッスンでは、文字列の中に「URL エンコードが必要な文字」が何個あるかを数える関数を作ります。

URL でそのまま使える文字

RFC 3986 では、URL の中でエスケープせずに置ける文字を unreserved 文字 と呼びます。具体的には次の通りです。

  • 英字 A-Z a-z
  • 数字 0-9
  • 4 種類の記号 - _ . ~

それ以外の文字 (空白、日本語、? & # / = + などの予約記号) は、UTF-8 でバイト列に変換した上で %XX の形にエスケープする必要があります。たとえば半角スペースは %20#%23 は UTF-8 で e3 81 82 の 3 バイトなので %E3%81%82 の 9 文字に化けます。

URL の pathquery でエスケープのルールが微妙に違います。今回は単純化のため「unreserved 以外はすべてエンコード対象」とします。

数える対象

ここでは「URL エンコード後に %XX が何個現れるか」ではなく、「入力文字列の中の何文字がエンコード対象か」を数えます。日本語 1 文字 ( など) は、入力文字列としては 1 文字ですが、エンコード対象として 1 つカウントします。

つまり判定はシンプルで、各文字について次の条件を満たすかどうかを見るだけです。

  • A-Z のいずれか
  • a-z のいずれか
  • 0-9 のいずれか
  • - _ . ~ のいずれか

これらに当てはまる文字は 安全 で、当てはまらない文字は エンコード対象 として 1 加算します。O(n) の単純な走査で答えが出ます。

diagram (will load when visible)

Python 実装

Python

def urlEncodeCount(s): safe = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~") return sum(1 for ch in s if ch not in safe)

セット safe に「そのまま使える文字」を全部入れておき、s の各文字が safe に含まれないかを判定します。in の判定は setO(1) です。

JavaScript 実装

JavaScript

function urlEncodeCount(s) { let count = 0; for (const ch of s) { if (!/[A-Za-z0-9\-_.~]/.test(ch)) count++; } return count; }

正規表現で unreserved の集合を表現し、当てはまらないものを数えます。for...of は文字列を Unicode コードポイント単位で回るので、😀 のような U+10000 以上の文字(サロゲートペア)にも対応できます。

文字列を for (let i = 0; i < s.length; i++) で回すと、😀 のような U+10000 以上の文字が 2 文字として扱われる ことがあります。コードポイント単位なら for...of が安全です。

具体例

  • "hello"0 (全部 ASCII 英字)
  • "hello world"1 (スペースだけ対象)
  • "a+b=c"2 (+=)
  • "こんにちは"5 (日本語はすべて対象)
  • "" (空文字列) → 0
  • "a-b_c.d~e"0 (- _ . ~ は安全)

注意: unreserved のうち、コードでは「safe セット」と呼んでいます。エスケープ不要という意味で 安全文字 と覚えると忘れにくいです。

よくある間違い

  • / ? & を「予約文字だから安全」と思い込んで除外してしまう (URL の path や query 区切り記号なので、 の中では必ずエスケープが必要)
  • 日本語をバイト単位で数えてしまい、 で 3 を返してしまう (今回は 文字単位 で 1)
  • . を「特殊記号だから対象」と勘違いする (. は unreserved の 1 つで対象外)
  • space+ にするか %20 にするかでサイズ計算がずれる
  • 大文字小文字を区別せず、A を unreserved 外と判定してしまう

やってみよう

関数 urlEncodeCount(s) を実装してください。s は任意の文字列で、URL エンコードが必要な文字の数を整数で返します。空文字列のときは 0 を返します。実装した関数を "https://example.com/?q=ねこ" のような文字列に適用すると、: が 1 個、/ が 3 個、? = と日本語 2 文字の合計 8 が返るはずです。URL を解析するライブラリは内部でこの数え方を使ってバッファサイズを見積もっています。

よくある質問

Q. このトピックを覚える意味は何ですか?

A. URLに直接書けない文字(予約文字・非ASCII文字)が何個あるかを正確に数えられると、パーセントエンコード後の文字列長やバッファサイズを事前に見積もれます。ブラウザやHTTPクライアントが内部で行っているエンコード処理の仕組みを理解する土台になります。

Q. 実務でこの知識を使う場面は?

A. クエリパラメータを組み立てるとき、URLの最大長制限を考慮するとき、ログやリダイレクトURLに日本語や記号を含む値を渡すときなど、URLエンコードの挙動を正しく把握しておく必要がある場面全般で登場します。

Q. 他の章とどう繋がりますか?

A. 前の章で扱った文字コード・バイト数の考え方が、日本語などのマルチバイト文字をエンコード対象として数える処理に直結します。次のIPv4アドレス変換のレッスンでも、文字列やビット列を1文字・1バイトずつ精査する視点を引き続き使います。

次のレッスン

次は IPv4 アドレスを 32bit 整数に変換 に進みましょう。

事前確認 — 進む前に次の 3 つができることを確認しましょう。

  1. URL エンコード対象数 の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. URL エンコード対象数 とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン

要件

  1. A-Z a-z 0-9 - _ . ~ を安全文字 (unreserved) とみなす
  2. それ以外の文字をエンコード対象として数え、整数を返す
  3. 日本語などの非 ASCII 文字は 1 文字として数える

入出力例

test-cases.txt

urlEncodeCount("")0 urlEncodeCount("hello")0 urlEncodeCount("hello world")1 urlEncodeCount("a+b=c")2 urlEncodeCount("こんにちは")5 urlEncodeCount("a-b_c.d~e")0 urlEncodeCount("1+1=2")2 urlEncodeCount("page?q=hi#top")3

ヒント

main.py
main.py
学習モード

メモ

URL エンコード対象文字数

⌘S で保存