URL 编码
因为很多服务器只识别 ASCII 编码的字符,如果 URL 中包含中文等非 ASCII 字符就会产生乱码。出于兼容性考虑,就需要将编码转换为统一的格式再进行传输。
URL 编码规则
- 以下字符保持不变:
[A, Z]、[a, z]、[0, 9]、-、_、.、*; - 其他字符先转换为 UTF-8 编码,然后对每个字节以
%XX表示。如:”中“在 UTF-8 编码中 16 进制展示为0xe4b8ad,编码后%E4%B8%AD(URL 编码转换的结果总是大写)。
编码后的字符序列只包含 [A, Z]、[a, z]、[0, 9]、-、_、.、*、%。
Java 实现
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.io.UnsupportedEncodingException;
public class Main {
public static void main(String[] args) {
String encoded = null;
try {
// 编码
encoded = URLEncoder.encode("中 文 !", StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
System.out.println("编码后:" + encoded); /* %E4%B8%AD+%E6%96%87+%21 */
String decoded = null;
try {
// 解码
decoded = URLDecoder.decode(encoded, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
System.out.println("解码后:" + decoded); /* 中文! */
}
}
和标准的 URL 编码稍有不同,URLEncoder 把空格字符编码成 +,而现在的 URL 编码标准要求空格被编码为 %20,不过,服务器都可以处理这两种情况。
Base64 编码
Base64 编码的目的是把二进制数据变成文本格式的数据,这样在很多文本中就可以处理二进制数据。如:电子邮件协议就是文本协议,如果要在电子邮件中添加一个二进制文件,就可以用 Base64 对二进制文件进行编码,然后以文本的形式传送。
Base64 编码规则
- 把 3 byte(= 24 bit) 的二进制数据按 6 bit 一组,划分为 4 组,用 4 个 int 整数分别表示这 4 组数据;
- 然后查表,把 int 整数用索引对应到字符,得到编码后的字符串。
因为 6 bit 组成的整数的范围总是 [0, 63],所以,能用 64 个字符表示:
- 字符 [A, Z] 对应索引 [0, 25];
- 字符 [a, z] 对应索引 [26, 51];
- 字符 [0, 9] 对应索引 [52, 61];
- 最后两个索引 62、63 分别用字符
+、/表示。
Base64 编码可以把任意长度的二进制数据变为纯文本,且只包含 [A, Z]、[a, z]、[0, 9]、+、/、= 这些字符。
因为将 3 byte 的数据经过 Base64 编码后得到 4 byte 数据,所以说,它把原始数据的长度增加了 1/3。如果把 Base64 的 64 个字符编码表换成 32、48 或 58 个,就可以使用 Base32、Base48 和 Base58 编码。字符越少,编码的效率就会越低。
提问:如果要编码的数据不是 3 的倍数该怎么办?针对这种情况,需要在末尾补充零(即 Ox00),使其成为 3 的倍数再进行编码。编码后,在结尾加几个 = 表示补充了几个 0x00,解码的时候,去掉末尾补充的一个或两个 0x00 即可。
这个补充的 0 不会和字符 A 冲突吗?疑惑ing ...
针对 URL 的 Base64 编码
因为标准的 Base64 编码会出现 +、/ 和 =,所以不适合把 Base64 编码后的字符串放到 URL 中。在针对 URL 的 Base64 编码中,把 + 变成 -、/ 变成 _。
Java 实现
import java.util.Arrays;
import java.util.Base64;
public class Main {
public static void main(String[] args) {
/* 在 Java 中,二进制数据就是 byte[] 数组 */
byte[] input = new byte[] { 1, 2, 3, 4, 5, 6 };
// 编码
String encoded = Base64.getEncoder().encodeToString(input);
System.out.println(encoded); /* AQIDBAUG */
// 解码
byte[] decoded = Base64.getDecoder().decode(encoded);
System.out.println(Arrays.toString(decoded)); /* [1, 2, 3, 4, 5, 6] */
}
}
/*
默认情况下,编码结果字符序列长度为 4 的倍数(编码前补充了 0x00,编码后自然多了 =)。
可以用 withoutPadding() 去掉填充的 =。
注:以上两种情况对解码不会有任何影响,因为编码后的长度加上 = 总是 4 的倍数,所以即使不加 = 也可以计算出原始输入的 byte[]。
*/
class Main2 {
public static void main(String[] args) {
byte[] input = new byte[] { 1, 2, 3, 4 };
/* 编码1 */
String encoded = Base64.getEncoder().encodeToString(input);
/* 编码2 */
String encoded2 = Base64.getEncoder().withoutPadding().encodeToString(input);
System.out.println(encoded + "\n" + encoded2);
/* Output:
AQIDBA==
AQIDBA
*/
byte[] decoded = Base64.getDecoder().decode(encoded);
byte[] decoded2 = Base64.getDecoder().decode(encoded2);
System.out.println(Arrays.toString(decoded) + "\n" + Arrays.toString(decoded2));
}
}
/*
使用 getUrlEncoder() 获得针对 URL 的 Base64 编码数据,对应使用 getUrlDecoder() 解码
*/
class Main3 {
public static void main(String[] args) {
byte[] input = new byte[] { 1, 2, 127, 0 };
String encoded = Base64.getEncoder().encodeToString(input);
System.out.println(encoded); /* AQJ/AA== */
String encoded2 = Base64.getUrlEncoder().encodeToString(input);
System.out.println(encoded2); /* AQJ_AA== */
byte[] decoded = Base64.getUrlDecoder().decode(encoded2);
System.out.println(Arrays.toString(decoded));
}
}
参考
- 廖雪峰的官方网站:www.liaoxuefeng.com/wiki/125259…