URL 编码(也叫百分号编码)将特殊字符转换为可以在 URL 中安全传输的格式。每个开发者都会遇到 URL 编码问题——链接损坏、参数乱码、或因未编码字符导致的 API 错误。本完整 URL 编码参考指南为你提供所有特殊字符及其编码形式的权威对照表,以及各主流编程语言的实用示例。
使用我们的 URL 编码/解码工具即时编码或解码任何 URL →
什么是 URL 编码(百分号编码)?
URL 编码用 % 符号加上两个十六进制数字(代表字符的 ASCII/UTF-8 字节值)来替换不安全或保留字符。例如,空格变为 %20,& 符号变为 %26。
这个机制在 RFC 3986(统一资源标识符:通用语法)中定义。URL 只能包含 US-ASCII 字符集中的有限字符集。任何超出此集合的字符——或任何在特殊含义之外使用的保留字符——都必须进行百分号编码。
通用格式为:%XX,其中 XX 是字节的十六进制值。对于多字节 UTF-8 字符(如中文或 emoji),每个字节分别编码。例如,汉字"中"(U+4E2D)编码为 %E4%B8%AD(UTF-8 中的三个字节)。
// URL encoding example
Original: https://example.com/search?q=hello world&lang=en
Encoded: https://example.com/search?q=hello%20world&lang=en
// The space in "hello world" becomes %20
// The & and = remain unencoded (they are structural)完整 URL 编码参考表
以下是所有常见特殊字符及其百分号编码形式的完整对照表。收藏此页面以便快速查阅。
| 字符 | 编码形式 | ASCII 码 | 说明 |
|---|---|---|---|
(space) | %20 | 32 | 空格(也可用 + 表示) |
! | %21 | 33 | 感叹号 |
" | %22 | 34 | 双引号 |
# | %23 | 35 | 井号 / 片段标识符 |
$ | %24 | 36 | 美元符号 |
% | %25 | 37 | 百分号(编码的转义字符本身) |
& | %26 | 38 | 与号 / 参数分隔符 |
' | %27 | 39 | 单引号 |
( | %28 | 40 | 左圆括号 |
) | %29 | 41 | 右圆括号 |
* | %2A | 42 | 星号 |
+ | %2B | 43 | 加号(表单中表示空格) |
, | %2C | 44 | 逗号 |
/ | %2F | 47 | 正斜杠 / 路径分隔符 |
: | %3A | 58 | 冒号(协议分隔符) |
; | %3B | 59 | 分号 |
< | %3C | 60 | 小于号 |
= | %3D | 61 | 等号(键值分隔符) |
> | %3E | 62 | 大于号 |
? | %3F | 63 | 问号(查询字符串起始符) |
@ | %40 | 64 | @ 符号(邮箱 / 用户信息分隔) |
[ | %5B | 91 | 左方括号 |
\ | %5C | 92 | 反斜杠 |
] | %5D | 93 | 右方括号 |
^ | %5E | 94 | 脱字符 |
` | %60 | 96 | 反引号 |
{ | %7B | 123 | 左花括号 |
| | %7C | 124 | 竖线 |
} | %7D | 125 | 右花括号 |
~ | %7E | 126 | 波浪号(RFC 3986 中为非保留字符) |
\n | %0A | 10 | 换行符(LF) |
\r | %0D | 13 | 回车符(CR) |
\t | %09 | 9 | 制表符(Tab) |
保留字符与非保留字符(RFC 3986)
RFC 3986 定义了两类字符,决定何时需要编码:
保留字符
这些字符在 URL 中有特殊含义。如果你想按字面意思使用它们(而非其特殊用途),必须进行百分号编码。
: / ? # [ ] @ ! $ & ' ( ) * + , ; =
// Reserved characters in RFC 3986
gen-delims = : / ? # [ ] @
sub-delims = ! $ & ' ( ) * + , ; =
// Example: & is reserved (parameter separator)
// To use & literally in a value, encode it:
?search=Tom%26Jerry // searching for "Tom&Jerry"
?search=Tom&Jerry // WRONG: "Jerry" becomes a separate param非保留字符
这些字符可以在 URL 中直接使用而无需编码,它们没有特殊含义:
字母:A-Z、a-z 数字:0-9 特殊:- _ . ~
// Unreserved characters (RFC 3986) — never need encoding
ALPHA = A-Z / a-z
DIGIT = 0-9
other = - . _ ~
// These are always safe in any part of a URL:
https://example.com/my-file_name.v2~backup其他所有字符——包括空格、非 ASCII 字符和控制字符——都必须进行百分号编码。
JavaScript 中的 encodeURI 与 encodeURIComponent
JavaScript 提供两个内置 URL 编码函数,使用错误的那个是最常见的错误之一:
encodeURI()
编码完整 URI。不会编码在 URL 中有特殊含义的字符:: / ? # [ ] @ ! $ & ' ( ) * + , ; =
// encodeURI() — for complete URLs
const url = 'https://example.com/path name/file?q=hello world';
encodeURI(url);
// → "https://example.com/path%20name/file?q=hello%20world"
// Note: :, /, ?, = are NOT encoded (URL structure preserved)encodeURIComponent()
编码 URI 组件(如查询参数值)。会编码保留字符如 & = + / ——这正是处理参数值时所需要的。
// encodeURIComponent() — for parameter values
const query = 'price=100¤cy=USD';
encodeURIComponent(query);
// → "price%3D100%26currency%3DUSD"
// Note: = and & ARE encoded (they're data, not structure)
// Correct usage: build URL piece by piece
const base = 'https://api.example.com/search';
const param = 'Tom & Jerry: The Movie';
const fullUrl = base + '?q=' + encodeURIComponent(param);
// → "https://api.example.com/search?q=Tom%20%26%20Jerry%3A%20The%20Movie"经验法则:对查询参数值和路径段使用 encodeURIComponent()。仅在编码希望保留结构的完整 URL 时使用 encodeURI()。
// Side-by-side comparison
const text = 'hello world & goodbye=fun';
encodeURI(text);
// → "hello%20world%20&%20goodbye=fun"
// & and = are NOT encoded
encodeURIComponent(text);
// → "hello%20world%20%26%20goodbye%3Dfun"
// & and = ARE encoded
// decodeURI() and decodeURIComponent() reverse the process
decodeURIComponent('%E4%B8%AD%E6%96%87'); // → "中文"不同编程语言中的 URL 编码
Python
# Python 3 — urllib.parse
from urllib.parse import quote, quote_plus, unquote, urlencode
# Encode a path segment (spaces → %20)
quote('hello world/file name')
# → 'hello%20world/file%20name' (/ is safe by default)
# Encode a path segment including /
quote('hello world/file name', safe='')
# → 'hello%20world%2Ffile%20name'
# Encode a query parameter value (spaces → +)
quote_plus('hello world&foo=bar')
# → 'hello+world%26foo%3Dbar'
# 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 / URI
import java.net.URLEncoder;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
// Encode a query parameter value (spaces → +)
String encoded = URLEncoder.encode(
"hello world&foo=bar", StandardCharsets.UTF_8
);
// → "hello+world%26foo%3Dbar"
// Decode
String decoded = URLDecoder.decode(
"hello+world%26foo%3Dbar", StandardCharsets.UTF_8
);
// → "hello world&foo=bar"
// For path segments, use URI class
import java.net.URI;
URI uri = new URI("https", "example.com", "/path with spaces", "q=hello world", null);
// → https://example.com/path%20with%20spaces?q=hello%20worldGo
// Go — net/url package
package main
import (
"fmt"
"net/url"
)
func main() {
// Encode a query parameter value
encoded := url.QueryEscape("hello world&foo=bar")
// → "hello+world%26foo%3Dbar"
// Encode a path segment
pathEncoded := url.PathEscape("hello world/file")
// → "hello%20world%2Ffile"
// Build a complete URL safely
u := &url.URL{
Scheme: "https",
Host: "example.com",
Path: "/search",
}
q := u.Query()
q.Set("q", "Tom & Jerry")
q.Set("lang", "en")
u.RawQuery = q.Encode()
fmt.Println(u.String())
// → "https://example.com/search?lang=en&q=Tom+%26+Jerry"
}PHP
<?php
// PHP — urlencode / rawurlencode
// Query parameter encoding (spaces → +)
echo urlencode('hello world&foo=bar');
// → "hello+world%26foo%3Dbar"
// Path segment encoding (spaces → %20, RFC 3986)
echo rawurlencode('hello world&foo=bar');
// → "hello%20world%26foo%3Dbar"
// Build query string from array
echo http_build_query([
'q' => 'Tom & Jerry',
'page' => 1
]);
// → "q=Tom+%26+Jerry&page=1"
// Decode
echo urldecode('hello+world%26foo%3Dbar');
// → "hello world&foo=bar"
echo rawurldecode('hello%20world%26foo%3Dbar');
// → "hello world&foo=bar"
?>C#
// C# — Uri.EscapeDataString / HttpUtility
using System;
using System.Web;
// Encode a URI component (RFC 3986)
string encoded = Uri.EscapeDataString("hello world&foo=bar");
// → "hello%20world%26foo%3Dbar"
// Encode for query string (spaces → +)
string queryEncoded = HttpUtility.UrlEncode("hello world&foo=bar");
// → "hello+world%26foo%3Dbar"
// Decode
string decoded = Uri.UnescapeDataString("hello%20world%26foo%3Dbar");
// → "hello world&foo=bar"
// Build query string
var query = HttpUtility.ParseQueryString(string.Empty);
query["q"] = "Tom & Jerry";
query["page"] = "1";
string queryString = query.ToString();
// → "q=Tom+%26+Jerry&page=1"Ruby
# Ruby — URI / CGI
require 'uri'
require 'cgi'
# Encode a component (spaces → %20)
URI.encode_www_form_component('hello world&foo=bar')
# → "hello+world%26foo%3Dbar"
# Encode for path (spaces → %20)
URI::DEFAULT_PARSER.escape('hello world')
# → "hello%20world"
# Build query string
URI.encode_www_form('q' => 'Tom & Jerry', 'page' => '1')
# → "q=Tom+%26+Jerry&page=1"
# Decode
CGI.unescape('hello%20world%26foo%3Dbar')
# → "hello world&foo=bar"常见 URL 编码错误
1. 重复编码(Double Encoding)
重复编码发生在对已编码的字符串再次编码时。例如,%20 变成 %2520(% 被编码为 %25)。这是最常见且最难调试的 URL 问题之一。
// Double encoding example
const value = 'hello world';
// First encoding (correct)
const encoded = encodeURIComponent(value);
// → "hello%20world"
// Accidentally encoding again (BUG!)
const doubleEncoded = encodeURIComponent(encoded);
// → "hello%2520world"
// %20 → %25 (the %) + 20 → %2520
// The server receives "hello%20world" instead of "hello world"
// Fix: only encode once, at the point where you build the URL2. 编码整个 URL
永远不要将整个 URL 传给 encodeURIComponent()。它会编码 ://、/、? 和 = 字符,破坏 URL 结构。只编码单个参数值。
// WRONG: encoding the entire URL
const url = 'https://example.com/api?q=hello world';
encodeURIComponent(url);
// → "https%3A%2F%2Fexample.com%2Fapi%3Fq%3Dhello%20world"
// Completely broken URL!
// CORRECT: only encode the parameter value
const base = 'https://example.com/api';
const query = 'hello world';
const correctUrl = base + '?q=' + encodeURIComponent(query);
// → "https://example.com/api?q=hello%20world"3. 忘记 + 与 %20 的区别
在 application/x-www-form-urlencoded 格式(HTML 表单)中,空格编码为 +。在标准百分号编码(RFC 3986)中,空格为 %20。大多数服务端框架两者都支持,但 API 可能较严格。不确定时使用 %20。
// + vs %20 for spaces
// Form data (application/x-www-form-urlencoded):
// "hello world" → "hello+world"
// RFC 3986 (URI standard):
// "hello world" → "hello%20world"
// In JavaScript:
encodeURIComponent('hello world'); // → "hello%20world"
// To get + for form data, replace after:
encodeURIComponent('hello world').replace(/%20/g, '+');
// → "hello+world"
// URLSearchParams automatically uses + for spaces:
new URLSearchParams({ q: 'hello world' }).toString();
// → "q=hello+world"4. 未编码非 ASCII 字符
如中文、日文、表情符号等字符必须先转换为 UTF-8 编码,然后对每个字节进行百分号编码。忘记这一步会导致 URL 中出现乱码。
// Non-ASCII characters must be UTF-8 encoded first
encodeURIComponent('café');
// → "caf%C3%A9" (é = 2 UTF-8 bytes: C3 A9)
encodeURIComponent('中文');
// → "%E4%B8%AD%E6%96%87" (中 = 3 bytes, 文 = 3 bytes)
encodeURIComponent('🚀');
// → "%F0%9F%9A%80" (rocket emoji = 4 UTF-8 bytes)5. 在服务端编码 Hash 片段
片段标识符(#section)不会发送到服务器——它仅在客户端使用。在服务端编码或处理它是无意义的。
6. 在 JavaScript 中使用已弃用的 escape()
escape() 函数已被弃用,且不能正确处理 UTF-8。请始终使用 encodeURI() 或 encodeURIComponent()。
// DEPRECATED — do NOT use
escape('hello world'); // → "hello%20world" (works for ASCII)
escape('café'); // → "caf%E9" (WRONG! not UTF-8)
// CORRECT alternatives
encodeURIComponent('café'); // → "caf%C3%A9" (correct UTF-8)何时进行 URL 编码:查询参数、路径段、片段
URL 的不同部分有不同的编码规则:
// URL anatomy and encoding zones
https://user:pass@example.com:8080/path/to/page?key=value&k2=v2#section
|____| |_______| |__________| |__||____________||________________||______|
scheme userinfo host port path query string fragment
// Each part has different encoding rules:查询参数
始终编码参数的键和值。这是最常需要编码的地方。
// Query parameters: ALWAYS encode keys and values
const params = new URLSearchParams();
params.set('search', 'C++ programming'); // auto-encodes
params.set('filter', 'price>100&sale=true'); // auto-encodes
params.toString();
// → "search=C%2B%2B+programming&filter=price%3E100%26sale%3Dtrue"
// Or manually:
'?q=' + encodeURIComponent('price > $100 & discount = 20%')
// → "?q=price%20%3E%20%24100%20%26%20discount%20%3D%2020%25"路径段
分别编码每个路径段。作为 URL 结构的正斜杠(<code>/</code>)不应编码,但段值中的斜杠必须编码。
// Path segments: encode each segment individually
const fileName = 'my report (final).pdf';
const path = '/files/' + encodeURIComponent(fileName);
// → "/files/my%20report%20(final).pdf"
// DO NOT encode the structural slashes:
// WRONG: encodeURIComponent('/files/my report.pdf')
// → "%2Ffiles%2Fmy%20report.pdf" (broken path)片段标识符
片段(<code>#</code> 之后的部分)遵循与查询字符串相同的编码规则。注意片段不会发送到服务器。
// Fragment: encode like query string values
const section = 'Q&A Section';
const url = '/page#' + encodeURIComponent(section);
// → "/page#Q%26A%20Section"
// Remember: browsers never send fragments to the server
// window.location.hash gives you the raw fragment域名(主机名)
域名对国际化域名(IDN)使用 Punycode(而非百分号编码)。例如,带变音符的 münchen.de 变为 xn--mnchen-3ya.de。
// International Domain Names use Punycode, NOT percent encoding
// münchen.de → xn--mnchen-3ya.de
// 中文.com → xn--fiq228c.com
// 日本語.jp → xn--wgv71a309e.jp
// In JavaScript:
const url = new URL('https://münchen.de/path');
url.hostname; // → "xn--mnchen-3ya.de" (auto-converted)常见问题
%20 和 + 表示空格有什么区别?
两者都表示空格,但来自不同标准。%20 由 RFC 3986(URI 标准)定义,用于路径段和现代 API。+ 由 application/x-www-form-urlencoded 格式(HTML 表单)定义。在查询字符串中,大多数服务器两者都接受。为了最大兼容性,使用 %20。
应该编码 URL 路径中的正斜杠(/)吗?
仅当斜杠是数据值的一部分(而非路径分隔符)时才需编码。例如,如果文件名包含斜杠,必须在路径段中编码为 %2F。分隔路径段的结构性斜杠永远不应编码。
如何在 URL 中编码 Unicode 字符(如中文、阿拉伯文)?
先将字符转换为 UTF-8 字节序列,然后对每个字节进行百分号编码。例如,"café"→ UTF-8 字节 63 61 66 C3 A9 → é 变为 %C3%A9。现代编程语言的 URL 编码函数会自动处理这些。
URL 编码区分大小写吗?
百分号编码中的十六进制数字不区分大小写(%2f 和 %2F 等价)。但 RFC 3986 建议使用大写字母(%2F)以保持一致性和规范化。
需要编码整个 URL 还是只编码部分?
永远不要一次性编码整个 URL。只编码各个组件——查询参数值、包含特殊字符的路径段和片段标识符。结构性字符(://、/、?、=、&)必须保持未编码以保留 URL 结构。
URL 编码是 Web 开发的基础技能。收藏此参考表以便快速查阅,并使用我们的工具实时编码和解码 URL。