在 URL 中见过 %20、%3A 或 %2F 吗?这些是百分号编码字符——使 URL 能够安全携带特殊字符的机制。本指南从字节级别的编码原理到各主流编程语言的实用代码示例,全面解析 URL 编码的工作原理。
使用我们的 URL 编码/解码工具即时编码或解码任何字符串 →
什么是 URL 编码(百分号编码)?
URL 编码,正式名称为百分号编码,是 RFC 3986 中定义的一种机制,用于表示 URI 中不允许或具有特殊含义的字符。它通过将每个目标字符替换为 % 符号加上两个大写十六进制数字(表示字节值)来工作。
URL 只能包含 ASCII 字符集中的有限字符。字符分为保留字符(具有结构含义,如 /、?、&)和非保留字符(字母、数字、-、_、.、~)。其他所有字符在出现在 URL 中之前都必须进行百分号编码。
例如,空格字符(ASCII 32,十六进制 0x20)变为 %20。冒号(ASCII 58,十六进制 0x3A)变为 %3A。这就是你看到 https://example.com/search?q=hello%20world 这样 URL 的原因。
// URL encoding in action
Original: https://example.com/search?q=hello world&lang=en
Encoded: https://example.com/search?q=hello%20world&lang=en
// Reserved characters and their encoded forms:
// space → %20 : → %3A / → %2F
// ? → %3F & → %26 = → %3D
// # → %23 + → %2B @ → %40URL 编码字符参考表
以下是最常见的百分号编码字符快速参考表。收藏此页面以便日常使用。
| 字符 | 编码形式 | 名称 / 用途 |
|---|---|---|
(space) | %20 | 空格 |
& | %26 | 参数分隔符 |
? | %3F | 查询字符串起始符 |
/ | %2F | 路径分隔符 |
: | %3A | 冒号(协议分隔符) |
= | %3D | 键值分隔符 |
# | %23 | 片段标识符 |
+ | %2B | 加号(表单中表示空格) |
@ | %40 | AT 符号 |
% | %25 | 百分号(转义字符本身) |
! | %21 | 感叹号 |
" | %22 | 双引号 |
< | %3C | 小于号 |
> | %3E | 大于号 |
{ | %7B | 左花括号 |
} | %7D | 右花括号 |
[ | %5B | 左方括号 |
] | %5D | 右方括号 |
| | %7C | 竖线 |
\ | %5C | 反斜杠 |
URL 编码的工作原理(逐步解析)
编码过程分三步:(1)取目标字符,(2)将其转换为 UTF-8 字节序列,(3)将每个字节百分号编码为 %XX。对于 ASCII 字符,这会生成一个 %XX 标记。对于多字节 UTF-8 字符,每个字节都有自己的百分号编码三元组。
让我们通过几个示例来了解这个过程:
// Step-by-step: How characters become percent-encoded
// Example 1: ASCII character (space)
// Character: " " (space)
// ASCII code: 32 → hex: 0x20
// Encoded: %20
// Example 2: ASCII character (ampersand)
// Character: "&"
// ASCII code: 38 → hex: 0x26
// Encoded: %26
// Example 3: Multi-byte UTF-8 (accented letter)
// Character: "é"
// Unicode: U+00E9
// UTF-8 bytes: 0xC3 0xA9 (2 bytes)
// Encoded: %C3%A9
// Example 4: Multi-byte UTF-8 (Chinese character)
// Character: "中"
// Unicode: U+4E2D
// UTF-8 bytes: 0xE4 0xB8 0xAD (3 bytes)
// Encoded: %E4%B8%AD
// Example 5: Multi-byte UTF-8 (emoji)
// Character: "🚀"
// Unicode: U+1F680
// UTF-8 bytes: 0xF0 0x9F 0x9A 0x80 (4 bytes)
// Encoded: %F0%9F%9A%80现代编程语言会自动处理这些——你很少需要自己做字节转换。使用本指南后面展示的内置 URL 编码函数,或试试我们的在线工具。
JavaScript 中 encodeURI 与 encodeURIComponent 的区别
JavaScript 提供两个 URL 编码函数,选错是常见的 bug 来源。理解它们的区别对于构建正确的 URL 至关重要。
encodeURI() 编码完整 URI。它保留在 URL 中有结构意义的字符:: / ? # [ ] @ ! $ & ' ( ) * + , ; =。当你有一个完整 URL 并想修复空格或非 ASCII 字符而不破坏 URL 结构时使用它。
// encodeURI() — preserves URL structure
const url = 'https://example.com/path name?q=hello world';
encodeURI(url);
// → "https://example.com/path%20name?q=hello%20world"
// ✓ :// / ? = are NOT encoded (structure preserved)encodeURIComponent() 编码 URI 组件(如查询参数值)。它编码除字母、数字和 - _ . ~ ! ' ( ) * 之外的所有字符。这意味着它会编码 & = + / : ?——当值本身可能包含这些字符时,这正是你需要的。
// encodeURIComponent() — encodes everything for use as a value
const value = 'price=100&discount=20%';
encodeURIComponent(value);
// → "price%3D100%26discount%3D20%25"
// ✓ = & % ARE encoded (they're data, not structure)
// Correct pattern: build URL piece by piece
const base = 'https://api.example.com/search';
const query = 'Tom & Jerry: The Movie';
const fullUrl = base + '?q=' + encodeURIComponent(query);
// → "https://api.example.com/search?q=Tom%20%26%20Jerry%3A%20The%20Movie"经验法则:对查询参数值始终使用 encodeURIComponent()。仅在编码需要保留结构的完整 URL 时使用 encodeURI()。
// Comparison table — which characters get encoded?
//
// Character encodeURI() encodeURIComponent()
// ───────── ─────────── ────────────────────
// space %20 %20
// / / (kept) %2F (encoded)
// ? ? (kept) %3F (encoded)
// & & (kept) %26 (encoded)
// = = (kept) %3D (encoded)
// # # (kept) %23 (encoded)
// : : (kept) %3A (encoded)
// + + (kept) %2B (encoded)
// @ @ (kept) %40 (encoded)
// é %C3%A9 %C3%A9
// 中 %E4%B8%AD %E4%B8%AD不同编程语言中的 URL 编码
每种主流语言都有内置的 URL 编码函数。以下是最常见的用法:
JavaScript
// JavaScript — encodeURIComponent / URLSearchParams
// Encode a query parameter value
encodeURIComponent('hello world&key=val');
// → "hello%20world%26key%3Dval"
// Decode
decodeURIComponent('hello%20world%26key%3Dval');
// → "hello world&key=val"
// URLSearchParams (auto-encodes, uses + for spaces)
const params = new URLSearchParams({ q: 'Tom & Jerry', page: '1' });
params.toString(); // → "q=Tom+%26+Jerry&page=1"Python
# Python 3 — urllib.parse
from urllib.parse import quote, quote_plus, unquote, urlencode
# Path encoding (spaces → %20)
quote('hello world/file') # → "hello%20world/file"
quote('hello world/file', safe='') # → "hello%20world%2Ffile"
# Query value encoding (spaces → +)
quote_plus('hello world&key=val') # → "hello+world%26key%3Dval"
# Build query string from dict
urlencode({'q': 'Tom & Jerry', 'page': '1'})
# → "q=Tom+%26+Jerry&page=1"
# Decode
unquote('%E4%B8%AD%E6%96%87') # → "中文"Java
// Java — URLEncoder (spaces → +)
import java.net.URLEncoder;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
String encoded = URLEncoder.encode(
"hello world&key=val", StandardCharsets.UTF_8
);
// → "hello+world%26key%3Dval"
String decoded = URLDecoder.decode(encoded, StandardCharsets.UTF_8);
// → "hello world&key=val"Go
// Go — net/url package
package main
import "net/url"
// Query value encoding (spaces → +)
url.QueryEscape("hello world&key=val")
// → "hello+world%26key%3Dval"
// Path segment encoding (spaces → %20)
url.PathEscape("hello world/file")
// → "hello%20world%2Ffile"
// Build URL safely with url.Values
vals := url.Values{}
vals.Set("q", "Tom & Jerry")
vals.Encode() // → "q=Tom+%26+Jerry"PHP
<?php
// PHP — urlencode (spaces → +) / rawurlencode (spaces → %20)
urlencode('hello world&key=val');
// → "hello+world%26key%3Dval"
rawurlencode('hello world&key=val');
// → "hello%20world%26key%3Dval"
http_build_query(['q' => 'Tom & Jerry', 'page' => 1]);
// → "q=Tom+%26+Jerry&page=1"
urldecode('hello%20world'); // → "hello world"
?>Ruby
# Ruby — CGI / URI
require 'cgi'
require 'uri'
CGI.escape('hello world&key=val')
# → "hello+world%26key%3Dval"
URI.encode_www_form('q' => 'Tom & Jerry', 'page' => '1')
# → "q=Tom+%26+Jerry&page=1"
CGI.unescape('hello%20world') # → "hello world"常见 URL 编码错误
1. 重复编码
重复编码发生在对已编码的字符串再次编码时。%20 中的 % 被重新编码为 %25,产生 %2520。服务器解码后得到 %20(字面字符串)而非空格。始终只编码原始值一次。
// Double encoding — a very common bug
const value = 'hello world';
const encoded = encodeURIComponent(value); // "hello%20world"
// BUG: encoding again
encodeURIComponent(encoded);
// → "hello%2520world" (%20 → %25 + 20)
// Server decodes to "%20" (literal) instead of " " (space)
// FIX: encode raw values exactly once2. 编码整个 URL
将完整 URL 传给 encodeURIComponent() 会编码 ://、/、? 和 = 字符,完全破坏 URL。只编码包含用户数据的单个查询参数值或路径段。
// WRONG: encoding entire URL
encodeURIComponent('https://example.com/api?q=test');
// → "https%3A%2F%2Fexample.com%2Fapi%3Fq%3Dtest" (broken!)
// CORRECT: encode only the parameter value
'https://example.com/api?q=' + encodeURIComponent('test value');
// → "https://example.com/api?q=test%20value"3. 空格用 + 还是 %20
在 application/x-www-form-urlencoded 格式(HTML 表单)中,空格编码为 +。在 RFC 3986 百分号编码中,空格为 %20。大多数服务器两者都处理,但某些严格的 API 只接受一种。构建 API 请求时,建议使用 %20。
// Space encoding: + vs %20
// HTML form submission (application/x-www-form-urlencoded):
// "hello world" → "hello+world"
// RFC 3986 (URI standard):
// "hello world" → "hello%20world"
// JavaScript functions:
encodeURIComponent('hello world'); // → "hello%20world" (RFC 3986)
new URLSearchParams({q: 'hello world'}).toString(); // → "q=hello+world" (form)4. 未编码查询参数值
忘记编码查询字符串中的用户输入值是安全风险(潜在注入)并导致 URL 损坏。如果用户搜索 Tom & Jerry,未编码的 & 会被解释为参数分隔符。
// Missing encoding breaks URLs
const search = 'Tom & Jerry';
// WRONG: unencoded & splits the value
'/search?q=' + search;
// → "/search?q=Tom & Jerry"
// Server sees: q="Tom " and Jerry="" (two params!)
// CORRECT: encode the value
'/search?q=' + encodeURIComponent(search);
// → "/search?q=Tom%20%26%20Jerry"
// Server sees: q="Tom & Jerry" (one param)URL 编码最佳实践
始终编码用户输入,然后再将其插入查询字符串或路径段。这既防止 URL 损坏,又防止潜在的注入攻击。
使用库函数,而非手动替换。如 encodeURIComponent()、Python 的 urllib.parse.quote() 或 Go 的 url.QueryEscape() 等函数能正确处理 UTF-8、边界情况和字符集。
用特殊字符测试。始终用包含 & = ? / # + % 和非 ASCII 字符(如重音字母、中日韩字符和 emoji)的输入测试 URL 构建代码。
嵌入二进制数据时使用 URL 安全的 Base64。标准 Base64 使用 + 和 /,与 URL 语法冲突。URL 安全的 Base64 将它们替换为 - 和 _。
// Best practice: use URL / URLSearchParams API
const url = new URL('https://api.example.com/search');
url.searchParams.set('q', 'Tom & Jerry: The Movie');
url.searchParams.set('lang', 'en');
url.toString();
// → "https://api.example.com/search?q=Tom+%26+Jerry%3A+The+Movie&lang=en"
// All encoding handled automatically!
// URL-safe Base64 (avoids + and / in URLs)
// Standard: "SGVsbG8gV29ybGQ=" (may contain + / =)
// URL-safe: "SGVsbG8gV29ybGQ" (uses - _ instead)常见问题
为什么空格编码为 %20?
空格字符的 ASCII 码是 32,十六进制为 0x20。百分号编码将字节表示为 % 加上其十六进制值,所以空格变为 %20。这在 RFC 3986 中定义。
空格用 + 和 %20 有什么区别?
%20 来自 RFC 3986(URI 标准),用于路径段和现代 API。+ 来自 HTML 表单使用的 application/x-www-form-urlencoded 格式。在查询字符串中大多数服务器两者都接受,但 %20 更通用。
需要编码整个 URL 吗?
不需要。你只应编码单个组件——查询参数值、包含用户数据的路径段和片段标识符。结构字符(://、/、?、=、&)必须保持未编码。
encodeURI 和 encodeURIComponent 有什么区别?
encodeURI() 编码完整 URL,保留结构字符如 / ? & =。encodeURIComponent() 编码单个组件(如查询值),会编码这些结构字符。对查询参数值始终使用 encodeURIComponent()。
如何解码 URL 编码的字符串?
JavaScript 用 decodeURIComponent(),Python 用 urllib.parse.unquote(),Go 用 url.QueryUnescape(),PHP 用 urldecode() 或 rawurldecode()。也可以使用我们的在线 URL 解码工具。
URL 编码是每个开发者都需要理解的基础 Web 概念。一旦你了解百分号编码在字节级别的工作原理,调试损坏的 URL 和构建正确的 API 请求就变得简单了。收藏此指南以便快速参考。