自定义 Base32 编码与解码的实现与解析
一、问题描述
这道题目需要我们实现一个自定义的 Base32 编码和解码函数,具体要求如下:
-
编码流程:
-
字符串转换为二进制:将输入字符串按照 UTF-8 编码转换为字节数组,然后将每个字节转换为 8 位二进制字符串,拼接成完整的二进制串。
-
补齐位数:如果二进制数据的位数不是 5 的倍数,在末尾补
0,使其长度成为 5 的倍数。 -
分组与索引转换:将补齐后的二进制串以每 5 位为一组,转换为对应的索引值(0 - 31)。
-
索引映射为字符:根据索引-字符映射表,将每个索引值转换为对应的字符,拼接成编码字符串。
-
添加填充字符:根据原始二进制数据的位数除以 40 后的余数,确定末尾需要补
'+'的数量:- 余数为 0:不需要补
'+'。 - 余数为 8:补 6 个
'+'。 - 余数为 16:补 4 个
'+'。 - 余数为 24:补 3 个
'+'。 - 余数为 32:补 1 个
'+'。
- 余数为 0:不需要补
-
-
解码流程:
- 分割编码字符串:按照
'+'分割编码字符串,得到多个编码块。 - 逐块解码:对每个编码块,逆向执行编码过程,注意根据
'+'的数量移除填充位,恢复原始的二进制数据,然后转换为字符串。 - 拼接结果:将所有解码得到的字符串拼接,得到最终的解码结果。
- 分割编码字符串:按照
-
索引-字符映射表:
索引 字符 0 9 1 8 2 7 3 6 4 5 5 4 6 3 7 2 8 1 9 0 10 m 11 n 12 b 13 v 14 c 15 x 16 z 17 a 18 s 19 d 20 f 21 g 22 h 23 j 24 k 25 l 26 p 27 o 28 i 29 u 30 y 31 t -
测试样例:
-
样例 1:
- 输入:
rawStr = "foo",encodedStr = "b0zj5+++" - 输出:
"bljhy+++:bar"
- 输入:
-
样例 2:
- 输入:
rawStr = "The encoding process",encodedStr = "bljhy+++b0zj5+++" - 输出:
"maf3m164vlahyl60vlds9i6svuahmiod:foobar"
- 输入:
-
样例 3:
- 输入:
rawStr = "Base32 encoding and decoding",encodedStr = "bvchz+++v4j21+++cals9+++" - 输出:
"10zj3l0d31z3mod6vus3sod258zhil89bash3oo5v4j3c+++:c]hintts "
- 输入:
-
二、解题思路
1. 问题理解
我们的任务是按照特定的编码规则和字符映射表,实现自定义的 Base32 编码和解码函数。与标准的 Base32 不同,我们需要注意以下几点:
- 特定的索引-字符映射表,需要严格按照题目提供的表进行映射。
- 编码过程中需要在二进制数据末尾补
0,使其长度成为 5 的倍数。 - 编码结果可能需要在末尾添加
'+',具体数量根据原始数据位数除以 40 的余数确定。 - 解码时,需要根据
'+'的数量,正确移除填充位,确保数据还原准确。
2. 编码过程详解
-
字符串转换为二进制:
将输入字符串按照 UTF-8 编码转换为字节数组,然后将每个字节转换为 8 位二进制字符串,拼接成完整的二进制串。
-
补齐位数:
计算二进制串的长度,如果不是 5 的倍数,则在末尾补
0,使其长度成为 5 的倍数。 -
分组与索引转换:
将补齐后的二进制串以每 5 位为一组,分割为多个组。将每组 5 位二进制转换为对应的索引值(0 - 31)。
-
索引映射为字符:
根据索引-字符映射表,将每个索引值转换为对应的字符,拼接成编码字符串。
-
添加填充字符:
根据原始数据位数除以 40 的余数,确定需要在编码字符串末尾添加的
'+'数量。
3. 解码过程详解
-
分割编码字符串:
按照
'+'分割编码字符串,得到多个编码块。 -
逐块解码:
对于每个编码块,执行以下步骤:
-
字符到索引转换:
将编码块中的每个字符转换为对应的索引值。
-
索引转换为二进制:
将每个索引值转换为 5 位的二进制字符串,拼接成完整的二进制串。
-
移除填充位:
根据编码块末尾
'+'的数量,确定需要从二进制串末尾移除的填充位数。- 6 个
'+':移除 20 位。 - 4 个
'+':移除 16 位。 - 3 个
'+':移除 12 位。 - 1 个
'+':移除 8 位。 - 无
'+':不移除。
- 6 个
-
二进制转换为字节:
将处理后的二进制串以每 8 位为一组,转换为字节数组。
-
字节转换为字符串:
将字节数组按照 UTF-8 编码转换为字符串,得到解码块。
-
-
拼接结果:
将所有解码块拼接起来,得到完整的解码字符串。
4. 实现要点
-
字符编码:在编码和解码过程中,统一使用 UTF-8 字符集,确保字符转换的准确性。
-
填充位的处理:
- 编码时,根据二进制串长度,补齐到 5 的倍数。
- 解码时,根据每个编码块的
'+'数量,正确计算需要移除的填充位数。
-
大小写处理:
映射表中的字母都是小写的,在解码时需要将编码字符串转换为小写,确保字符匹配。
-
异常处理:
在解码过程中,如果遇到不在映射表中的字符,需要抛出异常或进行错误处理,避免程序崩溃。
5. 示例分析
示例 1:
-
输入:
rawStr = "foo" -
编码过程:
-
字符串转换为二进制:
"foo"的 UTF-8 编码为:f: 01100110 o: 01101111 o: 01101111拼接得到二进制串:
011001100110111101101111 -
补齐位数:
长度为 24 位,补齐到最近的 5 的倍数,需要补
1个0,得到0110011001101111011011110,长度为 25 位。 -
分组与索引转换:
分组为:
01100 (12) 11001 (25) 10111 (23) 01101 (13) 11100 (28)对应的索引为
[12, 25, 23, 13, 28] -
索引映射为字符:
12 -> b 25 -> l 23 -> j 13 -> v 28 -> i编码字符串为
bljvi -
添加填充字符:
原始数据位数为 24 位,除以 40 的余数为 24,需添加 3 个
'+',得到编码结果:bljvi+++
-
示例 2 和示例 3 也可按照上述步骤进行分析。
三、总结
在实现自定义的 Base32 编码和解码函数时,需要严格按照题目提供的规则进行编码和解码,请务必注意以下几点,我就在这上面卡了很久:
- 字符映射关系的准确性:确保索引和字符的映射表正确无误。
- 填充位的处理:在编码和解码时,正确计算并处理填充位,保证数据的完整性。
- 大小写敏感性:在处理字符映射时,统一转换为小写或大写,避免因大小写差异导致的错误。
- 异常处理:在解码过程中,做好非法字符的检查和异常处理,提高代码的健壮性。
四、参考代码
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.*;
import java.io.ByteArrayOutputStream;
public class CustomBase32 {
// 索引到字符的映射表
private static final char[] INDEX_TO_CHAR = {
'9', '8', '7', '6', '5', '4', '3', '2', '1', '0',
'm', 'n', 'b', 'v', 'c', 'x', 'z', 'a', 's', 'd',
'f', 'g', 'h', 'j', 'k', 'l', 'p', 'o', 'i', 'u', 'y', 't'
};
// 字符到索引的映射表
private static final Map<Character, Integer> CHAR_TO_INDEX = new HashMap<>();
static {
for (int i = 0; i < INDEX_TO_CHAR.length; i++) {
CHAR_TO_INDEX.put(INDEX_TO_CHAR[i], i);
}
}
// 编码函数
public static String encode(String rawStr) {
byte[] bytes = rawStr.getBytes(StandardCharsets.UTF_8);
// 将字节数组转换为二进制字符串
StringBuilder bitStringBuilder = new StringBuilder();
for (byte b : bytes) {
String bitString = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
bitStringBuilder.append(bitString);
}
String bitString = bitStringBuilder.toString();
// 计算需要补充的位数使其成为5的倍数
int paddingBits = (5 - (bitString.length() % 5)) % 5;
bitString += "0".repeat(paddingBits);
// 将二进制字符串按5位分组
StringBuilder encodedStringBuilder = new StringBuilder();
for (int i = 0; i < bitString.length(); i += 5) {
String group = bitString.substring(i, i + 5);
int index = Integer.parseInt(group, 2);
encodedStringBuilder.append(INDEX_TO_CHAR[index]);
}
// 根据原始数据的位数确定需要补充的'+'数量
int totalBits = bytes.length * 8;
int remainder = totalBits % 40;
String plusStr = "";
if (remainder == 8) {
plusStr = "++++++";
} else if (remainder == 16) {
plusStr = "++++";
} else if (remainder == 24) {
plusStr = "+++";
} else if (remainder == 32) {
plusStr = "+";
}
encodedStringBuilder.append(plusStr);
return encodedStringBuilder.toString();
}
// 解码函数
public static String decode(String encodedStr) {
// 使用正则表达式分割编码字符串,保留'+'号
Pattern pattern = Pattern.compile("([0-9a-z]+)(\++|$)");
Matcher matcher = pattern.matcher(encodedStr.toLowerCase());
StringBuilder decodedStringBuilder = new StringBuilder();
while (matcher.find()) {
String block = matcher.group(1);
String padding = matcher.group(2);
int plusCount = padding.length();
// 将字符转换为索引
StringBuilder bitStringBuilder = new StringBuilder();
for (char c : block.toCharArray()) {
Integer index = CHAR_TO_INDEX.get(c);
if (index == null) {
throw new IllegalArgumentException("待解码的字符串中包含非法字符: '" + c + "'");
}
String bitString = String.format("%5s", Integer.toBinaryString(index)).replace(' ', '0');
bitStringBuilder.append(bitString);
}
String bitString = bitStringBuilder.toString();
// 根据'+'号数量计算需要移除的填充位数
int paddingBits = 0;
switch (plusCount) {
case 6:
paddingBits = 20;
break;
case 4:
paddingBits = 16;
break;
case 3:
paddingBits = 12;
break;
case 1:
paddingBits = 8;
break;
default:
paddingBits = 0;
break;
}
if (paddingBits > 0 && paddingBits <= bitString.length()) {
bitString = bitString.substring(0, bitString.length() - paddingBits);
}
// 将二进制字符串转换回字节数组
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
for (int i = 0; i + 8 <= bitString.length(); i += 8) {
String byteString = bitString.substring(i, i + 8);
int byteValue = Integer.parseInt(byteString, 2);
byteArrayOutputStream.write(byteValue);
}
byte[] bytes = byteArrayOutputStream.toByteArray();
String decodedBlock = new String(bytes, StandardCharsets.UTF_8);
decodedStringBuilder.append(decodedBlock);
}
return decodedStringBuilder.toString();
}
// 主函数,用于测试
public static void main(String[] args) {
// 测试样例1
String rawStr1 = "foo";
String encodedStr1 = "b0zj5+++";
String encodedResult1 = encode(rawStr1);
String decodedResult1 = decode(encodedStr1);
System.out.println(encodedResult1 + ":" + decodedResult1);
// 测试样例2
String rawStr2 = "The encoding process";
String encodedStr2 = "bljhy+++b0zj5+++";
String encodedResult2 = encode(rawStr2);
String decodedResult2 = decode(encodedStr2);
System.out.println(encodedResult2 + ":" + decodedResult2);
// 测试样例3
String rawStr3 = "Base32 encoding and decoding";
String encodedStr3 = "bvchz+++v4j21+++cals9+++";
String encodedResult3 = encode(rawStr3);
String decodedResult3 = decode(encodedStr3);
System.out.println(encodedResult3 + ":" + decodedResult3);
}
}