欢迎光临
我们一直在努力

14个方法搞定URL编解码,表单和URI组件两套语义分开

📌 高并发系统设计: 基于Redis与MQ的秒杀系统架构:库存预减、防超卖与限流防刷全流程解析

📋 目录

  • 一、工具类功能
  • 二、所需环境
  • 三、方法与调用示例(按需调用)
  • 四、工具类源码(可直接复制)

一、工具类功能

把表单语义(空格编成 +)与 URI 组件语义(空格编成 %20、+ 编成 %2B)两套编解码分开命名,再给查询串拼装解析与开放平台签名原文拼接。

二、所需环境

JDK 17或以上

三、方法与调用示例(按需调用)

🔢 方法序号:1

🎯 方法用途:

  • 按表单语义(application/x-www-form-urlencoded)做 UTF-8 编码,空格编成 +
  • 提交表单、拼老开放平台的签名串这类场景用它,与浏览器的编码规则一致

🧩 方法定义:public static String encode(String content)

🗣️ 入参解释:public static String encode(待编码内容)

📤 返回值解释:String,表单语义的编码结果

💡 边界口径:

  • 保留字符由 JDK URLEncoder 决定:* – _ . 不编码、~ 编成 %7E、/ 编成 %2F,与前端 encodeURIComponent 不完全一致
  • 它不判断是否已经编过码,重复编码会得到 %2520 这种二次编码
  • 内容为 null 抛 IllegalArgumentException

🧪 方法示例:

  • encode("门店 A+B 有 空 格") 拿到 %E9%97%A8%E5%BA%97+A%2BB+%E6%9C%89+%E7%A9%BA+%E6%A0%BC
  • encode(null) 抛 IllegalArgumentException: 待编码内容不能为null

🔢 方法序号:2

🎯 方法用途:

  • 表单语义按指定字符集编码,对接只收 GBK 的老系统时用
  • 编与解必须成对使用同一个字符集,这一版把编码显式写出来

🧩 方法定义:public static String encode(String content, Charset charset)

🗣️ 入参解释:public static String encode(待编码内容, 字符集)

📤 返回值解释:String,指定字符集下的表单编码结果

💡 边界口径:

  • 同一句中文在 GBK 下是 2 字节一组、UTF-8 下是 3 字节一组,编码结果完全不同
  • charset 传 null 抛 IllegalArgumentException: 字符集不能为null
  • 接收方的 URIEncoding 与 Nginx 重编码都可能改动字符集,跨端要核对

🧪 方法示例:

  • encode("门店 A+B 有 空 格", Charset.forName("GBK")) 拿到 %C3%C5%B5%EA+A%2BB+%D3%D0+%BF%D5+%B8%F1
  • 同一句按默认 UTF-8 编是 %E9%97%A8%E5%BA%97+A%2BB+%E6%9C%89+%E7%A9%BA+%E6%A0%BC,两种结果互不通用

🔢 方法序号:3

🎯 方法用途:

  • 按 URI 组件语义(RFC 3986)做 UTF-8 编码,空格编成 %20、+ 编成 %2B
  • 要塞进 path 片段或 query 值里的内容用它,不会被接收方当成字面加号

🧩 方法定义:public static String encodeComponent(String content)

🗣️ 入参解释:public static String encodeComponent(待编码内容)

📤 返回值解释:String,URI 组件语义的编码结果

💡 边界口径:

  • 与表单语义的差别就两处:空格是 %20 而不是 +,+ 必须编成 %2B
  • 空串是合法输入,编出来还是空串
  • 把表单编码结果当组件拼进 URL,接收方按 RFC 3986 解析时 + 就是字面加号,签名类场景会直接对不上

