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 行为 |
|---|---|---|---|---|
| 未保留 unreserved | A-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 字节数 | % 形式长度 | 编码结果(节选) |
|---|---|---|---|---|
| 你 | 1 | 3 | 9 | %E4%BD%A0 |
| 你好世界 | 4 | 12 | 36 | %E4%BD%A0%E5%A5%BD%E4%B8%96%E7%95%8C |
| ö(德文) | 1 | 2 | 6 | %C3%B6 |
| ß(德文) | 1 | 2 | 6 | %C3%9F |
| あ(日文假名) | 1 | 3 | 9 | %E3%81%82 |
| こんにちは(日文) | 5 | 15 | 45 | %E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF |
| U+1F600(emoji 笑脸) | 1 | 4 | 12 | %F0%9F%98%80 |
| U+1F680(emoji 火箭) | 1 | 4 | 12 | %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 原始百分号编码 | %20 | RFC 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 中。