URL 编码 / 解码

免费在线 URL 编码与解码工具。将文本转换为 URL 编码格式,或将 URL 编码字符串解码回纯文本。基于 UTF-8,无乱码。所有处理均在浏览器本地完成。

编码 解码
解码错误

说明:

URL 编码不是"把特殊字符藏起来":它是 UTF-8 字节的百分号转写

很多人把 URL 编码理解成"把空格、中文、& 这类麻烦字符替换掉"。这个直觉只说对了一半。百分号编码(percent-encoding)的准确含义是:先把字符按约定的字符集(现代几乎都是 UTF-8)拆成字节,再把每个字节写成"%"加两位十六进制数。也就是说,%E4%BD%A0 不是"你"这个汉字本身的代号,而是汉字"你"在 UTF-8 下的三个字节 E4 BD A0 的另一种写法。

意识到"编码是对字节、而不是对字符"这一点,能解开绝大多数困惑:为什么同一个中文在不同系统里有时乱码(字符集不一致)、为什么把已经是 %XX 的字符串再编码一次会越编越长(双重编码)、为什么 emoji 占的字符特别多(四个字节)。本工具严格采用浏览器内置的 encodeURIComponent 加 decodeURIComponent,全程 UTF-8,因此不会出现字符集错配导致的乱码。关于前端处理这类参数的更多细节,可参考 URL 编码完整指南。

RFC 3986 字符分类表:哪些"不用编码"、哪些"必须编码"

RFC 3986(统一资源标识符通用语法)把 URI 允许出现的字符分成三大类。关键在于:未保留字符在任何位置都可原样出现、永远不需要编码;保留字符被保留作分隔符用途,只有在"它出现的位置与它的分隔角色冲突"时才必须编码;其余字符(空格、%、引号、尖括号、反斜杠、花括号等)一律必须编码。下表列出本工具(encodeURIComponent)对各类字符的实际处理——注意它比 RFC 更严格:几乎所有保留字符都会被编码,只留下未保留字符加上 !'()*-._~ 中那几个碰巧也被放行的子分隔符。

类别字符数量是否必须编码encodeURIComponent 行为
未保留 unreservedA-Z a-z 0-9 - . _ ~66不需要(原样即可)全部保留,不编码
保留·通用分隔符 gen-delims: / ? # [ ] @7在其分隔角色之外的位置必须编码全部编码(: 转为 %3A 等)
保留·子分隔符 sub-delims! $ & ' ( ) * + , ; =11作为"数据值"出现时建议编码除 ! ' ( ) * 外全部编码
其余(控制与标点)空格 % " < > \ ^ ` { | } 等其余必须编码全部编码(空格转为 %20,% 转为 %25)

注意:未保留字符即便被编码(例如把 a 写成 %61)在语义上仍然等价,服务器解码后会还原;但保留字符一旦被错误编码,可能破坏 URL 结构。所以"要不要在值里编码保留字符"取决于这个字符是"分隔符"还是"数据"。

path、query、fragment 的编码差异对照

同一个字符出现在 URL 的不同位置,能否原样出现完全不同。原因在 RFC 3986 的 ABNF:路径(path)允许 pchar(未保留加子分隔符加 : @ /),查询(query)和片段(fragment)允许的范围更宽(甚至允许 ?)。但语义上的"允许"不等于"安全"——比如在查询里 & 和 = 虽然语法上能原样写,却被当作参数分隔符;要把它们当数据传递就必须编码。下表同时给出本工具的输出作为对照:

字符path 路径query 查询fragment 片段encodeURIComponent说明
+可原样可原样可原样%2B在表单编码里 + 表示空格,作数据应编码
/可原样(段分隔)可原样可原样%2F路径里是层级分隔符
?必须编码可原样可原样%3F? 在路径里会误判为查询起点
&可原样可原样可原样%26查询里是参数分隔符,作数据须编码
=可原样可原样可原样%3D查询里是键值分隔符,作数据须编码
#必须编码必须编码可原样(片段起点)%23# 在任何数据位置都要编码
:可原样可原样可原样%3A常用于 scheme 与端口
@可原样可原样可原样%40用于 userinfo
[ ]必须编码必须编码必须编码%5B %5D仅 IPv6 字面量专用,数据须编码