🧪 方法示例:

  • encodeComponent("门店 A+B 有 空 格") 拿到 %E9%97%A8%E5%BA%97%20A%2BB%20%E6%9C%89%20%E7%A9%BA%20%E6%A0%BC
  • encodeComponent(""") 空串拿到空串
  • encodeComponent("商品/开票明细") 拼进 path 后是 https://img.example.com/%E5%95%86%E5%93%81%2F%E5%BC%80%E7%A5%A8%E6%98%8E%E7%BB%86.xlsx,斜杠也被编成 %2F

🔢 方法序号:4

🎯 方法用途:

  • URI 组件语义按指定字符集编码
  • 老系统既要求组件语义又只认 GBK 时用这一版

🧩 方法定义:public static String encodeComponent(String content, Charset charset)

🗣️ 入参解释:public static String encodeComponent(待编码内容, 字符集)

📤 返回值解释:String,指定字符集下的组件编码结果

💡 边界口径:

  • 与组件语义 UTF-8 版只差字节生成规则,%20/%2B 口径不变
  • 字符集为 null 抛 IllegalArgumentException

🧪 方法示例:

  • encodeComponent("门店 A+B 有 空 格", Charset.forName("GBK")) 拿到 %C3%C5%B5%EA%20A%2BB%20%D3%D0%20%BF%D5%20%B8%F1

🔢 方法序号:5

🎯 方法用途:

  • 按表单语义做 UTF-8 解码,+ 按空格还原
  • 解表单、解老开放平台回传的查询串用它

🧩 方法定义:public static String decode(String content)

🗣️ 入参解释:public static String decode(已编码内容)

📤 返回值解释:String,解码后的原文

💡 边界口径:

  • 百分号转义非法(%zz、结尾只剩 a%2)抛 IllegalArgumentException,消息点明是解码而非组件解码
  • 内容为 null 抛 IllegalArgumentException

🧪 方法示例:

  • decode("%E9%97%A8%E5%BA%97+A%2BB+%E6%9C%89+%E7%A9%BA+%E6%A0%BC") 拿到 门店 A+B 有 空 格
  • decode("a+b") 拿到 a b

🔢 方法序号:6

🎯 方法用途:

  • 表单语义按指定字符集解码
  • 收 GBK 系统回传的串时用,字符集要和对方编码时一致

🧩 方法定义:public static String decode(String content, Charset charset)

🗣️ 入参解释:public static String decode(已编码内容, 字符集)

📤 返回值解释:String,解码后的原文

💡 边界口径:

  • 字符集用错不会报错:JDK 解码器默认 REPLACE,解不出的字节换成 U+FFFD
  • 所以 GBK 串按 UTF-8 硬解会得到一半乱码字符,成对约定仍要靠调用方
  • 百分号转义非法抛 IllegalArgumentException

🧪 方法示例:

  • decode("%C3%C5%B5%EA+A%2BB+%D3%D0+%BF%D5+%B8%F1", Charset.forName("GBK")) 拿到 门店 A+B 有 空 格
  • 同一串按 UTF-8 硬解,16 个字符里有 8 个 U+FFFD 替换字符

🔢 方法序号:7

🎯 方法用途:

  • 按 URI 组件语义解码,+ 保持字面加号
  • 解 path 片段、query 值时用它,和表单解码不会互相冒充

🧩 方法定义:public static String decodeComponent(String content)

🗣️ 入参解释:public static String decodeComponent(已编码内容)

📤 返回值解释:String,解码后的原文

💡 边界口径:

  • 与表单解码的唯一区别是 + 不还原成空格
  • 半截转义(a%2)抛 IllegalArgumentException,消息是「URL组件解码失败(非法百分号转义)」

🧪 方法示例:

  • decodeComponent("%E9%97%A8%E5%BA%97%20A%2BB%20%E6%9C%89%20%E7%A9%BA%20%E6%A0%BC") 拿到 门店 A+B 有 空 格
  • decodeComponent("a+b") 拿到 a+b
  • 同一输入 decode("a+b") 拿到的是 a b,两种语义互不通用

🔢 方法序号:8

🎯 方法用途:

  • URI 组件语义按指定字符集解码
  • 组件编码加 GBK 这一组合的老系统用它解

🧩 方法定义:public static String decodeComponent(String content, Charset charset)

🗣️ 入参解释:public static String decodeComponent(已编码内容, 字符集)

📤 返回值解释:String,解码后的原文

💡 边界口径:

  • 口径同方法7,只是字节按传入字符集还原
  • 字符集为 null 抛 IllegalArgumentException

🧪 方法示例:

  • decodeComponent("%C3%C5%B5%EA%20A%2BB%20%D3%D0%20%BF%D5%20%B8%F1", Charset.forName("GBK")) 拿到 门店 A+B 有 空 格

🔢 方法序号:9

🎯 方法用途:

  • 把参数 Map 拼成查询串,按 key 字典序、值做组件编码、null 值整个跳过
  • 拼回调地址、请求 URL 后面那段查询串,不用自己拼 & 再逐个编码

🧩 方法定义:public static String encodeQuery(Map<String, ?> params)

🗣️ 入参解释:public static String encodeQuery(参数 Map)

📤 返回值解释:String,形如 k1=v1&k2=v2 的查询串

💡 边界口径:

  • 值是集合或数组时展开成同名多参数,例如 tags=a&tags=b
  • 值为 null 的项整个跳过,不输出 key=;键为 null 的项同样跳过
  • 默认按 key 字典序、UTF-8 编码,空 Map 得到空串
  • 参数 Map 为 null 抛 IllegalArgumentException

🧪 方法示例:

  • encodeQuery(params) 拿到 amount=128.5&merchantId=M8001&shopName=%E9%97%A8%E5%BA%97%20A%2BB&tags=%E7%94%9F%E9%B2%9C&tags=24h,shopName 里的空格是 %20
  • encodeQuery(new LinkedHashMap<>()) 空 Map 拿到空串
  • 参数表里放一个 null 键,拿到 k=v,这一项被跳过

🔢 方法序号:10

🎯 方法用途:

  • 拼查询串时指定字符集
  • 接收方按 GBK 解参数就按 GBK 编,保持成对

🧩 方法定义:public static String encodeQuery(Map<String, ?> params, Charset charset)

🗣️ 入参解释:public static String encodeQuery(参数 Map, 字符集)

📤 返回值解释:String,指定字符集下的查询串

💡 边界口径:

  • 除字节生成规则外,排序与跳过 null 值的口径同方法9
  • 参数 Map 为 null 抛 IllegalArgumentException

🧪 方法示例:

  • encodeQuery(params, Charset.forName("GBK")) 拿到 amount=128.5&merchantId=M8001&shopName=%C3%C5%B5%EA%20A%2BB&tags=%C9%FA%CF%CA&tags=24h

🔢 方法序号:11

🎯 方法用途:

  • 拼查询串时选择是否排序,sorted=false 就保持传入顺序
  • 参数顺序本身有意义(老接口按顺序取参)时用这一版

🧩 方法定义:public static String encodeQuery(Map<String, ?> params, Charset charset, boolean sorted)

🗣️ 入参解释:public static String encodeQuery(参数 Map, 字符集, 是否排序)

📤 返回值解释:String,查询串

💡 边界口径:

  • sorted=false 时按 Map 的迭代顺序输出,配 LinkedHashMap 就能控制顺序
  • 多值展开与 null 值跳过口径不变
  • 参数 Map、字符集任一为 null 抛 IllegalArgumentException

🧪 方法示例:

  • encodeQuery(params, StandardCharsets.UTF_8, false) 拿到 merchantId=M8001&shopName=%E9%97%A8%E5%BA%97%20A%2BB&tags=%E7%94%9F%E9%B2%9C&tags=24h&amount=128.5,amount 排到了最后

🔢 方法序号:12

🎯 方法用途:

  • 把查询串解析成「键到值列表」的多值参数表,键按出现顺序保留
  • 收到的 URL 参数要原样读出来(含重复键)时用它,split("&") 手切会漏掉多值和编码

🧩 方法定义:public static Map<String, List<String>> parseQuery(String query)

🗣️ 入参解释:public static Map<String, List<String>> parseQuery(查询串)

📤 返回值解释:Map<String, List<String>>,键到值列表,每个值列表不可变

💡 边界口径:

  • 按表单语义解码,所以 + 会还原成空格
  • 没有等号的裸参数(key)取值为一个空串,不会没有这个键
  • 与 encodeQuery 互为逆运算
  • 查询串为 null 抛 IllegalArgumentException: 查询串不能为null

🧪 方法示例:

  • parseQuery(query) 的键顺序拿到 [amount, merchantId, shopName, tags]
  • 多值参数 tags 拿到 [生鲜, 24h]
  • 含空格的 shopName 拿到 [门店 A+B]
  • parseQuery("key&flag=") 拿到键 [key, flag],两项的取值都是一个空串

🔢 方法序号:13

🎯 方法用途:

  • 按指定字符集解析查询串里的 %XX 字节
  • 老系统用 GBK 编的参数,用它才解得出中文

🧩 方法定义:public static Map<String, List<String>> parseQuery(String query, Charset charset)

🗣️ 入参解释:public static Map<String, List<String>> parseQuery(查询串, 字符集)

📤 返回值解释:Map<String, List<String>>,多值参数表

💡 边界口径:

  • 只有 %XX 字节的解释跟随传入字符集,+ 与空值口径同方法12
  • 字符集传错不会抛异常,会得到替换字符,成对约定仍要靠调用方

🧪 方法示例:

  • parseQuery(gbkQuery, Charset.forName("GBK")) 里 shopName 拿到 [门店 A+B]

🔢 方法序号:14

🎯 方法用途:

  • 拼开放平台的签名原文:按 key 字典序拼 k1=v1&k2=v2,不做 URL 编码
  • 对接微信、支付宝、钉钉这类要拼签名原文的网关用它,比拿查询串去签名可靠

🧩 方法定义:public static String buildSignSource(Map<String, ?> params)

🗣️ 入参解释:public static String buildSignSource(参数 Map)

📤 返回值解释:String,签名原文,值保持字面量

💡 边界口径:

  • 空值与 null 值参数按微信、支付宝、钉钉的惯例跳过,不参与签名
  • 值里的中文和空格都是字面量,不做任何编码
  • 拿 encodeQuery 的结果去签名,参数里只要有中文或空格就必然验签失败
  • 参数 Map 为 null 抛 IllegalArgumentException

🧪 方法示例:

  • buildSignSource(params) 拿到 amount=128.50&appId=wx8001&nonce=abc def,带空格的值仍是字面空格
  • 同一份参数走 encodeQuery(params) 得到 amount=128.50&appId=wx8001&empty=&nonce=abc%20def,已经编成 %20

四、工具类源码(可直接复制)

📦 源码文件

赞(0)
未经允许不得转载:171主机测评 » 14个方法搞定URL编解码,表单和URI组件两套语义分开
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址