基于异或的简易校验和算法
背景介绍
校验和 (Checksum) 是一种在数据通信和存储中广泛使用的错误检测技术。通过对一段数据进行计算得出一个固定长度的值(即校验和),可以用来验证数据在传输或存储过程中是否被意外篡改。如果接收方计算出的校验和与发送方提供的不一致,就说明数据可能已损坏。
本题要求你实现一种特定的、基于异或 (XOR) 运算的简易校验和算法。
核心概念
-
网络报文 (Packet):
- 输入的数据是一段网络报文,以十六进制字符串的形式给出。
- 每 2 个十六进制字符代表 1 个字节。例如,字符串
"61"代表十六进制值0x61。
-
4字节块 (Word):
- 本算法的核心操作单元是 4 字节(32位)的块。所有计算都将在这些块之间进行。
校验和计算规则
整个计算过程分为两个主要阶段:
第一阶段:数据补齐 (Padding)
- 首先,检查输入报文的字节长度是否为 4 的倍数。
- 如果不是,需要在报文的尾部用值为
0xFF的字节进行补齐,直到总字节长度成为 4 的倍数。 - 最多补充 3 个字节。
示例:
- 原始报文
"616263"(3字节),不是4的倍数,补齐 1 个FF->"616263FF"。 - 原始报文
"6162636465"(5字节),不是4的倍数,补齐 3 个FF->"6162636465FFFFFF"。
第二阶段:异或折叠 (XOR Folding)
-
将补齐后的字节流,从头到尾分割成若干个 4 字节的块。设共有
n个块。 -
如果
n = 1:- 那么这个唯一的 4 字节块本身就是最终的校验和。
-
如果
n > 1:- 取出第 1 个块和第 2 个块,对它们进行按位异或 (XOR) 运算。
- 然后,将上一步的运算结果与第 3 个块继续进行异或运算。
- 重复此过程,直到所有块都被异或完毕。最后得到的结果即为校验和。
- 公式:
Checksum = Block_1 XOR Block_2 XOR Block_3 ... XOR Block_n
解答要求
- 时间限制: 1000ms
- 内存限制: 256MB
输入格式
-
一个十六进制字符串
expression。1 <= expression.length <= 200,且长度为偶数。- 字符串仅由字符
0-9和大写字母A-F组成。
输出格式
-
一个十六进制字符串,表示最终计算出的校验和。
-
格式要求:
- 字母使用大写 (A-F)。
- 固定为 8 个字符长度。如果计算结果不足 8 个十六进制字符(例如 "A9"),则需要在前面补充前导 0(例如 "000000A9")。
样例
输入样例 1
"61626364323031324C6162"
输出样例 1
"1F3330A9"
样例 1 解释:
-
原始报文:
"61626364323031324C6162",字节长度为 11。 -
步骤 1 - 补齐:
- 长度 11 不是 4 的倍数,需要补齐到 12 字节。
- 补充
1个0xFF字节。 - 补齐后报文:
"61626364323031324C6162FF"
-
步骤 2 - 异或计算:
-
将报文分为 3 个 4字节块:
块 1:0x61626364块 2:0x32303132块 3:0x4C6162FF
-
进行计算:
(块1 XOR 块2)=0x61626364 XOR 0x32303132=0x53525256(上一步结果 XOR 块3)=0x53525256 XOR 0x4C6162FF=0x1F3330A9
-
-
最终输出:
- 计算结果为
0x1F3330A9,转换为 8 位大写十六进制字符串即为"1F3330A9"。
- 计算结果为
输入样例 2
"41"
输出样例 2
"41FFFFFF"
解释:
- 输入报文长度为 1 字节。
- 补齐到 4 字节,需要补充 3 个
0xFF字节,得到"41FFFFFF"。 - 此时只有 1 个 4字节块,它本身就是校验和。
输入样例 3
"687561776569"
输出样例 3
"0D1C9E88"
解释:
- 输入长度 6 字节,补齐 2 个
FF->"687561776569FFFF"。 - 分为两个块:
0x68756177和0x6569FFFF。 0x68756177 XOR 0x6569FFFF=0x0D1C9E88。- 结果的十六进制表示是
"D1C9E88",长度为 7。根据输出要求,前面补充一个前导 0,变为"0D1C9E88"。
import java.util.Arrays;
/**
* 实现一个简易的校验和(Checksum)算法.
*/
public class ChecksumCalculator {
/**
* 主方法,计算给定网络报文的校验和.
* @param hexMessage 十六进制字符串表示的网络报文
* @return 8位大写的十六进制字符串形式的校验和
*/
public String calculateChecksum(String hexMessage) {
// --- 步骤 1: 解析输入并进行填充 ---
// 将十六进制字符串转换为字节数组
byte[] originalBytes = hexStringToByteArray(hexMessage);
// 计算需要补充的字节数
int paddingCount = 0;
if (originalBytes.length % 4 != 0) {
paddingCount = 4 - (originalBytes.length % 4);
}
// 创建填充后的新字节数组
byte[] paddedBytes = new byte[originalBytes.length + paddingCount];
// 复制原始字节
System.arraycopy(originalBytes, 0, paddedBytes, 0, originalBytes.length);
// 填充 0xFF
for (int i = 0; i < paddingCount; i++) {
paddedBytes[originalBytes.length + i] = (byte) 0xFF;
}
// --- 步骤 2: 计算校验和 ---
// 如果填充后只有一个4字节的块,那么它本身就是校验和
if (paddedBytes.length == 4) {
int checksum = bytesToInt(paddedBytes, 0);
return formatChecksum(checksum);
}
// 如果有多个块,则进行累积异或运算
// 初始化校验和为第一个4字节块的值
int checksum = bytesToInt(paddedBytes, 0);
// 从第二个4字节块开始,依次进行异或运算
for (int i = 4; i < paddedBytes.length; i += 4) {
int nextChunk = bytesToInt(paddedBytes, i);
checksum ^= nextChunk; // checksum = checksum ^ nextChunk
}
// --- 步骤 3: 格式化并返回结果 ---
return formatChecksum(checksum);
}
/**
* 辅助方法:将十六进制字符串转换为字节数组.
* @param s 待转换的十六进制字符串
* @return 转换后的字节数组
*/
private byte[] hexStringToByteArray(String s) {
int len = s.length();
// 一个字节由两个十六进制字符表示
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
// 将两个十六进制字符(如 "6A")合并成一个字节
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i + 1), 16));
}
return data;
}
/**
* 辅助方法:从字节数组的指定位置读取4个字节,并将其转换为一个32位整数 (int).
* 采用大端序(Big-Endian),即高位字节在前。
* @param bytes 源字节数组
* @param offset 起始偏移量
* @return 转换后的整数
*/
private int bytesToInt(byte[] bytes, int offset) {
// (bytes[i] & 0xFF) 是为了将有符号的 byte 转换为无符号的 int,防止符号位扩展带来的问题
return ((bytes[offset] & 0xFF) << 24) |
((bytes[offset + 1] & 0xFF) << 16) |
((bytes[offset + 2] & 0xFF) << 8) |
((bytes[offset + 3] & 0xFF));
}
/**
* 辅助方法:将一个32位整数格式化为8位长度、大写的十六进制字符串.
* 一个16进制的字符表示半个字节,所以8位表示4个字节,正好是32位
* @param checksum 待格式化的整数校验和
* @return 格式化后的字符串
*/
private String formatChecksum(int checksum) {
// %08X 的含义:
// X: 以大写十六进制格式输出
// 8: 指定输出宽度为8位
// 0: 如果不足8位,在前面用 '0' 补齐
return String.format("%08X", checksum);
}
}