注意:上面"可原样"是语法层面。把 URL 当作"完整地址"编码时应使用 encodeURI(保留结构字符);把"某个参数值"编码时才用本工具的 encodeURIComponent(把结构字符也编码掉)。混淆二者会让地址结构被破坏或让特殊字符漏网。配合 Base64 编解码 处理二进制或复杂文本时,先 Base64 再放进参数往往更省心。

UTF-8 多字节编码对照:中文 1 字 = 9 个 % 字符

百分号编码的长度取决于字符在 UTF-8 下占几个字节:每字节变成"%"加两位十六进制,所以字节数乘 3 等于 % 形式字符数。中文常用汉字落在基本多文种平面(BMP),每个占 3 字节;德文变音、部分欧洲字符落在 Latin-1 增补区,占 2 字节;emoji 落在辅助平面,占 4 字节。下表数值均由 Node 的 Buffer 按 UTF-8 实测:

样例字符数UTF-8 字节数% 形式长度编码结果(节选)
你139%E4%BD%A0
你好世界41236%E4%BD%A0%E5%A5%BD%E4%B8%96%E7%95%8C
ö(德文)126%C3%B6
ß(德文)126%C3%9F
あ(日文假名)139%E3%81%82
こんにちは(日文)51545%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF
U+1F600(emoji 笑脸)1412%F0%9F%98%80
U+1F680(emoji 火箭)1412%F0%9F%9A%80

注意:字节数由字符集决定。若某个旧系统用 GBK 而非 UTF-8,"你"会变成 2 个字节(%C4%E3)而非 3 个。本工具固定 UTF-8,因此与 RFC 3986 及 WHATWG URL Standard 的现代约定一致,也是避免乱码的关键。需要统计这类文本长度时,可用 字数统计 工具先看清字符与字节差异。

encodeURI 与 encodeURIComponent 与 escape 三者的对照

这是开发者最常踩坑的地方。三者的"不编码字符集"完全不同,而且 escape 早已被标准废弃(ES3 起不推荐,WHATWG 明确不鼓励)。关键差别:escape 不按 UTF-8 编码,而是对非 ASCII 字符输出 %uXXXX 形式的 UTF-16 码元,服务器端几乎无法正确解码,极易产生乱码。以测试串 https://example.com/p?q=1&x=a b+c#frag(ok)~*.中 为例:

函数不编码的字符集对"中"的处理是否 UTF-8 安全
encodeURI未保留加全部保留字符(除 % 等少数)%E4%B8%AD(正确)是
encodeURIComponent仅 未保留加 ! ' ( ) * - . _ ~%E4%B8%AD(正确)是(本工具采用)
escape@ * + - _ . / 及字母数字%u4E2D(非 UTF-8)否(已废弃)

同一测试串三者的输出(节选差异):

  • encodeURI 得到 https://example.com/p?q=1&x=a%20b+c#frag(ok)~*.%E4%B8%AD(保留了 : / ? # & = + ( ) ~ *,只编码了空格和中文)
  • encodeURIComponent 得到 https%3A%2F%2Fexample.com%2Fp%3Fq%3D1%26x%3Da%20b%2Bc%23frag(ok)~*.%E4%B8%AD(几乎全部编码,空格仍为 %20)
  • escape 得到 https%3A//example.com/p%3Fq%3D1%26x%3Da%20b+c%23frag%28ok%29%7E*.%u4E2D(中文变成 %u4E2D,斜杠与 + 未编码)

注意:新代码一律用 encodeURIComponent 编码"参数值",用 encodeURI 编码"完整 URL"。escape 仅在维护极老脚本时见到,新项目禁止使用。需要剥掉 HTML 标签再编码时,可先用 HTML 剥离 清理文本。

表单编码:空格为什么在表单里是 +,在路径里却是 %20

application/x-www-form-urlencoded 是 HTML 表单默认的提交格式。它的历史约定来自早期邮件与 CGI:空格被写成 +(加号),其余特殊字符仍走百分号编码。而 URL 路径(path)里的空格,按百分号编码规则直接写成 %20。两者都"合法",但语境不同:

场景空格的写法成因
URL 路径 / query 原始百分号编码%20RFC 3986 规定空格必须编码,标准写法是 %20
application/x-www-form-urlencoded 表单体+HTML 与 WHATWG 表单规范把 + 当作空格的简写

