使用 Java 调用 c++代码时,需要组装 TLV(Tag-Length-Value)格式的数据结构给到底层。Length字段的编码方式是使用 变长编码(Variable-Length Quantity,VLQ)。下面是开发过程:
一、什么是 TLV?
TLV 是一种经典的数据结构格式,由三部分组成:
| 字段 | 含义 | 举例 |
|---|---|---|
| T | Tag(标签) | 如:0x01 表示姓名 |
| L | Length(长度) | 数据的长度 |
| V | Value(值) | 实际的数据,如 “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~8 | 0x12 |
| 低位 | bit 7~0 | 0x34 |
在 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;
}
}
参考文档:

被折叠的 条评论
为什么被折叠?



