DevToolBox免费
博客

URL 编码(百分号编码)详解:%20 和 %3A 到底是什么意思

10 分钟阅读作者 DevToolBox

在 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    @ → %40

URL 编码字符参考表

以下是最常见的百分号编码字符快速参考表。收藏此页面以便日常使用。

字符编码形式名称 / 用途
(space)%20空格
&%26参数分隔符
?%3F查询字符串起始符
/%2F路径分隔符
:%3A冒号(协议分隔符)
=%3D键值分隔符
#%23片段标识符
+%2B加号(表单中表示空格)
@%40AT 符号
%%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 编码函数,或试试我们的在线工具。

使用我们的 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 once

2. 编码整个 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)

Base64 Encoder →URL Parser →

常见问题

为什么空格编码为 %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 请求就变得简单了。收藏此指南以便快速参考。

试试我们的 URL 编码/解码工具,即时编码和解码 →

𝕏 Twitterin LinkedIn
这篇文章有帮助吗?

保持更新

获取每周开发技巧和新工具通知。

无垃圾邮件,随时退订。

试试这些相关工具

%20URL Encoder/Decoder🔗URL ParserB64Base64 Encoder/Decoder%%Percent Encoding Tool

相关文章

URL 编码特殊字符:完整参考表与示例

URL 百分号编码完整参考。所有特殊字符查找表、encodeURIComponent 与 encodeURI 的使用场景,以及常见编码错误。

URL 编码与解码完全指南:百分号编码详解

免费在线 URL 编码解码工具。详解百分号编码原理,支持 JavaScript、Python、Bash、PHP 代码示例。

ASCII vs Unicode vs UTF-8 编码详解

理解 ASCII、Unicode 和 UTF-8 的区别。了解字符编码原理、为什么 UTF-8 主导了互联网,以及如何处理编码问题。

URL 编码解码在线指南:百分比编码、RFC 3986 与最佳实践

URL 编码(百分比编码)完整指南。涵盖 RFC 3986 保留与非保留字符、encodeURIComponent 与 encodeURI、Python urllib.parse、Java URLEncoder、常见编码字符、表单编码、API 查询参数、双重编码调试与 URL 安全 Base64。