URL编码

把文字或整条链接转成 %XX 百分号编码,四种口径互相对照,并逐字节拆解 UTF-8 来源。

#字符码点UTF-8 字节当前口径 %XX

提示:切换「编码口径」会立刻按新口径重算,不需要再点按钮。默认示例 40 个码点,按 encodeURI 得到 74 个字符;改用 encodeURIComponent 会得到 90 个字符,因为 : / ? = & 这五个分隔符也会被一起编码。

使用方法

  1. 在输入框里粘贴要处理的文字或整条链接。页面打开时已经预置示例 https://liuquan.net/search?q=柳泉 工具&n=100,它是 40 个码点、48 个 UTF-8 字节。
  2. 先选编码口径,四档分别对应 encodeURIComponent(编单个参数值)、encodeURI(编整条 URL)、表单序列化(application/x-www-form-urlencoded,空格输出 +)和遗留函数 escape(汉字输出 %u67F3)。默认停在「整条 URL · encodeURI」,与示例的形态吻合。
  3. 点「编码」得到百分号编码结果,点「解码」把 %E6%9F%B3 这类序列还原回字符。输入框里按 Ctrl + Enter(macOS 用 Cmd + Enter)等同于点「编码」——Enter 本身留给换行,分行粘贴多条链接时不会误触发。
  4. 下面的逐字节明细表把每个字符拆到码点和 UTF-8 字节,再给出它在当前口径下的 %XX。表默认显示 12 行,「加载更多」每次再放 12 行;「复制对照表」以 TSV 进剪贴板,「导出 CSV」存成带 BOM 的文件,Excel 打开中文不乱码。
  5. 「结果 → 输入」把结果搬回输入框,换一档口径再编一次,两次结果并排就能看出差异;「载入示例」随时还原那条默认示例。

这个 URL编码 工具能解决什么问题

一是拼出能用的带参链接。搜索、跳转、分享链接里带中文搜索词时,直接把「柳泉 工具」写进 query 会在部分客户端被截断或乱码,编成 %E6%9F%B3%E6%B3%89%20%E5%B7%A5%E5%85%B7 就稳了。

二是定位「参数被吞掉」的事故。用户的搜索词里打了一个 &,服务端却把后半段当成新参数——这是因为参数值没有编码。把值丢进来选「参数值 · encodeURIComponent」,& 会变成 %26,隐患立刻可见。

三是分清 %20 与 + 该用哪个。同一个空格,写在 path 段里必须是 %20,写进表单序列化的 body 才是 +;本页把两者的差别做成了可选口径,切换一次就能对照。

四是把日志和埋点里的一长串还原成人话。埋点上报的落地页常常整条都被编码过,粘进来点「解码」即可读。

本页不做这些事:不把 URL 拆成协议、主机、路径、查询参数,那属于「URL解析」;不做 Punycode 域名转码;不检查链接是否可访问。要拆解查询参数请用「Query字符串解析」,要把国际域名转成 xn-- 形式请用「Punycode转换器」。

两种编码口径的原理:RFC 3986 与 UTF-8 逐字节

URL编码的全过程只有两步:先按字符编码把字符折成字节,再对每个需要转义的字节写两位十六进制。RFC 3986《Uniform Resource Identifier (URI): Generic Syntax》第 2.1 节把百分号编码的语法定为 pct-encoded = "%" HEXDIG HEXDIG,也就是一个百分号紧跟两位十六进制数;RFC 5234 的 HEXDIG 只包含大写 A–F,所以规范输出是大写,本页与浏览器一致,一律输出大写。这里最关键的一点是:百分号编码作用在字节上,不作用在字符上——先把字符编成字节序列,再对每个需要转义的字节写 %XX。

RFC 3986 把字符分成三类。未保留字符(unreserved)有 66 个:26 个大写字母、26 个小写字母、10 个数字,再加 - . _ ~,它们永远不需要编码。保留字符(reserved)有 18 个,分两组:gen-delims 是 : / ? # [ ] @ 共 7 个,sub-delims 是 ! $ & ' ( ) * + , ; = 共 11 个;它们在 URI 里有语法作用,所以「看场合决定编不编」。空格既不在未保留也不在保留里,任何位置都必须编码。

