简易变长编码规则
背景介绍
在计算机系统中,为了高效地存储和传输文本,我们经常使用变长编码方案。这种方案可以用较少的字节表示常用字符,用较多的字节表示不常用的字符。UTF-8 就是最著名的一种。
本题要求你实现一个名为 XUTF 的简易变长编码规则,将一个给定的字符编号值(unicodeVal)转换为其对应的字节序列。
XUTF 编码规则
1. 编码格式总览
XUTF 使用 1 到 6 个字节来表示一个字符,具体使用几个字节取决于字符编号 unicodeVal 的大小。下表定义了编码的格式:
| 字节数 | 编号值范围 (unicodeVal) | 二进制格式 (x 表示有效数据位) | 有效位数 |
|---|---|---|---|
| 1 字节 | [0, 2⁷) | 1xxxxxxx | 7位 |
| 2 字节 | [2⁷, 2¹¹) | 001xxxxx 01xxxxxx | 11位 (5+6) |
| 3 字节 | [2¹¹, 2¹⁶) | 0001xxxx 01xxxxxx 01xxxxxx | 16位 (4+6+6) |
| 4 字节 | [2¹⁶, 2²¹) | 00001xxx 01xxxxxx 01xxxxxx 01xxxxxx | 21位 (3+6+6+6) |
| 5 字节 | [2²¹, 2²⁶) | 000001xx 01xxxxxx 01xxxxxx 01xxxxxx 01xxxxxx | 26位 (2+6+6+6+6) |
| 6 字节 | [2²⁶, 2³¹) | 0000001x 01xxxxxx 01xxxxxx 01xxxxxx 01xxxxxx 01xxxxxx | 31位 (1+6*5) |
2. 规则文字说明
-
单字节字符 (n = 1):
- 字节的第 1 位固定为
1。 - 剩余的 7 位 (
xxxxxxx) 用于存放数据。
- 字节的第 1 位固定为
-
多字节字符 (n > 1):
- 首字节: 前
n位固定为0,第n+1位固定为1。 - 后续字节: 每个后续字节的前 2 位都固定为
01。 - 其余所有标记为
x的位都是有效位,用于存放unicodeVal的二进制数据。
- 首字节: 前
编码过程详解
- 确定字节数: 根据输入的
unicodeVal,对照上表,确定编码需要 1, 2, 3, 4, 5, 还是 6 个字节。 - 填充有效位: 将
unicodeVal的二进制表示,从右到左(从低位到高位)依次填入二进制格式中的x(有效位)。 - 处理高位: 如果
unicodeVal的位数不足以填满所有有效位,则在左侧(高位)的有效位补 0。
解答要求
- 时间限制: 1000ms
- 内存限制: 256MB
输入格式
- 一个十进制整数
unicodeVal。 0 <= unicodeVal < 2^31
输出格式
- 一个大写的十六进制字符串,表示该字符的
XUTF编码。 - 注意: 如果最终编码后的字节数为奇数个,导致十六进制字符串长度为奇数,则需要在最前面补充一个前导字符
'0',以确保输出长度为偶数。
样例
输入样例 1
21326
输出样例 1
"154D4E"
样例 1 解释:
-
确定字节数:
21326落在范围[2048, 65536)(即[2¹¹, 2¹⁶)) 内,所以需要 3个字节。 -
获取格式和有效位数: 3字节的格式为
0001xxxx 01xxxxxx 01xxxxxx,共有4 + 6 + 6 = 16个有效位。 -
转换
unicodeVal:21326的二进制表示是101001101001110,共 15 位。 -
填充: 我们需要 16 位有效数据,但只有 15 位,所以在左侧补一个 0,得到
0101001101001110。-
将其从高位到低位依次填入
x的位置:- 第一个字节的
xxxx->0101 - 第二个字节的
xxxxxx->001101 - 第三个字节的
xxxxxx->001110
- 第一个字节的
-
-
组合成字节:
- 字节1:
00010101-> 二进制00010101-> 十六进制15 - 字节2:
01001101-> 二进制01001101-> 十六进制4D - 字节3:
01001110-> 二进制01001110-> 十六进制4E
- 字节1:
-
最终结果: 拼接起来得到
"154D4E"。
输入样例 2
34
输出样例 2
"A2"
解释:
- 字节数:
34落在[0, 128)范围外,但34 < 2048,咦,样例解释有误。应该是落在[0, 2⁷)范围内。让我们重新检查范围。2⁷=128。0 <= 34 < 128。所以需要 1个字节。 - 格式:
1xxxxxxx - 转换:
34的二进制是100010,共 6 位。 - 填充: 有效位有 7 位,需在左侧补一个 0,得到
0100010。 - 组合:
10100010-> 二进制10100010-> 十六进制A2。
输入样例 3
1225859
输出样例 3
"0C6B5243"
解释:
- 字节数:
1225859落在[2¹⁶, 2²¹)范围内,需要 4个字节。 - 编码后: 计算得到的字节序列为
0C 6B 52 43。 - 原始十六进制字符串:
"C6B5243",长度为 7(奇数)。 - 格式化输出: 根据要求,前面补充一个前导
0,最终输出"0C6B5243"。
/**
* 实现一个自定义的XUTF变长字符编码器.
*/
public class XutfEncoder {
/**
* 对给定的unicode值进行XUTF编码.
* @param unicodeVal 十进制表示的字符编号值
* @return 编码后的大写十六进制字符串
*/
public String encode(int unicodeVal) {
// 最终编码生成的字节数组
byte[] resultBytes;
// --- 步骤 1: 根据 unicodeVal 的范围确定编码所需的字节数 ---
// 1字节编码: [0, 2^7)
if (unicodeVal < (1 << 7)) { // 128
resultBytes = new byte[1];
// 编码格式: 1xxxxxxx
// 固定位是 10000000 (0x80)
// 将 unicodeVal 的7个有效位与固定位合并
resultBytes[0] = (byte) (0x80 | unicodeVal);
}
// 2字节编码: [2^7, 2^11)
else if (unicodeVal < (1 << 11)) { // 2048
resultBytes = new byte[2];
// 格式: 001xxxxx 01xxxxxx
// byte 2: 固定位 01000000 (0x40), 有6个有效位
resultBytes[1] = (byte) (0x40 | (unicodeVal & 0x3F)); // 0x3F 是 00111111, 用于取最低6位
// byte 1: 固定位 00100000 (0x20), 有5个有效位
resultBytes[0] = (byte) (0x20 | (unicodeVal >> 6)); // 右移6位后,剩下的就是高位的有效部分
}
// 3字节编码: [2^11, 2^16)
else if (unicodeVal < (1 << 16)) { // 65536
resultBytes = new byte[3];
// 格式: 0001xxxx 01xxxxxx 01xxxxxx
// byte 3: 01xxxxxx
resultBytes[2] = (byte) (0x40 | (unicodeVal & 0x3F));
// byte 2: 01xxxxxx
resultBytes[1] = (byte) (0x40 | ((unicodeVal >> 6) & 0x3F));
// byte 1: 0001xxxx, 固定位 00010000 (0x10), 有4个有效位
resultBytes[0] = (byte) (0x10 | (unicodeVal >> 12));
}
// 4字节编码: [2^16, 2^21)
else if (unicodeVal < (1 << 21)) { // 2097152
resultBytes = new byte[4];
// 格式: 00001xxx 01xxxxxx 01xxxxxx 01xxxxxx
resultBytes[3] = (byte) (0x40 | (unicodeVal & 0x3F));
resultBytes[2] = (byte) (0x40 | ((unicodeVal >> 6) & 0x3F));
resultBytes[1] = (byte) (0x40 | ((unicodeVal >> 12) & 0x3F));
// byte 1: 00001xxx, 固定位 00001000 (0x08), 有3个有效位
resultBytes[0] = (byte) (0x08 | (unicodeVal >> 18));
}
// 5字节编码: [2^21, 2^26)
else if (unicodeVal < (1 << 26)) { // 67108864
resultBytes = new byte[5];
// 格式: 000001xx ...
resultBytes[4] = (byte) (0x40 | (unicodeVal & 0x3F));
resultBytes[3] = (byte) (0x40 | ((unicodeVal >> 6) & 0x3F));
resultBytes[2] = (byte) (0x40 | ((unicodeVal >> 12) & 0x3F));
resultBytes[1] = (byte) (0x40 | ((unicodeVal >> 18) & 0x3F));
// byte 1: 000001xx, 固定位 00000100 (0x04), 有2个有效位
resultBytes[0] = (byte) (0x04 | (unicodeVal >> 24));
}
// 6字节编码: [2^26, 2^31)
else { // unicodeVal < (1L << 31)
resultBytes = new byte[6];
// 格式: 0000001x ...
resultBytes[5] = (byte) (0x40 | (unicodeVal & 0x3F));
resultBytes[4] = (byte) (0x40 | ((unicodeVal >> 6) & 0x3F));
resultBytes[3] = (byte) (0x40 | ((unicodeVal >> 12) & 0x3F));
resultBytes[2] = (byte) (0x40 | ((unicodeVal >> 18) & 0x3F));
resultBytes[1] = (byte) (0x40 | ((unicodeVal >> 24) & 0x3F));
// byte 1: 0000001x, 固定位 00000010 (0x02), 有1个有效位
resultBytes[0] = (byte) (0x02 | (unicodeVal >> 30));
}
// --- 步骤 2: 将生成的字节数组转换为十六进制字符串 ---
return byteArrayToHexString(resultBytes);
}
/**
* 辅助方法:将字节数组转换为大写的十六进制字符串.
* @param bytes 待转换的字节数组
* @return 转换后的十六进制字符串
*/
private String byteArrayToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
for (byte b : bytes) {
// String.format("%02X", b) 的作用:
// %X: 以大写十六进制格式输出一个整数
// 02: 指定输出宽度为2,如果不足2位,则在前面用 '0' 补齐。
// 例如,byte值 5 会被格式化为 "05"。
// (b & 0xFF) 是为了确保将byte作为无符号数处理,防止负数byte转换时出现问题。
sb.append(String.format("%02X", b & 0xFF));
}
String hex = sb.toString();
// 根据题目要求,如果最终的十六进制字符串长度为奇数,前面补充一个'0'
// 虽然对于字节数组来说,这种情况理论上不会发生(一个byte总是两个hex字符),
// 但作为防御性编程,可以保留这个逻辑。
// 按标准字节转换逻辑,长度必为偶数。
// 此处的实现遵循标准逻辑。
if (hex.length() % 2 != 0) {
return "0" + hex;
}
return hex;
}
}
// --- ACM模式主类示例 ---
// 在实际平台提交时,通常只需要提交上面的 XutfEncoder 类。
// 下面的 Main 类用于本地测试和验证。
/*
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
XutfEncoder encoder = new XutfEncoder();
Scanner scanner = new Scanner(System.in);
System.out.println("请输入一个十进制整数:");
while(scanner.hasNextInt()) {
int unicodeVal = scanner.nextInt();
String result = encoder.encode(unicodeVal);
System.out.println("输入: " + unicodeVal);
System.out.println("输出: "" + result + """);
System.out.println("--------------------");
System.out.println("请输入一个十进制整数:");
}
scanner.close();
}
}
*/