编码 · URL

URL 编码:那些 %E4%B8%AD 后面藏着的坑

"URL 编码就是把汉字变 % 加十六进制"——对,但每次写代码还是会卡在 encodeURI 还是 encodeURIComponent、用 + 还是 %20、Java 怎么传字符集这些细节上。这篇按真实踩坑场景列。

30 秒搞清楚原理

URL 的"地理位置"层(路径、查询字符串)只允许有限的 ASCII 字符。其他所有东西——中文、空格、emoji、保留字符——都得编码成 % + 两位十六进制。

# UTF-8 字节序列 → 每字节单独 % 编码
"中" → UTF-8 → E4 B8 AD → "%E4%B8%AD"
"你好 world" → "%E4%BD%A0%E5%A5%BD%20world"

所以 URL 里看到 %E4%B8%AD%E6%96%87 你心算就能知道这是 6 字节的 UTF-8——大概率是两个汉字。

encodeURI vs encodeURIComponent(最高频踩坑)

JS 里有两个编码函数,**99% 的场景应该用第二个**:

// encodeURI:编码整个 URL,保留 URL 结构字符
encodeURI("https://example.com/搜索?q=你好&p=1");
// "https://example.com/%E6%90%9C%E7%B4%A2?q=%E4%BD%A0%E5%A5%BD&p=1"
// 注意 :/?&= 都没动

// encodeURIComponent:编码一个值(query param 或路径片段)
encodeURIComponent("a&b=c");
// "a%26b%3Dc"
// 注意 &= 也编了,因为它们是"值"而不是"结构"

判断方法:你在编码整个 URL 还是 URL 中的一段值?后者用 Component。

// ✅ 正确用法
const url = "https://api.com/search?q=" + encodeURIComponent(userInput);

// ❌ 错误:会把 & 留着,userInput 含 & 时 URL 被截断
const url = "https://api.com/search?q=" + encodeURI(userInput);
⚠️

真实事故:用户搜索 "javascript & jquery",代码用了 encodeURI(),结果 & 没被编码。后端把 URL 解析为两个参数 q=javascript jquery=,搜索结果完全错了。改成 encodeURIComponent 立刻好。

+ 还是 %20?历史包袱问题

同样是"空格",两种编码都看见过:

编码来源用在哪
%20RFC 3986 (URL 标准)URL path、JSON 里、所有正确场景
+HTML 表单 (application/x-www-form-urlencoded)POST 表单 body、部分 query 实现

JS 的 encodeURIComponent 永远用 %20。但解码时两种都得识别:

// URLSearchParams 兼容两种
new URLSearchParams("q=hello+world").get("q");  // "hello world"
new URLSearchParams("q=hello%20world").get("q"); // "hello world"

// 但 decodeURIComponent 不会把 + 当空格
decodeURIComponent("hello+world");  // "hello+world"  ← 注意

哪些字符必须编码(RFC 3986)

按规范分三类:

各语言的编解码 API

JavaScript

// 编
encodeURIComponent("你好 world");
// "%E4%BD%A0%E5%A5%BD%20world"

// 解
decodeURIComponent("%E4%BD%A0%E5%A5%BD%20world");
// "你好 world"

// 处理整个 URL 推荐用 URL/URLSearchParams 而不是手拼
const u = new URL("https://api.com/search");
u.searchParams.set("q", "你好 world");
console.log(u.toString());
// https://api.com/search?q=%E4%BD%A0%E5%A5%BD+world

Python

from urllib.parse import quote, unquote, quote_plus, urlencode

quote("你好 world")        # '%E4%BD%A0%E5%A5%BD%20world'   ← 空格变 %20
quote_plus("你好 world")   # '%E4%BD%A0%E5%A5%BD+world'    ← 空格变 +
unquote('%E4%BD%A0')       # '你好'

# 构造完整 query string
urlencode({"q": "你好 world", "p": 1})
# 'q=%E4%BD%A0%E5%A5%BD+world&p=1'

Java

// Java URLEncoder 是 form 编码(空格变 +)
URLEncoder.encode("你好 world", "UTF-8");
// "%E4%BD%A0%E5%A5%BD+world"

// 真正的 URL 编码(RFC 3986)需要后处理
URLEncoder.encode("你好 world", "UTF-8")
  .replace("+", "%20")
  .replace("*", "%2A")
  .replace("%7E", "~");

Go

import "net/url"

url.QueryEscape("你好 world")  // "%E4%BD%A0%E5%A5%BD+world"  (空格变 +)
url.PathEscape("你好/path")    // "%E4%BD%A0%E5%A5%BD/path"  (保留 /)
📌

跨语言对接时不要假设对方用什么编码——加一行测试:让对方传一个含空格、中文、& 的字符串,看你能不能正确还原。

URL 编码 vs Base64 vs HTML 实体

三种都叫"编码",用途完全不同:

编码解决什么体积典型场景
URL 编码让数据通过 URL 安全传输中文 3xquery string、URL path
Base64让二进制通过文本协议1.33xData URL、JWT、邮件附件
HTML 实体让 HTML 里的特殊字符不被解析为标签3-7xHTML 文档中渲染 < > &

常见疑问

为什么有时候 URL 里中文不用编码?

浏览器地址栏显示的是解码后的版本。复制粘贴或者通过 JS 拿到的是原始编码版本。试试在 Chrome 地址栏输入 https://example.com/中文,然后右键复制 URL——你会看到 %E4%B8%AD%E6%96%87

对已经编码过的字符串再编码会怎样?

encodeURIComponent("%E4%BD%A0");
// "%25E4%25BD%25A0"   ← % 本身被编码成 %25
// 解码两次才能拿回原字符串
decodeURIComponent(decodeURIComponent("%25E4%25BD%25A0"));
// "你"

所以:不要重复编码。某些后端会自动解码一次,前端再编码一次刚好,但如果链路里有多个组件都"贴心地"编码,就会出现 %2525 这种现象。

哈希(#)后面的内容需要编码吗?

需要。SPA 路由 /#/页面?q=值 里的中文必须编码,否则 history.pushState / location.hash 行为不一致。

💡

需要快速编/解码?URL 编解码工具支持批量、双向转换,自动识别 UTF-8。

URL 编解码工具

双向转换、UTF-8 自动识别、批量处理

打开 URL 工具