浏览器实际给你的是两个函数。encodeURIComponent 原样保留 71 个字符——66 个 unreserved,外加 ! ' ( ) * 这 5 个;这 5 个来自更早的 RFC 2396,当年被定义为 mark 集合,浏览器为了兼容一直没改,所以它们在 RFC 3986 里虽然属于 sub-delims 却仍然不被编码。encodeURI 保留 82 个,比前者多出 # $ & + , / : ; = ? @ 这 11 个,正好是「除了 [ ] 之外的保留字符」。

表单口径走的是另一条规范。HTML 标准定义的 application/x-www-form-urlencoded 序列化算法把空格写成 +,它的安全集只有 A–Z a–z 0–9 * - . _ 共 66 个——比 encodeURIComponent 少了 ! ' ( ) ~ 这 5 个。也就是说 URLSearchParams 会把 ~ 编成 %7E、把 ' 编成 %27,而 encodeURIComponent 不会。这也解释了为什么解码时 + 有两种含义:decodeURIComponent('+') 返回的是加号本身,只有表单口径才把它当空格。

UTF-8 把字符折叠成字节。汉字柳的码点是 U+67F3,按 RFC 3629 编成 3 字节 E6 9F B3,于是编码结果是 %E6%9F%B3——一个汉字从 1 个码点膨胀成 9 个 ASCII 字符。字节数的分档是固定的:U+0000–U+007F 占 1 字节,U+0080–U+07FF 占 2 字节(如 © → C2 A9),U+0800–U+FFFF 占 3 字节,U+10000–U+10FFFF 占 4 字节(如 😀 U+1F600 → F0 9F 98 80,编码后 12 个字符)。三字节的首字节形如 1110xxxx,两个续字节都是 10xxxxxx,取值时首字节用 & 0x0F、续字节用 & 0x3F 取出低位再拼接——这正是明细表里每个 %XX 的来源。

完整示例(数值仅作演示)

示例条件与页面默认值完全一致:输入 https://liuquan.net/search?q=柳泉 工具&n=100,口径选「整条 URL · encodeURI」。这串文字共 40 个码点、48 个 UTF-8 字节,编码后 74 个字符。

把结果拆开正好对上 74:28(未保留 ASCII)+ 7(保留 ASCII)+ 36(四个汉字)+ 3(空格的 %20)= 74。同一串文字换口径的结果如下:

下面就是默认口径(encodeURI)的真实输出,也是打开页面时结果框里显示的内容:

https://liuquan.net/search?q=%E6%9F%B3%E6%B3%89%20%E5%B7%A5%E5%85%B7&n=100

字符分类与四种口径对照表

下表按 RFC 3986 的分类列出典型字符,四种口径的输出来自浏览器原生函数实测。看这张表比记规则快:同一列里出现原样字符越多,说明这一档保留得越宽;+ 只出现在表单那一列,是它唯一独有的写法。

