转换器

URL 编码指南:百分号编码、RFC 3986 与常见错误

了解 URL 编码的工作原理、哪些字符需要百分号编码,以及常见陷阱。涵盖 RFC 3986 规范、encodeURIComponent 与 encodeURI 的区别、重复编码问题,以及七种编程语言的编码示例。

发布于 2026年3月20日
15 分钟阅读
密码学指南

URL 编码指南:百分号编码、RFC 3986 与常见错误

如果你曾在网址中看到 %20,或者疑惑为何在查询参数中加入 & 后 API 请求就失败了,那你已经遇到了 URL 编码。URL 编码也称为百分号编码,是一种让特殊字符能够安全出现在 URL 中而不破坏 Web 功能的机制。

本指南涵盖开发者需要了解的 URL 编码全貌:哪些字符需要编码、算法如何运作、会破坏应用的常见错误,以及七种编程语言中的编码函数。欢迎使用我们的免费 URL 编码与解码工具 亲身体验百分号编码。

什么是 URL 编码?

URL 编码(在 RFC 3986 中正式称为百分号编码)是将字符转换为可安全嵌入 URL 的格式的过程。在 URL 语法中具有特殊含义的字符,或超出允许 ASCII 范围的字符,会被替换为 % 符号加上对应的两位十六进制值。

举例如下:

  • 空格 → %20
  • 与号 &%26
  • 等号 =%3D
  • 斜杠 /%2F
  • 欧元符号 %E2%82%AC(三个 UTF-8 字节)

为什么需要 URL 编码

URL 的严格语法定义于 RFC 3986。某些字符充当结构分隔符

https://example.com:8080/path/to/page?key=value&other=data#section
 ___    ___________  ____  ___________  _____________________  _______
scheme      host     port     path          query string        fragment

每个分隔符字符都有明确的作用:

字符URL 中的作用未编码时的后果
:分隔协议与主机、主机与端口key=time:30 会使解析器混乱
/分隔路径段/search/cats/dogs 看起来像 3 个路径段
?开始查询字符串?query=what? 含义不明确
#开始片段标识符key=C# 会在 # 处截断
&分隔查询参数name=Tom&Jerry 会被解析为 2 个参数
=分隔键与值equation=2+2=4 含有两个等号
@分隔用户信息与主机email=user@host 看起来像认证信息
+表单编码中的空格含义不明确:加号还是空格?

当这些字符作为数据而非分隔符出现时,必须进行百分号编码。

哪些字符需要编码?

RFC 3986 将字符分为三类:

非保留字符(永远不需要编码)

这些字符可以在 URL 中的任意位置出现,无需编码:

A-Z  a-z  0-9  -  _  .  ~

共计 66 个字符。

保留字符(作为数据时需编码)

这些字符在 URL 语法中具有特殊含义,当作为数据值使用时必须编码:

字符十六进制代码作用
:%3A协议/端口分隔符
/%2F路径分隔符
?%3F查询字符串起始
#%23片段起始
[%5BIPv6 括号
]%5DIPv6 括号
@%40用户信息分隔符
!%21子分隔符
$%24子分隔符
&%26参数分隔符
'%27子分隔符
(%28子分隔符
)%29子分隔符
*%2A子分隔符
+%2B子分隔符 / 空格
,%2C子分隔符
;%3B子分隔符
=%3D键值分隔符

其他所有字符(始终编码)

所有其他字符——包括空格、非 ASCII 字符和控制字符——必须始终编码:

