TDR表 string 类型多语言兼容性
TDR 表定义中的 string 类型字段,在底层存储时约定以 \0 结尾(C 风格字符串)。
不同编程语言的 SDK 对 \0 的处理方式并不完全一致,当业务同时使用多种语言读写同一张表时(特别是 key 字段),需要特别注意,避免出现同一逻辑字符串被解读为不同二进制内容的兼容性问题。
差异产生的原因:TDR 协议本身源自 C,而 C 语言没有独立的 string 类型,字符串以
const char*+\0终止符的形式表达,长度信息隐含在\0中;而 Go、Java 都有原生的string类型(其长度独立存储,字符串中可合法包含\0,也可不以\0结尾)。因此各 SDK 在把语言原生类型转换为 TDRstring字段(C 风格字符串)时,对\0的补齐策略和校验程度不一致,这就是下面行为差异的根源。
1. 各 SDK 对 string 字段的处理行为
1.1 C++ SDK
接口:SetKeyStr / SetValueStr / GetKeyStr / GetValueStr / SetData / GetData
入参为 C 字符串(
const char*),SDK 内部按strlen(field_value) + 1计算长度进行打包。其实现形如:int TcaplusServiceRecord::SetKeyStr(IN const char* field_name, IN const char* field_value) { return SetKey(field_name, field_value, strlen(field_value) + 1); }这意味着:
- SDK 总是打包到第一个
\0为止,并把这个\0一并写入,最终存储的 string 末尾有且只有一个\0; - 用户传入
"abc"(无\0)时,实际存储为 4 字节:a b c \0; - 用户传入
"abc\0"(末尾带\0)时,strlen仍返回 3,实际存储同样为 4 字节a b c \0,不会出现"末尾多个\0"; - 用户传入
"abc\0def"(字符串中间含\0)时,strlen返回 3,实际存储只有a b c \0,后面的"def"会被截断丢弃。
- SDK 总是打包到第一个
- 读取侧
GetKeyStr/GetValueStr返回的字符串以单个\0结尾,可作为标准 C 字符串使用。
1.2 Go SDK
接口:TDR 打包相关的 SetKeyStr / SetValueStr 等(以及 SetData 中 string 类型字段)
- 在 TDR 打包时,Go SDK 会无条件对
string类型字段的末尾追加一个\0,不校验用户传入的字符串末尾是否已经有\0。 - 使用建议:在 Go SDK 中按 Go 原生
string使用即可,不要自行在末尾追加\0。若用户自行在字符串末尾加上\0,SDK 仍会再追加一个\0,最终存储结果会出现末尾多个\0的情况,导致与其他语言写入的数据不一致。
例如:Go 侧传入
"abc\x00",实际打包后为"abc\x00\x00";而 C++ 侧对于同样语义的"abc"会打包为"abc\x00"。二者虽然在部分场景下可被解读为同一字符串,但在作为 key 进行精确匹配时,会被视为不同的 key。
1.3 C++ / Go SDK 的 Blob 接口
接口:SetKeyBlob / SetValueBlob
- 入参是字节数组 + 数组长度,SDK 将其视为透明的二进制数据:
- 不会校验用户输入是否以
\0结尾; - 不会自动追加
\0。
- 不会校验用户输入是否以
- 如果业务希望与
SetKeyStr/SetValueStr写入的数据保持二进制一致(以便跨语言 / 跨接口互读),需要用户自行在字节数组末尾追加\0,并把长度计入\0。
场景示例:若同一个 string 字段,一端用
SetKeyStr("abc")写入(实际存储 4 字节a b c \0),另一端用SetKeyBlob读/写,必须按 4 字节处理;若另一端用SetKeyBlob("abc", 3)写入,则只存储 3 字节,与前者不一致,会导致 key 精确匹配失败。
1.4 Java SDK
接口:SetKeyString / SetValueString
- 对于
string类型字段,Tcaplus Java SDK 不会校验、也不会主动保证用户输入的字符串是否以\0结尾。 - 这意味着:
- 如果用户传入的字符串不含
\0,最终存储不会有结尾\0; - 如果用户自行在字符串末尾附加了
\0,最终存储会包含该\0。
- 如果用户传入的字符串不含
- 因此,使用 Java SDK 写入的 string 字段,其二进制形态由用户代码决定,而 C++ / Go SDK 写入的 string 字段末尾固定有且仅有一个
\0。
2. 行为对比
| SDK / 接口 | 末尾 \0 处理 |
是否一定有 \0 |
说明 |
|---|---|---|---|
C++ SetKeyStr / SetValueStr / SetData |
按 strlen+1 打包,截断至第一个 \0 并保留该 \0 |
是,且有且只有一个 | 用户无需关心 \0;字符串中间若含 \0,其后内容会被截断丢弃 |
Go SetKeyStr / SetValueStr / SetData |
SDK 自动追加(不校验) | 是,至少一个;若用户自带 \0,会出现多个 |
按 Go 原生 string 使用,不要自行加 \0 |
C++ / Go SetKeyBlob / SetValueBlob |
SDK 不做任何处理 | 取决于用户 | 如需与 Str 接口一致,请自行追加 \0 并把长度 +1 |
Java SetKeyString / SetValueString |
SDK 不做校验与保证 | 取决于用户 | 如需与 C++ / Go 保持二进制一致,业务需自行在末尾附加一个 \0 |
3. 注意事项与建议
- 跨语言访问同一张表时,string 类型 key 字段尤其容易出现兼容性问题。key 的二进制在存储层需完全一致,才能命中同一条记录。
- 推荐做法(确保跨语言二进制一致,存储形式统一为"有效字符 + 一个
\0"):- C++:使用
SetKeyStr/SetValueStr/SetData,按普通 C 字符串传入即可,SDK 会自动补上末尾\0,用户无需额外处理。 - Go:使用 TDR 打包相关的 string 接口,直接按 Go 原生
string传入,SDK 会自动追加\0,用户不要在末尾手动添加\0,否则会出现"末尾多个\0"导致与 C++ 不一致。 - Java:使用
SetKeyString/SetValueString时,SDK 不会主动追加\0。如需与 C++ / Go 的 Str 接口在存储层二进制一致,业务需自行在字符串末尾显式附加一个\0(例如value + "\0"),否则存储字节数会比 C++/Go 少一字节\0,在精确匹配场景下会命中不到同一条记录。 - Blob 接口(C++/Go 的
SetKeyBlob/SetValueBlob):SDK 不做任何\0处理。若需与 Str 接口互通,请自行在字节数组末尾加上\0,并把长度+1使其包含该字节。
- C++:使用
- 字段值为 string 类型时,写入端的约定需与读取端一致,否则在精确匹配(key 查询、条件过滤等)场景下可能出现读不到记录的问题。