字符码点RFC 3986 分类encodeURIComponentencodeURI表单口径escape
(空格)U+0020三类之外,必须编码%20%20+%20
:U+003Agen-delims%3A:%3A%3A
/U+002Fgen-delims%2F/%2F/
?U+003Fgen-delims%3F?%3F%3F
#U+0023gen-delims%23#%23%23
[U+005Bgen-delims(IPv6 用)%5B%5B%5B%5B
@U+0040gen-delims%40@%40@
&U+0026sub-delims%26&%26%26
=U+003Dsub-delims%3D=%3D%3D
+U+002Bsub-delims%2B+%2B+
!U+0021sub-delims(RFC 2396 遗留 safe)!!%21%21
'U+0027sub-delims(RFC 2396 遗留 safe)''%27%27
~U+007Eunreserved~~%7E~
-U+002Dunreserved----
柳U+67F3非 ASCII,UTF-8 3 字节%E6%9F%B3%E6%9F%B3%E6%9F%B3%u67F3
😀U+1F600非 ASCII,UTF-8 4 字节%F0%9F%98%80%F0%9F%98%80%F0%9F%98%80%uD83D%uDE00
%U+0025百分号本身%25%25%25%25

换算出字符个数也很快:柳 是 1 个码点变 3 个字节变 9 个字符,😀 是 1 个码点变 4 个字节变 12 个字符。这也是为什么一条全中文的链接编码后长度常常是原文的 3 倍,而表情会到 3 倍以上——😀 在 UTF-16 里本来就是 2 个码元,按 String.length 算是原文 2 个字符、编码后 12 个字符,正好 6 倍。把这层关系记住,URL编码结果有多长就能口算了:未保留字符按 1 个算,汉字按 9 个算,表情按 12 个算。

输入范围、边界与精度说明

常见问题

同一个空格,到底该编成 %20 还是 +?

看这个空格出现在哪一层。RFC 3986 的 URI 里只有 %20 是合法的百分号编码,path 段、fragment 里都必须用 %20。而 application/x-www-form-urlencoded 是表单提交时的序列化格式,它规定空格写成 +,所以表单 body 和 URLSearchParams 输出的是 +。query 段两者都能见到:如果服务端按表单规则解析,+ 就是空格;如果按 RFC 3986 解析,+ 是 sub-delims 里的字面加号。

为什么用 encodeURI 编码参数值会把接口调用弄坏?

因为 encodeURI 保留了 82 个字符,其中包含 & = + ? #。当参数值本身含 & 时,它不会被编码,服务端就会把后半段当成另一个参数——这就是参数注入。参数值请一律用 encodeURIComponent,它会把 & 编成 %26、= 编成 %3D。

encodeURIComponent 到底保留哪些字符?

一共 71 个:26 个大写字母、26 个小写字母、10 个数字、- . _ ~,再加 ! ' ( ) *。前 66 个是 RFC 3986 的 unreserved,后 5 个是 RFC 2396 时代留下的 mark 集合,放进 URL 本身没问题,但若服务端对单引号做转义处理(例如拼进 SQL),仍建议再手工把 ' 换成 %27。

为什么结果里出现了 %25,我明明只编了一次?

因为原文里就有字面量百分号。% 不是未保留字符,必须编成 %25,所以 100% 变成 100%25。如果结果是 %2520 而不是 %20,那才是编了两遍;把 %25 再编一次会得到 %2525,多出的每一层都是一个 25。

用 escape 得到 %u67F3,为什么别的系统解不开?

因为 %uXXXX 不是 RFC 3986 定义的百分号编码。它是 ECMAScript 早期为 UCS-2 设计的遗留写法,正式规范里只在 Annex B.2.1 里描述,被认为应当废弃;只有 unescape() 与少数老系统认得。标准做法是把字符按 UTF-8 编成字节再写 %XX,也就是本页其余三档的输出。新代码不要再用 escape。

编码会不会因为语言环境不同而结果不一样?

不会。RFC 3986 只规定字节级转义,具体把字符折成哪些字节由字符编码决定;现代 Web 场景统一用 UTF-8(RFC 3629),所以「中」在任何浏览器、任何系统上都是 %E4%B8%AD。早期有人用 GBK 编中文得到 %D6%D0,那是非标准做法,现在遇到这种串需要先按 GBK 解码再按 UTF-8 重编。

明细表的「UTF-8 字节」和「当前口径 %XX」为什么有的行一样、有的不一样?

一样说明这个字节必须编码。不一样的情况有两种:一是该字符是未保留字符(如 a、-),字节照给但 %XX 一列显示原字符;二是选了 escape,汉字那一行会显示 %u67F3 而不是三组 %XX,因为 escape 不按字节走而是按 UTF-16 码元走。

我输入的文本会被发到服务器吗?

不会。编码与解码分别调用浏览器内建的 encodeURIComponent / decodeURIComponent 与页面内那份 UTF-8 编码函数,全部在当前标签页完成,没有任何网络请求,也不写浏览器存储。想验证可以打开开发者工具的 Network 面板,点几次「编码」观察是否有新请求。

相关工具