如何正确编码VarInt?字符串解析与编码异常排查
我有一份用于验证VarInt编码/解码正确性的YAML测试用例,左侧是预期编码字节(以字符串形式表示),右侧是原始数字:
examples: "\0": 0 "\u0001": 1 "\u000A": 10 "\u00c8\u0001": 200 "\u00e8\u0007": 1000 "\u00a9\u0046": 9001 "\u00ff\u00ff\u00ff\u00ff\u00ff\u00ff\u00ff\u00ff\u00ff\u0001": -1
前三个示例按无符号数解析时结果正确,但从第四个示例(200)开始,实际编码结果与测试用例预期不匹配。
最小复现代码(Rust)
use bytes::{Buf, BufMut}; use integer_encoding::{VarIntReader, VarIntWriter, VarInt}; let value = "\u{00c8}\u{0001}"; // "È\u{1}" println!("Expected encoded number as a string: {:?}", value); let mut buf: &[u8] = value.as_bytes(); // [195, 136, 1] println!("Expected encoded number as a byte array: {:?}", buf); let num_as_i32: i32 = 200; let mut wr = vec![].writer(); wr.write_varint(num_as_i32); let encoded_result_as_i32: Vec<u8> = wr.into_inner(); // [144, 3] println!("Encoded result as i32: {:?}", encoded_result_as_i32); let num_as_u32: u32 = 200; let mut wr2 = vec![].writer(); wr2.write_varint(num_as_u32); let encoded_result_as_u32: Vec<u8> = wr2.into_inner(); // [200, 1] println!("Encoded result as u32: {:?}", encoded_result_as_u32);
运行后发现:u32类型200的编码结果[200, 1]与"\u00c8\u0001"的十六进制值(0xC8 0x01)匹配,但与字符串转换后的字节数组[195, 136, 1]不符。另外,测试用例中-1的编码与Protobuf文档中描述的也存在差异。
问题分析与解答
YAML字符串的字节解析问题
YAML中的\u00c8是Unicode字符转义,它会被解析为Unicode字符È,而这个字符的UTF-8编码是0xC3 0x88(对应十进制195、136),再加上\u0001的0x01,最终得到[195,136,1]。但VarInt编码200的正确原始字节是0xC8 0x01(十进制200、1)。问题出在测试用例的写法上:用Unicode转义表示原始字节是错误的。如果要在YAML中表示原始字节序列,应该使用YAML的二进制标量格式(比如
!!binary jA==对应0xC8 0x01,需Base64编码),或者使用支持直接字节转义的YAML语法(部分实现支持\xC8这样的十六进制字节转义)。-1的VarInt编码误解
Protobuf的VarInt分为两种情况:- 对于无符号整数(uint32/uint64),直接按二进制位编码;
- 对于有符号整数,
int32/int64直接用补码的VarInt编码(此时-1的补码是全1,需要10个字节的VarInt表示,即测试用例中的\u00ff...\u0001); - 而
sint32/sint64使用ZigZag编码,-1的ZigZag编码结果是0x01(单个字节)。
测试用例中的-1编码是
int32类型直接VarInt编码的结果,而你参考的文档描述的是sint32的ZigZag编码,这是对Protobuf VarInt类型的混淆。
内容的提问来源于stack exchange,提问作者Nivaldo T

