← 中文资源

简体中文版本

十六进制编解码

在字节与十六进制字符串之间转换,支持可选前缀与空白分隔,并拒绝非法字符和奇数长度。

阅读英文原文

功能说明

哈希摘要、协议调试和二进制配置常以十六进制文本表示。编码方向把每个字节拆成高四位和低四位,查表转换为小写字符;解码方向接受带 0x 前缀、包含空格或换行的输入,先过滤空白再两位一组转换,遇到非法字符给出明确错误。

示例用 hello 演示往返,另外测试带前缀的 DE AD BE EF、奇数长度与非法字符。输出使用 from_utf8_lossy 展示解码后的文本,实际二进制数据应直接保留字节。

关键代码

encode_hex 预分配两倍长度的字符串,逐字节查表,避免格式化开销。hex_value 用模式匹配把数字、大小写字母映射到零到十五。decode_hex 先 trim 并去掉可选前缀,再过滤所有 ASCII 空白,长度必须为偶数,然后用 chunks 每两个字符一组计算并合并。错误信息保留原始字符,便于定位输入问题。

使用注意

解码会忽略所有空白,包括字符串中间的换行,这对粘贴多行摘要很方便,但也意味着无法区分带空格的二进制格式。编码结果固定小写,比较时应统一大小写。0x 前缀只在最外层识别一次,中间出现会报错。超长输入会一次性分配两倍内存,大文件应分块处理。十六进制不是加密,不能用于隐藏敏感数据。

验证方式

hello 编码为 68656c6c6f,解码后还原为 hello。带前缀且含空格的 DE AD BE EF 解码为四字节。abc 因长度为奇数返回错误,zz 因包含非法字符返回错误,两个提示均为 true。可以把大写输入再编码,确认输出统一为小写。

代码示例

fn encode_hex(bytes: &[u8]) -> String {
    const DIGITS: &[u8; 16] = b"0123456789abcdef";
    let mut output = String::with_capacity(bytes.len() * 2);
    for &byte in bytes {
        output.push(DIGITS[(byte >> 4) as usize] as char);
        output.push(DIGITS[(byte & 0x0f) as usize] as char);
    }
    output
}

fn hex_value(byte: u8) -> Result<u8, String> {
    match byte {
        b'0'..=b'9' => Ok(byte - b'0'),
        b'a'..=b'f' => Ok(byte - b'a' + 10),
        b'A'..=b'F' => Ok(byte - b'A' + 10),
        _ => Err(format!("invalid hex digit: {}", byte as char)),
    }
}

fn decode_hex(input: &str) -> Result<Vec<u8>, String> {
    let trimmed = input.trim();
    let cleaned = trimmed
        .strip_prefix("0x")
        .or_else(|| trimmed.strip_prefix("0X"))
        .unwrap_or(trimmed);
    let digits: Vec<u8> = cleaned
        .bytes()
        .filter(|byte| !byte.is_ascii_whitespace())
        .collect();
    if digits.len() % 2 != 0 {
        return Err("hex input must have an even number of digits".to_string());
    }
    let mut output = Vec::with_capacity(digits.len() / 2);
    for pair in digits.chunks(2) {
        let high = hex_value(pair[0])?;
        let low = hex_value(pair[1])?;
        output.push((high << 4) | low);
    }
    Ok(output)
}

fn main() {
    let payload = b"hello";
    let encoded = encode_hex(payload);
    println!("encoded: {encoded}");
    println!(
        "decoded: {:?}",
        decode_hex(&encoded).map(|bytes| String::from_utf8_lossy(&bytes).into_owned())
    );
    println!("prefixed: {:?}", decode_hex("0xDE AD BE EF"));
    println!("odd length: {}", decode_hex("abc").is_err());
    println!("bad digit: {}", decode_hex("zz").is_err());
}