protobuf之string bytes的区别

85 篇文章 0 订阅

protobuf提供了多种基础数据格式,包括string/bytes。从字面意义上,我们了解bytes适用于任意的二进制字节序列。然而对C++程序员来讲,std::string既能存储ASCII文本字符串,也能存储任意多个\0的二进制序列。那么区别在哪里呢?

同时在实际使用中,我们偶尔会看到类似这样的运行错误:

[libprotobuf ERROR google/protobuf/wire_format.cc:1091] String field 'str' contains invalid UTF-8 data when serializing a protocol buffer. Use the 'bytes' type if you intend to send raw bytes.

[libprotobuf ERROR google/protobuf/wire_format.cc:1091] String field 'str' contains invalid UTF-8 data when parsing a protocol buffer. Use the 'bytes' type if you intend to send raw bytes.

这篇文章从源码角度分析下string/bytes类型的区别。

之前的文章里介绍过protobuf序列化的过程,我们看下string/bytes序列化的过程。 在之前的文章里介绍过protobuf序列化的过程,我们看下string/bytes序列化的过程。

所有的序列化操作都会在SerializeFieldWithCachedSizes这个函数里进行。根据不同的类型调用对应的序列化函数,例如对于string类型

case FieldDescriptor::TYPE_STRING: {

string scratch;

const string& value = field->is_repeated() ?

message_reflection->GetRepeatedStringReference(

message, field, j, &scratch) :

message_reflection->GetStringReference(message, field, &scratch);

VerifyUTF8StringNamedField(value.data(), value.length(), SERIALIZE,

field->name().c_str());

WireFormatLite::WriteString(field->number(), value, output);

break;

}

而对于bytes类型:

case FieldDescriptor::TYPE_BYTES: {

string scratch;

const string& value = field->is_repeated() ?

message_reflection->GetRepeatedStringReference(

message, field, j, &scratch) :

message_reflection->GetStringReference(message, field, &scratch);

WireFormatLite::WriteBytes(field->number(), value, output);

break;

}

可以看到在序列化时主要有两点区别:

  1. string类型调用了VerifyUTF8StringNamedField函数
  2. 序列化函数不同:WriteString vs WriteBytes

关于第二点,两个函数都定义在wire_format_lite.cc,实现是相同的。

那么我们继续看下第一点,VerifyUTF8StringNamedField调用了VerifyUTF8StringFallback(话说一直不理解fallback在这里什么意思,protobuf源码里经常看到这个后缀)。看下这个函数的实现:

void WireFormat::VerifyUTF8StringFallback(const char* data,

int size,

Operation op,

const char* field_name) {

if (!IsStructurallyValidUTF8(data, size)) {

const char* operation_str = NULL;

switch (op) {

case PARSE:

operation_str = "parsing";

break;

case SERIALIZE:

operation_str = "serializing";

break;

// no default case: have the compiler warn if a case is not covered.

}

string quoted_field_name = "";

if (field_name != NULL) {

quoted_field_name = StringPrintf(" '%s'", field_name);

}

// no space below to avoid double space when the field name is missing.

GOOGLE_LOG(ERROR) << "String field" << quoted_field_name << " contains invalid "

<< "UTF-8 data when " << operation_str << " a protocol "

<< "buffer. Use the 'bytes' type if you intend to send raw "

<< "bytes. ";

}

}

运行错误是从这里输出的,关键还是在于IsStructurallyValidUTF8这个函数,实现在structurally_valid.cc里:

bool IsStructurallyValidUTF8(const char* buf, int len) {

if (!module_initialized_) return true;


int bytes_consumed = 0;

UTF8GenericScanFastAscii(&utf8acceptnonsurrogates_obj,

buf, len, &bytes_consumed);

return (bytes_consumed == len);

}

这里逐个字符扫描是否符合utf-8规范,比如110xxxxx 10xxxxxx这样,具体可以参考utf-8的编码标准。

反序列化过程类似。

看到这里我们可以得到这样的结论:

  1. protobuf里的string/bytes在C++接口里实现上都是std::string
  2. 两者序列化、反序列化格式上一致,不过对于string格式,会有一个utf-8格式的检查。

出于效率,我们应当在确定字段编码格式后直接使用bytes,减少utf8编码的判断,效率上会有提高。

注意以上代码在pb2.6下,2.4不会输出field_name

据了解java接口上有一定的区别,分别对应String以及ByteString

  • 6
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
在Java中,使用protobufByteString类型可以方便地处理字节数据。通过调用ByteString.copyFrom(byte\[\] bytes)方法,可以将Java中的byte类型的数据转换成protobufByteString类型。这样就可以将Java中的byte类型的数据放入protobuf中定义的bytes类型的对象中了。\[1\] 在生成protobuf的Java类后,bytes字段会被转换成ByteString类型。在生成的Java代码中,可以看到对应字段的setter方法会接受ByteString类型的参数。例如,可以使用setData(com.google.protobuf.ByteString value)方法将ByteString类型的数据设置到对应的字段中。\[2\] 如果想在Java代码中传输byte类型的数据,直接将byte类型的数据放入protobuf定义的bytes类型的对象中是不可行的。需要通过protobuf的API进行转换。可以使用ByteString.copyFrom(byte\[\] bytes)方法将byte数组转换成ByteString类型,然后将其设置到对应的字段中。例如,可以使用MessageBase.newBuilder().setData(ByteString.copyFrom(b))来设置byte数组b到MessageBase对象的bytes字段中。\[3\] #### 引用[.reference_title] - *1* *2* *3* [Java中protobufByteStringbyte数组互转](https://blog.csdn.net/yaomian1999/article/details/127169463)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值