字符编码备注
空格%20最常见的编码
"%22双引号
<%3C小于号
>%3E大于号
{%7B左花括号
}%7D右花括号
``%7C
\%5C反斜杠
^%5E脱字符
`%60反引号
非 ASCII多字节UTF-8 编码后再百分号编码

百分号编码的工作原理

编码算法非常直观:

  1. 判断字符是否需要编码(不属于非保留字符,或是作为数据使用的保留字符)
  2. 将字符转换为 UTF-8 字节序列
  3. 将每个字节表示为 %XX,其中 XX 是对应的十六进制值

示例:编码「café」

字符UTF-8 字节百分号编码
c63c(非保留字符)
a61a(非保留字符)
f66f(非保留字符)
éC3 A9%C3%A9

结果:caf%C3%A9

示例:编码完整查询字符串

原始 URL:

https://example.com/search?q=hello world&lang=en&tag=c#

正确编码后:

https://example.com/search?q=hello%20world&lang=en&tag=c%23

注意:&= 保持未编码状态,因为它们在此处承担结构分隔作用。只有数据值(hello worldc#)才需要编码。

空格:%20 还是 +(加号)

空格的两种编码方式是无休止混乱的根源:

格式空格编码标准使用场景
百分号编码%20RFC 3986URL 路径,通用场景
表单编码+HTML 规范(application/x-www-form-urlencoded)HTML 表单提交

最佳实践:URL 路径中的空格使用 %20。在查询字符串中,%20+ 都被广泛接受,但 %20 的兼容性更强。

encodeURI 与 encodeURIComponent

JavaScript 提供了两个 URL 编码函数,其行为存在关键差异:

encodeURI()

完整 URI 进行编码,保留在 URL 中具有结构含义的字符:

encodeURI("https://example.com/path with spaces?q=hello world")
// "https://example.com/path%20with%20spaces?q=hello%20world"

不编码; , / ? : @ & = + $ - _ . ! ~ * ' ( ) #

encodeURIComponent()

URI 组成部分(如单个查询参数值)进行编码,对非保留字符以外的所有内容编码:

encodeURIComponent("hello world & goodbye")
// "hello%20world%20%26%20goodbye"

不编码A-Z a-z 0-9 - _ . ! ~ * ' ( )

如何选择

使用场景函数示例
编码完整 URLencodeURI()encodeURI(fullUrl)
编码查询参数值encodeURIComponent()?q=${encodeURIComponent(searchTerm)}
编码路径段encodeURIComponent()/users/${encodeURIComponent(username)}
将跳转 URL 作为参数编码encodeURIComponent()?redirect=${encodeURIComponent(returnUrl)}

简单原则:如果值中可能包含 &=?/,使用 encodeURIComponent()

常见错误

1. 重复编码

最常见的 URL 编码错误。当对已经编码过的字符串再次编码时就会出现:

原始字符串:   hello world
第一次编码:   hello%20world
重复编码:     hello%2520world  ← 错误

%20 中的 % 被编码成 %25,从而产生 %2520。服务器收到的是字面量 %20 而非空格。

预防方法:重新编码前始终先解码,或追踪字符串是否已被编码。

2. 未对查询参数值编码

// 错误 —— 若 searchTerm 包含 & 或 = 则会出问题
const url = `/search?q=${searchTerm}`;

// 正确
const url = `/search?q=${encodeURIComponent(searchTerm)}`;

如果 searchTermcats & dogs,错误写法会生成 /search?q=cats & dogs,这会被拆成两个参数:q=cats dogs(或更糟糕的结果)。

3. 对参数值使用 encodeURI()

// 错误 —— encodeURI 保留 & 和 =
const url = `/api?data=${encodeURI("a=1&b=2")}`;
// 结果:/api?data=a=1&b=2  ← 变成 3 个参数而非 1 个

// 正确
const url = `/api?data=${encodeURIComponent("a=1&b=2")}`;
// 结果:/api?data=a%3D1%26b%3D2  ← 1 个参数

4. 忘记对非 ASCII 字符编码

包含非 ASCII 字符(重音字母、CJK 字符、emoji)的 URL 必须编码:

❌ https://example.com/café
✅ https://example.com/caf%C3%A9

现代浏览器在地址栏中显示解码后的版本,但实际 HTTP 请求使用的是百分号编码。

各编程语言中的 URL 编码

语言编码解码
JavaScriptencodeURIComponent(str)decodeURIComponent(str)
Pythonurllib.parse.quote(str)urllib.parse.unquote(str)
JavaURLEncoder.encode(str, "UTF-8")URLDecoder.decode(str, "UTF-8")
PHPrawurlencode($str)rawurldecode($str)
C#Uri.EscapeDataString(str)Uri.UnescapeDataString(str)
Gourl.QueryEscape(str)url.QueryUnescape(str)
RubyCGI.escape(str)CGI.unescape(str)

注意:Java 的 URLEncoder.encode() 和 PHP 的 urlencode() 使用 + 表示空格(表单编码)。在 PHP 中若要符合 RFC 3986 规范、以 %20 表示空格,应使用 rawurlencode()

URL 编码与 HTML 编码

这两种编码系统经常被混淆:

特性URL 编码HTML 编码
目的在 URL 中安全表示字符在 HTML 中安全表示字符
格式%XX(十六进制字节)&name;&#number;
空格%20&nbsp; 或直接空格
&%26&amp;
<%3C&lt;
"%22&quot;
标准RFC 3986HTML 规范
适用场景URL 查询字符串、路径HTML 属性、内容

两者用途完全不同,不可互换。

常见问题

URL 中的 %20 是什么意思?

%20 是空格字符的百分号编码表示。% 表示这是一个编码序列,20 是空格字符的十六进制值(十进制为 32)。当浏览器或服务器在 URL 中遇到 %20 时,会将其替换为空格。

URL 编码区分大小写吗?

根据 RFC 3986,百分号编码中的十六进制数字(如 %3A%3a)不区分大小写,但标准建议使用大写。大多数实现两者都接受。不过,URL 路径的其余部分是否区分大小写取决于服务器(Linux 服务器通常区分大小写,Windows 服务器则不区分)。

国际化域名是如何工作的?

国际化域名(IDN)使用一种名为 Punycode 的系统,将 Unicode 域名转换为 ASCII。例如,münchen.de 会变成 xn--mnchen-3ya.de。路径和查询组成部分则使用标准的百分号编码来处理非 ASCII 字符。


准备好编码或解码 URL 了吗?使用我们的 URL 编码与解码工具,即时完成百分号编码并查看字符分解。如需其他编码工具,请查看我们的 Base64 编码器十六进制转文本工具

关于本文

本文是我们综合 转换器 教程系列的一部分。继续了解古典密码学,并探索我们的交互式密码工具。

更多 转换器 教程

试用 转换器 工具

通过我们的交互式转换器工具,将所学知识付诸实践。

试用 转换器 工具