学习方法与心得-易58-Base32编码与解码问题 | 豆包MarsCode AI刷题

132 阅读8分钟

自定义 Base32 编码与解码的实现与解析

一、问题描述

这道题目需要我们实现一个自定义的 Base32 编码和解码函数,具体要求如下:

  • 编码流程

    1. 字符串转换为二进制:将输入字符串按照 UTF-8 编码转换为字节数组,然后将每个字节转换为 8 位二进制字符串,拼接成完整的二进制串。

    2. 补齐位数:如果二进制数据的位数不是 5 的倍数,在末尾补 0,使其长度成为 5 的倍数。

    3. 分组与索引转换:将补齐后的二进制串以每 5 位为一组,转换为对应的索引值(0 - 31)。

    4. 索引映射为字符:根据索引-字符映射表,将每个索引值转换为对应的字符,拼接成编码字符串。

    5. 添加填充字符:根据原始二进制数据的位数除以 40 后的余数,确定末尾需要补 '+' 的数量:

      • 余数为 0:不需要补 '+'
      • 余数为 8:补 6 个 '+'
      • 余数为 16:补 4 个 '+'
      • 余数为 24:补 3 个 '+'
      • 余数为 32:补 1 个 '+'
  • 解码流程

    • 分割编码字符串:按照 '+' 分割编码字符串,得到多个编码块。
    • 逐块解码:对每个编码块,逆向执行编码过程,注意根据 '+' 的数量移除填充位,恢复原始的二进制数据,然后转换为字符串。
    • 拼接结果:将所有解码得到的字符串拼接,得到最终的解码结果。
  • 索引-字符映射表

    索引字符
    09
    18
    27
    36
    45
    54
    63
    72
    81
    90
    10m
    11n
    12b
    13v
    14c
    15x
    16z
    17a
    18s
    19d
    20f
    21g
    22h
    23j
    24k
    25l
    26p
    27o
    28i
    29u
    30y
    31t
  • 测试样例

    • 样例 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 位。
      • '+':不移除。
    • 二进制转换为字节

      将处理后的二进制串以每 8 位为一组,转换为字节数组。

    • 字节转换为字符串

      将字节数组按照 UTF-8 编码转换为字符串,得到解码块。

  • 拼接结果

    将所有解码块拼接起来,得到完整的解码字符串。

4. 实现要点

  • 字符编码:在编码和解码过程中,统一使用 UTF-8 字符集,确保字符转换的准确性。

  • 填充位的处理

    • 编码时,根据二进制串长度,补齐到 5 的倍数。
    • 解码时,根据每个编码块的 '+' 数量,正确计算需要移除的填充位数。
  • 大小写处理

    映射表中的字母都是小写的,在解码时需要将编码字符串转换为小写,确保字符匹配。

  • 异常处理

    在解码过程中,如果遇到不在映射表中的字符,需要抛出异常或进行错误处理,避免程序崩溃。

5. 示例分析

示例 1

  • 输入rawStr = "foo"

  • 编码过程

    1. 字符串转换为二进制

      "foo" 的 UTF-8 编码为:

      f: 01100110
      o: 01101111
      o: 01101111
      

      拼接得到二进制串:011001100110111101101111

    2. 补齐位数

      长度为 24 位,补齐到最近的 5 的倍数,需要补 10,得到 0110011001101111011011110,长度为 25 位。

    3. 分组与索引转换

      分组为:

      01100 (12)
      11001 (25)
      10111 (23)
      01101 (13)
      11100 (28)
      

      对应的索引为 [12, 25, 23, 13, 28]

    4. 索引映射为字符

      12 -> b
      25 -> l
      23 -> j
      13 -> v
      28 -> i
      

      编码字符串为 bljvi

    5. 添加填充字符

      原始数据位数为 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);
    }
}