实测:对 a b c 调用 encodeURIComponent 得到 a%20b%20c;若再按表单规则把 %20 替换成 +,则得到 a+b+c。这正是很多"明明编码了空格却收到加号"怪象的来源——服务器端把表单体里的 + 还原成空格,而把路径里的 + 当成真正的加号。所以"我传的是空格还是加号"取决于它处在表单体还是 URL 里。

三个真实算例(与页面默认输入自洽)

以下三个算例均使用本工具采用的 encodeURIComponent 加 UTF-8 逻辑,所有输出均由脚本按页面同一公式实测,可直接用页面顶部输入框复现。

算例一:默认输入本身就是编码串——双重编码陷阱

页面默认输入框内容是 https://example.com/search?q=hello%20world。注意其中的 %20 已经是编码后的空格。若你"随手再编码一次"(比如把它当普通文本塞进另一个参数),% 本身会被编码成 %25,结果变成:

编码结果:https%3A%2F%2Fexample.com%2Fsearch%3Fq%3Dhello%2520world
双重编码:https%253A%252F%252Fexample.com%252Fsearch%253Fq%253Dhello%252520world

解码本工具的编码结果能完美还原原串(decodeURIComponent 可逆);但若把"双重编码"的结果直接当参数,服务端解码一次后得到的仍是 hello%20world 这个字面量,而不是空格——这就是经典的双重编码 bug。注意:只有当输入确实是"原始文本"时才编码;已经编码过的串不要重复编码。

算例二:中文"你好世界"的字节展开

输入 你好世界(4 个汉字),按 UTF-8 每个字 3 字节,编码为:

%E4%BD%A0%E5%A5%BD%E4%B8%96%E7%95%8C

共 12 字节、36 个 % 形式字符。解码回 你好世界,与原输入完全一致。这正是"中文在 URL 里占 9 个字符每字"说法的来源(3 字节乘每字节 3 个显示字符)。

算例三:带结构字符的值 price=9.9&q=a/b c#tag

输入 price=9.9&q=a/b c#tag,其中 & = / # 与空格都是结构字符。作为"单个参数值"编码后全部被转义:

price%3D9.9%26q%3Da%2Fb%20c%23tag

解码后还原为 price=9.9&q=a/b c#tag。如果这里不编码,服务器会把 &q= 当成新参数、把 #tag 当成片段,原始数据就被截断了。

补充问答(绑定本工具具体业务)

为什么默认的搜索词 hello%20world 不能再用本工具编码一次?
因为它已经是用百分号编码过的串,% 是普通字符。再编码会把 % 变成 %25,空格原本的 %20 变成 %2520,服务端只解码一次就会得到字面量 hello%20world 而非空格。只有"原始文本"才需要编码,已编码串应直接解码或使用。
路径、查询、片段里的同一个字符,编码规则为什么不一样?
因为 RFC 3986 给三个位置定义了不同的合法字符集:路径允许 pchar(含子分隔符与 : @ /),查询和片段允许范围更宽(甚至允许 ?)。但"语法允许"不意味"语义安全"——像 & = 在查询里是分隔符,要当数据传就必须编码。本工具用 encodeURIComponent 把结构字符也编码掉,适合编码"参数值"。
一个中文字符在 URL 里会变成几个百分号字符?
按 UTF-8,常用汉字占 3 字节,每字节写成 %XX,所以 1 个汉字等于 3 个 %XX 等于 9 个显示字符(如"你"转为 %E4%BD%A0)。德文变音等 Latin-1 字符占 2 字节(6 个字符),emoji 占 4 字节(12 个字符)。长度由字符集而非字符本身决定。
encodeURI 和 encodeURIComponent 到底什么时候该用哪个?
编码"完整 URL 地址"用 encodeURI(保留 : / ? # & = 等结构字符,不破坏地址);编码"某个参数值"用 encodeURIComponent(把结构字符也编码,防止特殊字符被误读)。本工具对应后者。escape 已废弃,切勿在新代码使用。
表单提交里空格为什么变成了加号,而路径里却是 %20?
application/x-www-form-urlencoded 表单体沿用早期约定把空格写成 +;URL 路径与查询的原始百分号编码把空格写成 %20。两者语境不同,服务端对表单体里的 + 还原为空格、对路径里的 + 当作真正的加号。所以"空格还是加号"取决于它处在表单体还是 URL 中。