如何在 Go 中严格校验十六进制字符串的精确字符数

go 的 `fmt.sscanf` 不支持“必须恰好匹配指定字符数”的语义,但可通过 `strconv.parseuint` 结合长度预检实现严格两位十六进制解析。

在 Go 中,fmt.Sscanf 的格式化行为是“最多匹配”,而非“恰好匹配”。例如 %02x 仅表示“以至少两位、不足则补零的方式解析十六进制”,但输入 "f!" 仍会成功解析 'f'(即 0xf = 15),忽略后续非法字符 !,且 n 返回 1(成功读取 1 个值),无法区分 "f"、"ff" 或 "f!" —— 这显然不符合“必须严格两位合法十六进制字符”的业务需求(如解析 MAC 地址字节、固定宽编码等)。

此时应放弃 fmt.Sscanf,改用 strconv.ParseUint 配合显式长度校验。核心逻辑分三步:

  1. 长度前置检查:确保输入字符串长度严格等于目标位数(如 len(s) == 2);
  2. 基数解析:调用 strconv.ParseUint(s, 16, 8) 解析为 uint64(uint8 范围内安全);
  3. 错误统一处理:任一环节失败均返回 strconv.ErrSyntax 或其他具体错误。

以下为生产就绪的示例代码:

package main

import (
    "fmt"
    "strconv"
)

// ParseHexByte parses exactly size hex digits (e.g., "ff") into uint8.
// Returns error if length ≠ size or contains invalid hex chars.
func ParseHexByte(s string, size int) (uint8, error) {
    if len(s) != size {
        return 0, strconv.ErrSyntax // explicit length mismatch
    }
    n, err := strconv.ParseUint(s, 16, 8)
    return uint8(n), err
}

func main() {
    testCases := []string{"ff", "f", "", "f!", "12", "00", "fff"}
    for _, s := range testCases {
        if v, err := ParseHexByte(s, 2); err == nil {
            fmt.Printf("✓ %q → %d\n", s, v)
        } else {
            fmt.Printf("✗ %q → %v\n", s, err)
        }
    }
}

输出结果:

✓ "ff" → 255
✗ "f" → strconv.ParseUint: parsing "f": invalid syntax
✗ "" → strconv.ParseUint: parsing "": invalid syntax
✗ "f!" → strconv.ParseUint: parsing "f!": invalid syntax
✓ "12" → 18
✓ "00" → 0
✗ "fff" → strconv.ParseUint: parsing "fff": invalid syntax

关键优势

  • 精确控制输入宽度(len(s) == size),杜绝截断或冗余;
  • 复用标准库 strconv,无额外依赖,性能可靠;
  • 错误语义清晰:strconv.ErrSyntax 明确标识格式违规(含长度/字符非法)。

⚠️ 注意事项

  • 若需忽略大小写(如 "FF" 和 "ff" 均合法),strconv.ParseUint 默认支持,无需额外处理;
  • 对于更复杂场景(如带前缀 "0xff"),需先裁剪前缀再校验长度;
  • 在高并发解析中,该函数无状态、无副作用,可安全复用。

综上,当需要“精确字符数 + 严格语法”双重校验时,strconv.ParseUint + 显式长度检查是 Go 中最直接、可靠且符合惯用法的解决方案。