使用方法
- 在输入框里粘贴要处理的文字或整条链接。页面打开时已经预置示例
https://liuquan.net/search?q=柳泉 工具&n=100,它是 40 个码点、48 个 UTF-8 字节。 - 先选编码口径,四档分别对应
encodeURIComponent(编单个参数值)、encodeURI(编整条 URL)、表单序列化(application/x-www-form-urlencoded,空格输出+)和遗留函数escape(汉字输出%u67F3)。默认停在「整条 URL · encodeURI」,与示例的形态吻合。 - 点「编码」得到百分号编码结果,点「解码」把
%E6%9F%B3这类序列还原回字符。输入框里按 Ctrl + Enter(macOS 用 Cmd + Enter)等同于点「编码」——Enter 本身留给换行,分行粘贴多条链接时不会误触发。 - 下面的逐字节明细表把每个字符拆到码点和 UTF-8 字节,再给出它在当前口径下的
%XX。表默认显示 12 行,「加载更多」每次再放 12 行;「复制对照表」以 TSV 进剪贴板,「导出 CSV」存成带 BOM 的文件,Excel 打开中文不乱码。 - 「结果 → 输入」把结果搬回输入框,换一档口径再编一次,两次结果并排就能看出差异;「载入示例」随时还原那条默认示例。
这个 URL编码 工具能解决什么问题
一是拼出能用的带参链接。搜索、跳转、分享链接里带中文搜索词时,直接把「柳泉 工具」写进 query 会在部分客户端被截断或乱码,编成 %E6%9F%B3%E6%B3%89%20%E5%B7%A5%E5%85%B7 就稳了。
二是定位「参数被吞掉」的事故。用户的搜索词里打了一个 &,服务端却把后半段当成新参数——这是因为参数值没有编码。把值丢进来选「参数值 · encodeURIComponent」,& 会变成 %26,隐患立刻可见。
三是分清 %20 与 + 该用哪个。同一个空格,写在 path 段里必须是 %20,写进表单序列化的 body 才是 +;本页把两者的差别做成了可选口径,切换一次就能对照。
四是把日志和埋点里的一长串还原成人话。埋点上报的落地页常常整条都被编码过,粘进来点「解码」即可读。
本页不做这些事:不把 URL 拆成协议、主机、路径、查询参数,那属于「URL解析」;不做 Punycode 域名转码;不检查链接是否可访问。要拆解查询参数请用「Query字符串解析」,要把国际域名转成 xn-- 形式请用「Punycode转换器」。
两种编码口径的原理:RFC 3986 与 UTF-8 逐字节
URL编码的全过程只有两步:先按字符编码把字符折成字节,再对每个需要转义的字节写两位十六进制。RFC 3986《Uniform Resource Identifier (URI): Generic Syntax》第 2.1 节把百分号编码的语法定为 pct-encoded = "%" HEXDIG HEXDIG,也就是一个百分号紧跟两位十六进制数;RFC 5234 的 HEXDIG 只包含大写 A–F,所以规范输出是大写,本页与浏览器一致,一律输出大写。这里最关键的一点是:百分号编码作用在字节上,不作用在字符上——先把字符编成字节序列,再对每个需要转义的字节写 %XX。
RFC 3986 把字符分成三类。未保留字符(unreserved)有 66 个:26 个大写字母、26 个小写字母、10 个数字,再加 - . _ ~,它们永远不需要编码。保留字符(reserved)有 18 个,分两组:gen-delims 是 : / ? # [ ] @ 共 7 个,sub-delims 是 ! $ & ' ( ) * + , ; = 共 11 个;它们在 URI 里有语法作用,所以「看场合决定编不编」。空格既不在未保留也不在保留里,任何位置都必须编码。
浏览器实际给你的是两个函数。encodeURIComponent 原样保留 71 个字符——66 个 unreserved,外加 ! ' ( ) * 这 5 个;这 5 个来自更早的 RFC 2396,当年被定义为 mark 集合,浏览器为了兼容一直没改,所以它们在 RFC 3986 里虽然属于 sub-delims 却仍然不被编码。encodeURI 保留 82 个,比前者多出 # $ & + , / : ; = ? @ 这 11 个,正好是「除了 [ ] 之外的保留字符」。
表单口径走的是另一条规范。HTML 标准定义的 application/x-www-form-urlencoded 序列化算法把空格写成 +,它的安全集只有 A–Z a–z 0–9 * - . _ 共 66 个——比 encodeURIComponent 少了 ! ' ( ) ~ 这 5 个。也就是说 URLSearchParams 会把 ~ 编成 %7E、把 ' 编成 %27,而 encodeURIComponent 不会。这也解释了为什么解码时 + 有两种含义:decodeURIComponent('+') 返回的是加号本身,只有表单口径才把它当空格。
UTF-8 把字符折叠成字节。汉字柳的码点是 U+67F3,按 RFC 3629 编成 3 字节 E6 9F B3,于是编码结果是 %E6%9F%B3——一个汉字从 1 个码点膨胀成 9 个 ASCII 字符。字节数的分档是固定的:U+0000–U+007F 占 1 字节,U+0080–U+07FF 占 2 字节(如 © → C2 A9),U+0800–U+FFFF 占 3 字节,U+10000–U+10FFFF 占 4 字节(如 😀 U+1F600 → F0 9F 98 80,编码后 12 个字符)。三字节的首字节形如 1110xxxx,两个续字节都是 10xxxxxx,取值时首字节用 & 0x0F、续字节用 & 0x3F 取出低位再拼接——这正是明细表里每个 %XX 的来源。
完整示例(数值仅作演示)
示例条件与页面默认值完全一致:输入 https://liuquan.net/search?q=柳泉 工具&n=100,口径选「整条 URL · encodeURI」。这串文字共 40 个码点、48 个 UTF-8 字节,编码后 74 个字符。
- 40 个码点里 35 个是 ASCII 字符(
https://liuquan.net/search?q=这 29 个,加&n=100这 6 个),另外 4 个汉字、1 个空格; - 35 个 ASCII 字符里有 7 个是保留字符——
:1 个、/2 个、?1 个、&1 个、=2 个,encodeURI 把它们原样保留;剩下 28 个是未保留字符,也同样原样保留; 柳泉工具四个汉字各 3 字节、合计 12 字节,编成 12 组%XX即 36 个字符;- 空格 1 个,encodeURI 输出
%20(换成表单口径则输出+)。
把结果拆开正好对上 74:28(未保留 ASCII)+ 7(保留 ASCII)+ 36(四个汉字)+ 3(空格的 %20)= 74。同一串文字换口径的结果如下:
- encodeURI → 74 字符,7 个分隔符保留,空格仍是
%20; - encodeURIComponent → 90 字符,连
:/?=&一起编码,整条变成https%3A%2F%2Fliuquan.net%2Fsearch%3Fq%3D...; - 表单口径 → 88 字符,与上一档几乎相同,唯一差别是空格从
%20变成+(少 2 个字符); - escape → 72 字符,汉字走 UCS-2 老路,
柳输出%u67F3——这不是 RFC 3986 的百分号编码,只能被unescape()自己读懂。
下面就是默认口径(encodeURI)的真实输出,也是打开页面时结果框里显示的内容:
https://liuquan.net/search?q=%E6%9F%B3%E6%B3%89%20%E5%B7%A5%E5%85%B7&n=100
字符分类与四种口径对照表
下表按 RFC 3986 的分类列出典型字符,四种口径的输出来自浏览器原生函数实测。看这张表比记规则快:同一列里出现原样字符越多,说明这一档保留得越宽;+ 只出现在表单那一列,是它唯一独有的写法。
| 字符 | 码点 | RFC 3986 分类 | encodeURIComponent | encodeURI | 表单口径 | escape |
|---|---|---|---|---|---|---|
| (空格) | U+0020 | 三类之外,必须编码 | %20 | %20 | + | %20 |
| : | U+003A | gen-delims | %3A | : | %3A | %3A |
| / | U+002F | gen-delims | %2F | / | %2F | / |
| ? | U+003F | gen-delims | %3F | ? | %3F | %3F |
| # | U+0023 | gen-delims | %23 | # | %23 | %23 |
| [ | U+005B | gen-delims(IPv6 用) | %5B | %5B | %5B | %5B |
| @ | U+0040 | gen-delims | %40 | @ | %40 | @ |
| & | U+0026 | sub-delims | %26 | & | %26 | %26 |
| = | U+003D | sub-delims | %3D | = | %3D | %3D |
| + | U+002B | sub-delims | %2B | + | %2B | + |
| ! | U+0021 | sub-delims(RFC 2396 遗留 safe) | ! | ! | %21 | %21 |
| ' | U+0027 | sub-delims(RFC 2396 遗留 safe) | ' | ' | %27 | %27 |
| ~ | U+007E | unreserved | ~ | ~ | %7E | ~ |
| - | U+002D | unreserved | - | - | - | - |
| 柳 | U+67F3 | 非 ASCII,UTF-8 3 字节 | %E6%9F%B3 | %E6%9F%B3 | %E6%9F%B3 | %u67F3 |
| 😀 | U+1F600 | 非 ASCII,UTF-8 4 字节 | %F0%9F%98%80 | %F0%9F%98%80 | %F0%9F%98%80 | %uD83D%uDE00 |
| % | U+0025 | 百分号本身 | %25 | %25 | %25 | %25 |
换算出字符个数也很快:柳 是 1 个码点变 3 个字节变 9 个字符,😀 是 1 个码点变 4 个字节变 12 个字符。这也是为什么一条全中文的链接编码后长度常常是原文的 3 倍,而表情会到 3 倍以上——😀 在 UTF-16 里本来就是 2 个码元,按 String.length 算是原文 2 个字符、编码后 12 个字符,正好 6 倍。把这层关系记住,URL编码结果有多长就能口算了:未保留字符按 1 个算,汉字按 9 个算,表情按 12 个算。
输入范围、边界与精度说明
- 单个输入上限 20000 个码点,按 UTF-8 字节算最多允许约 80000 字节。超过之后页面会明确报错而不是静默截断——截断会让
%XX落在半个序列上,比报错危险得多。 - 明细表最多渲染 3000 行。再多只给汇总数字,不建表,避免一次插入几万个单元格把标签页卡死;行数超过 12 时用「加载更多」分页,每次 12 行。
- 汉字固定 3 字节、表情固定 4 字节,这是 UTF-8 的分档,与实际字体、显示宽度无关。生僻字如果落在补充平面(如 U+20BB7),同样是 4 字节 12 字符。
- 孤立代理会报错。如果输入里有一个不成对的 UTF-16 代理码元(例如从截断的字节流里复制出来的半个表情),
encodeURIComponent与encodeURI会抛URIError,本页也照样给出报错文案,而不是悄悄把它编成%ED%A0%BD这种非法序列。 - 解码对非法序列同样报错。
%后面不足两位十六进制(如%2)、十六进制字符非法(%ZZ)、字节序列不完整(%E6%9F)、过长编码(%C0%80)、代理区(%ED%A0%80)都会让decodeURIComponent抛URIError,实测这五类全部命中,本页逐类给出提示。 - 百分号自己也要编码。原文里写了字面量
100%,编码后是100%25;如果对已经是编码态的结果再编一次,%20会变成%2520,再编一次变成%252520——每编一层就多一截25,这是「双重编码」的典型症状。 - 本页不做零填充以外的规范化。输入里的大小写十六进制(
%e6与%E6)解码结果相同,但编码输出统一为大写。RFC 3986 明确HEXDIG是大写,所以 URL编码的输出不会出现小写形式,也不会做「把 %2F 和 / 视作等价」这类归一化——那属于 URI 规范化,不在本页范围内。
常见问题
同一个空格,到底该编成 %20 还是 +?
看这个空格出现在哪一层。RFC 3986 的 URI 里只有 %20 是合法的百分号编码,path 段、fragment 里都必须用 %20。而 application/x-www-form-urlencoded 是表单提交时的序列化格式,它规定空格写成 +,所以表单 body 和 URLSearchParams 输出的是 +。query 段两者都能见到:如果服务端按表单规则解析,+ 就是空格;如果按 RFC 3986 解析,+ 是 sub-delims 里的字面加号。
为什么用 encodeURI 编码参数值会把接口调用弄坏?
因为 encodeURI 保留了 82 个字符,其中包含 & = + ? #。当参数值本身含 & 时,它不会被编码,服务端就会把后半段当成另一个参数——这就是参数注入。参数值请一律用 encodeURIComponent,它会把 & 编成 %26、= 编成 %3D。
encodeURIComponent 到底保留哪些字符?
一共 71 个:26 个大写字母、26 个小写字母、10 个数字、- . _ ~,再加 ! ' ( ) *。前 66 个是 RFC 3986 的 unreserved,后 5 个是 RFC 2396 时代留下的 mark 集合,放进 URL 本身没问题,但若服务端对单引号做转义处理(例如拼进 SQL),仍建议再手工把 ' 换成 %27。
为什么结果里出现了 %25,我明明只编了一次?
因为原文里就有字面量百分号。% 不是未保留字符,必须编成 %25,所以 100% 变成 100%25。如果结果是 %2520 而不是 %20,那才是编了两遍;把 %25 再编一次会得到 %2525,多出的每一层都是一个 25。
用 escape 得到 %u67F3,为什么别的系统解不开?
因为 %uXXXX 不是 RFC 3986 定义的百分号编码。它是 ECMAScript 早期为 UCS-2 设计的遗留写法,正式规范里只在 Annex B.2.1 里描述,被认为应当废弃;只有 unescape() 与少数老系统认得。标准做法是把字符按 UTF-8 编成字节再写 %XX,也就是本页其余三档的输出。新代码不要再用 escape。
编码会不会因为语言环境不同而结果不一样?
不会。RFC 3986 只规定字节级转义,具体把字符折成哪些字节由字符编码决定;现代 Web 场景统一用 UTF-8(RFC 3629),所以「中」在任何浏览器、任何系统上都是 %E4%B8%AD。早期有人用 GBK 编中文得到 %D6%D0,那是非标准做法,现在遇到这种串需要先按 GBK 解码再按 UTF-8 重编。
明细表的「UTF-8 字节」和「当前口径 %XX」为什么有的行一样、有的不一样?
一样说明这个字节必须编码。不一样的情况有两种:一是该字符是未保留字符(如 a、-),字节照给但 %XX 一列显示原字符;二是选了 escape,汉字那一行会显示 %u67F3 而不是三组 %XX,因为 escape 不按字节走而是按 UTF-16 码元走。
我输入的文本会被发到服务器吗?
不会。编码与解码分别调用浏览器内建的 encodeURIComponent / decodeURIComponent 与页面内那份 UTF-8 编码函数,全部在当前标签页完成,没有任何网络请求,也不写浏览器存储。想验证可以打开开发者工具的 Network 面板,点几次「编码」观察是否有新请求。
相关工具
- URL解码:只做反向还原,适合手里已经有一串
%E6%9F%B3想读成中文的场景。 - Query字符串解析:把
a=1&b=2拆成键值表,看完每个参数的值再决定要不要编码。 - URL解析:拆出协议、主机、端口、路径、查询、片段六段,和本页的字节级处理互补。
- HTML实体编码:把
&<这类字符转成实体,用于放进 HTML 而不是放进 URL。 - Unicode转义反转义工具:处理
\u67F3形式的转义,和escape的%u67F3是两套东西。 - Base64编码:把任意字节装进 ASCII 文本,用于放进 URL 前一般还要再做一次 percent-encoding。
- Punycode转换器:把中文域名转成
xn--开头的主机名,这是主机名那一层的事。 - 编码解码:查看全部编码解码工具。