TLV结构中的变长长度编码(VLQ)详解:从入门到实战

新星杯·14天创作挑战营·第16期 10w+人浏览 564人参与

  使用 Java 调用 c++代码时,需要组装 TLV(Tag-Length-Value)格式的数据结构给到底层。Length字段的编码方式是使用 变长编码(Variable-Length Quantity,VLQ)。下面是开发过程:


一、什么是 TLV?

TLV 是一种经典的数据结构格式,由三部分组成:

字段含义举例
TTag(标签)如:0x01 表示姓名
LLength(长度)数据的长度
VValue(值)实际的数据,如 “Alice”

1. 为什么要用“变长”表示 Length?

如果 Length 字段写死 1 字节(最大127),就很难适应更大的数据块。

变长编码的优势:

  • 小数字只用 1 字节,节省空间;
  • 大数字最多用 5 字节也能完整表达(支持 2^35);
  • 每个字节的最高位标志是否“还有后续”。

2.VLQ 编码规则详解

变长编码的核心规则如下:

  • 每个字节使用 7 位表示数据第8位(最高位)作为标志位
  • 如果该字节后面还有数据,最高位 = 1
  • 如果这是最后一个字节,最高位 = 0
举个例子:编码整数 200

第一步:转成二进制

200 = 1100 1000(二进制) = 0xC8(十六进制)

第二步:按 7 位一组拆分(从低位开始)

补足为 16 位:0000 0001 1001 0000
低7位:1001000(0x48)
高7位:0000001(0x01)

第三步:加上标志位 bit7

数据块是否还有下一个?最终字节
0x48是(1)0xC8
0x01否(0)0x01

最终编码结果:0xC8 0x01


二 Java 实现:VLQ 编码函数

private static void writeLength(ByteArrayOutputStream output, int length) {
    while (true) {
        // 取出低7位
        int current = length & 0x7F;  
        // 右移7位
        length >>= 7;                 
        if (length != 0) {
             // 设置高位为1
            current |= 0x80;        
            output.write((byte) current);
        } else {
            // 最后一个字节
            output.write((byte) current); 
            break;
        }
    }
}

验证一下:

int value = 200;
System.out.println("Binary: " + Integer.toBinaryString(value));
System.out.println("低七位: " + (value & 0x7F)); 
System.out.println("高位: " + (value >> 7));  

TLV 示例编码

writeInfo(output, InfoTag.TAG_NAMEFIRST.getTag(), "LIU");

解释:

  • 写入 Tag
  • 写入 Length(使用 writeLength() 编码)
  • 写入实际值字节

什么是“高位 / 低位”?

名称对应位示例值(0x1234)
高位bit 15~80x12
低位bit 7~00x34

在 VLQ 中,“高位”是指 高7位在后写入,“低位”是指 低7位先写入每个字节仅使用 7 位存储数据,最高位用作“是否还有后续字节”的标志位。

特性描述
高位标志1 表示还有字节,0 表示最后一个字节
数据部分每字节低7位
应用场景TLV结构、Google Protobuf等

组装的完整代码:

public static void main(String[] args) {
    try {
          // …………
        if (nRet == DTCErrCode.CODE_OK.getCode()) {
            ByteArrayOutputStream output = new ByteArrayOutputStream();

            // Build diffInfo byte array
            writeInfo(output, InfoTag.TAG_NAMEFIRST.getTag(), "LIU");
            writeInfo(output, InfoTag.TAG_NAMELAST.getTag(), "DEHUA");

            byte[] diffInfoData = output.toByteArray();
            System.out.println("TLV hex: " + bytesToHex(diffInfoData));
          // …………
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}
    public static void writeInfo(ByteArrayOutputStream output, int tag, String value) {
        if (StrUtil.isNotBlank(value)) {
            byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
            output.write((byte) tag);                  // TAG
            // output.write((byte) bytes.length);         // Length
            writeLength(output, bytes.length);
            output.write(bytes, 0, bytes.length);      // Data
        }
    }
    private static void writeLength(ByteArrayOutputStream output, int length) {
        while (true) {
            int current = length & 0x7F;
            length >>= 7;
            if (length != 0) {
                current |= 0x80;
                output.write((byte) current);
            } else {
                output.write((byte) current);
                break;
            }
        }
    }
    private static String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("0x%02X ", b));
        }
        return sb.toString();
    }

Tag 枚举类:

@Getter
public enum InfoTag {
    //…………
    TAG_NAMEFIRST(0x05, "姓"),
    TAG_NAMELAST(0x06, "名");
    // …………
;

    private final int tag;
    private final String description;

    InfoTag(int tag, String description) {
        this.tag = tag;
        this.description = description;
    }
}


参考文档:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值