为什么需要 URL 编码?
URL(统一资源定位符)只能使用 ASCII 字符集中的一个有限子集,这个子集包括:
- 英文字母 (
a-z,A-Z) - 数字 (
0-9) - 特殊字符 (, ,
_, )
当 URL 中需要包含其他字符时(中文字符、空格、&, , 等),就必须对它们进行编码,编码的规则是:
- 将一个非 ASCII 字符或一个特殊字符转换为一个或多个字节。
- 每个字节都表示为 加上两个十六进制大写字母。
- 空格 (
`) 被编码为%20`。 - 中文字符 "中" 的 UTF-8 编码是
E4 B8 AD,所以它在 URL 中被表示为%E4%B8%AD。 - 字符
&被编码为%26。
注意:现代 URL 编码/解码的标准是 UTF-8,在 Java 中,我们默认就应该使用 UTF-8,除非有特殊的历史遗留原因。
Java 中的 URL 编码与解码方法
Java 提供了多种方式来处理 URL 编码和解码,我们主要关注最常用和最标准的几种。
java.net.URLEncoder 和 java.net.URLDecoder (已过时,但仍常见)
这是 Java 最早提供的工具类,主要用于对 HTML 表单的 application/x-www-form-urlencoded MIME 类型的数据进行编码和解码。
重要:这个类在 Java 9 之后被标记为 @Deprecated,因为它存在一些问题,并且有更好的替代方案,但在处理旧代码或特定场景时仍然会遇到。
编码方法:URLEncoder.encode(String s, String charset)
s: 要编码的字符串。charset: 字符集,强烈建议使用"UTF-8"。
解码方法:URLDecoder.decode(String s, String charset)
s: 要解码的字符串。charset: 字符集,强烈建议使用"UTF-8"。
特点:
- 它会编码所有非 ASCII 字符和很多特殊字符(如 , '+', 空格等)。
- 一个关键点:它将空格编码为 ,而不是
%20,这是 HTML 表单提交的传统做法。
示例代码:
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
public class OldURLEncoderExample {
public static void main(String[] args) {
String originalString = "Hello World! 你好 & 世界";
try {
// 1. 编码
String encodedString = URLEncoder.encode(originalString, "UTF-8");
System.out.println("原始字符串: " + originalString);
System.out.println("编码后字符串: " + encodedString);
// 输出: Hello+World%21+%E4%BD%A0%E5%A5%BD+%26+%E4%B8%96%E7%95%8C
// 注意:空格变成了 +
// 2. 解码
String decodedString = URLDecoder.decode(encodedString, "UTF-8");
System.out.println("解码后字符串: " + decodedString);
// 输出: Hello World! 你好 & 世界
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
java.net.URI (推荐,用于构建和解析 URL)
URI 类是处理 URL 的更现代、更健壮的方式,它提供了对 URL 各个部分(协议、主机、路径、查询参数等)的精确控制。
编码和解码主要通过其构造函数和 resolve() 方法自动完成,但如果你想手动对组件进行编码,可以使用 java.net.URI.create() 结合 java.net.URLEncoder。
URI 的编码规则:
- 它会将空格编码为
%20,而不是 ,这更符合 URL 的标准规范。 - 它会自动对路径、查询参数等进行编码。
示例代码:
import java.net.URI;
import java.net.URISyntaxException;
public class URIExample {
public static void main(String[] args) {
try {
String originalPath = "/search?q=Java 编码&lang=zh-CN";
String originalQuery = "q=你好 世界&user=张三";
// URI 构造函数会自动对路径和查询参数进行编码
URI uri = new URI(
"https", // scheme
"www.example.com", // host
originalPath, // path
originalQuery, // query
null // fragment
);
System.out.println("原始 Path: " + originalPath);
System.out.println("原始 Query: " + originalQuery);
System.out.println("构建的 URI: " + uri.toString());
// 输出: https://www.example.com/search?q=Java%20%E7%BC%96%E7%A0%81&lang=zh-CN?q=%E4%BD%A0%E5%A5%BD%20%E4%B8%96%E7%95%8C&user=%E5%BC%A0%E4%B8%89
// 解析 URI 的各个部分(URI 会自动解码)
System.out.println("解码后的 Path: " + uri.getPath());
System.out.println("解码后的 Query: " + uri.getQuery());
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
}
java.net.URL 和 java.net.HttpURLConnection (用于网络请求)
当你需要向服务器发送请求时,通常会使用 URL 和 HttpURLConnection,你需要手动构建查询字符串,这时 URLEncoder 就派上用场了。
最佳实践:使用 URLEncoder 对每个查询参数的值进行编码,然后将它们拼接成查询字符串。
示例代码:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
public class URLConnectionExample {
public static void main(String[] args) {
String baseUrl = "https://www.example.com/search";
String queryParam1 = "keyword";
String value1 = "Java 网络编程";
String queryParam2 = "user";
String value2 = "李四";
try {
// 1. 对每个查询参数的值进行编码
String encodedValue1 = URLEncoder.encode(value1, StandardCharsets.UTF_8.name());
String encodedValue2 = URLEncoder.encode(value2, StandardCharsets.UTF_8.name());
// 2. 构建完整的查询字符串
String queryString = String.format("%s=%s&%s=%s", queryParam1, encodedValue1, queryParam2, encodedValue2);
String fullUrl = baseUrl + "?" + queryString;
System.out.println("请求的完整 URL: " + fullUrl);
// 输出: https://www.example.com/search?keyword=Java+%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B&user=%E6%9D%8E%E5%9B%9B
// 3. 发送 HTTP 请求 (示例代码)
URL url = new URL(fullUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
int responseCode = connection.getResponseCode();
System.out.println("响应码: " + responseCode);
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String inputLine;
StringBuilder response = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
System.out.println("响应内容: " + response.toString());
} else {
System.out.println("GET 请求失败");
}
} catch (UnsupportedEncodingException e) {
System.err.println("不支持的字符编码: " + e.getMessage());
} catch (Exception e) {
e.printStackTrace();
}
}
}
Java 11+ 的 java.net.StandardCharsets 和 java.net.URI 的改进
从 Java 11 开始,URLEncoder 和 URLDecoder 引入了新的重载方法,不再需要指定字符集,默认使用 UTF-8,这大大减少了出错的可能性。
编码方法:URLEncoder.encode(String s)
解码方法:URLDecoder.decode(String s)
示例代码 (Java 11+):
import java.net.URLEncoder;
import java.net.URLDecoder;
public class ModernURLEncoderExample {
public static void main(String[] args) {
String originalString = "Modern Java 11+ 你好";
// Java 11+ 的编码方法,默认使用 UTF-8
String encodedString = URLEncoder.encode(originalString);
System.out.println("原始字符串: " + originalString);
System.out.println("编码后字符串: " + encodedString);
// 输出: Modern+Java+11%2B+%E4%BD%A0%E5%A5%BD
// 注意:+ 空格,%2B 是 +
// Java 11+ 的解码方法,默认使用 UTF-8
String decodedString = URLDecoder.decode(encodedString);
System.out.println("解码后字符串: " + decodedString);
// 输出: Modern Java 11+ 你好
}
}
总结与最佳实践
| 方法/类 | 主要用途 | 编码空格 | 状态 | 推荐场景 |
|---|---|---|---|---|
URLEncoder / URLDecoder |
HTML 表单数据编码/解码 | Java 9+ 已过时 | 处理旧代码或遗留系统。 仅用于编码查询参数的值,拼接成查询字符串。 |
|
java.net.URI |
构建、解析和规范化 URL | %20 |
推荐 | 构建 URL,特别是路径和查询部分。 解析 URL 的各个组件。 |
java.net.URL |
表示一个网络资源 | - | - | 代表一个资源,通常与 HttpURLConnection 配合使用发送请求。 |
Java 11+ URLEncoder |
HTML 表单数据编码/解码 | 现代且推荐 | 与旧版 URLEncoder 用途相同,但更安全,因为默认使用 UTF-8。 |
核心最佳实践:
- 优先使用
java.net.URI:当你需要从零开始构建一个 URL 时,URI是最安全、最清晰的选择,它会自动处理编码,并将空格编码为%20。 - 正确使用
URLEncoder:当你需要手动构建查询字符串(在发送 GET 请求前)时,只对查询参数的值进行编码,而不是对整个 URL 或键值对一起编码。// 错误示范 String wrongQuery = URLEncoder.encode("q=Java 编码", "UTF-8"); // 正确示范 String key = "q"; String value = "Java 编码"; String correctQuery = key + "=" + URLEncoder.encode(value, "UTF-8"); - 始终指定字符集:如果你使用的是 Java 10 或更早版本的
URLEncoder/URLDecoder,必须指定字符集,并且永远使用"UTF-8"。 - 了解空格的差异:
URLEncoder产生 ,而URI产生%20,根据你的上下文(是 HTML 表单还是标准 URL)来理解这一点。 - 拥抱 Java 11+:如果你的项目允许,升级到 Java 11 或更高版本,使用新的
URLEncoder.encode(String s)方法,它能有效避免因忘记指定字符集而导致的编码错误。
