震惊,竟然没人正确的使用过这个功能
一、背景
最近有朋友问我怎么缩小数据占用内存的大小。
我回答道:如果是为了缩小内存大小,可以尝试压缩一下?
接着我又补充道:这个算是使用时间换空间,压缩和解压缩需要时间的。
朋友又问:自己 protobuf 已经进行序列化了,这个和压缩是等价的吧?
我回答到:反序列化压缩的大小很有限。平常 protobuf 序列化缩小内存的说法是相对于 json 等文本协议来说的。
二、自带压缩功能
我给朋友建议,找一个压缩算法,比如 snappy 压缩算法。
然后先对 Protobuf 进行序列化,然后对序列化的二进制进一步压缩。
后来我便思考,二进制压缩效果如何呢?
一搜索,发现 protobuf 自带压缩功能,可选的压缩算法有 GZIP 和 ZLIB。
此时我便感兴趣了,所有 pb 协议的缓存系统都可以引入这个压缩功能来缩小内存。
三、网上的压缩样例
网上一搜索,所有的 protobuf 压缩样例都是文件当做出入输出的。
压缩序列化样例:
std::ofstream output("scene.art", std::ofstream::out | std::ofstream::trunc | std::ofstream::binary);OstreamOutputStream outputFileStream(&output);GzipOutputStream::Options options;options.format = GzipOutputStream::GZIP;options.compression_level = _COMPRESSION_LEVEL;GzipOutputStream gzipOutputStream(&outputFileStream, &options);scene->SerializeToZeroCopyStream(&gzipOutputStream)
可以看出来,大概分 5 步骤:
1、定义一个 写文件流
2、文件流 塞到 Ostream 流中。
3、定义压缩参数 Options。
4、定义压缩流 gzip 流。
5、序列化
反序列化也类似。
std::ifstream input("scene.art", std::ifstream::in | std::ifstream::binary);IstreamInputStream inputFileStream(&input);GzipInputStream GzipInputStream(&inputFileStream);scene1->ParseFromZeroCopyStream(&GzipInputStream)
解压缩只需要 4 个步骤。
1、定义一个读文件流。
2、文件流塞到 Istream 流中。
3、定义解压缩流 Gzip 流
4、反序列化
使用文件可以跑通了,那能不能直接把数据压缩序列化到 string 呢?
网上搜索一圈,全部是介绍 文件的 样例。
而这个文件的操作样例也是 protobuf 官方提供的。
于是我便只能去读一下 protobuf 的压缩部分的源码,找找如何使用 string 来输入输出。
四、偶遇解决方案
由于正常的 protobuf 代码我已经看过了。
所以这里只需要看压缩解压缩的代码了。
先看 Gzip 流的文件代码,发现特别简单,就两个类。
一个压缩类 GzipInputStream
一个解压缩类 GzipOutputStream
都继承了 零拷贝 类 ZeroCopyStream。
再看看 Istream 流文件代码,发现也是几个简单的类。
比如输入流 IstreamInputStream,
输出流 OstreamOutputStream,
还有些其他的类,
都继承了 ZeroCopyStream。
然后我就对这个 ZeroCopyStream 类好奇了,
一不小心打开错文件了,这不要紧,中大奖了。
我本来计划打开 zero_copy_stream.h
文件的,
结果打开了 zero_copy_stream_impl_lite.h
文件。
只因多瞅了一眼,竟然看到了 StringOutputStream 这个类。
顾名思义,iostream 是读写文件的,那 stringStream 就是用于操作 string 的了。
序列化需要 StringOutputStream ,那自然可以猜到反序列化就需要 StringinputStream 了。
但是发现这个类不存在。
一搜索,
在注释里找到了解释,
原来要使用 ArrayInputStream。

就这样,我使用 string 写出了压缩序列化与反序列化 protbuf 的代码。
五、完整版方案
完整版代码如下:
#include <google/protobuf/io/gzip_stream.h>#include <google/protobuf/io/zero_copy_stream_impl.h>std::string output;// 压缩序列化google::protobuf::io::GzipOutputStream::Options options;options.format = google::protobuf::io::GzipOutputStream::GZIP;options.compression_level = 9;google::protobuf::io::StringOutputStream outputStream(&output);google::protobuf::io::GzipOutputStream gzipStream(&outputStream, options);person.SerializeToZeroCopyStream(&gzipStream)gzipStream.Flush(); //数据刷到储存中printf("COMPRESSION output size : %d\n", static_cast<int>(output.length()));// 解压缩反序列化person.Clear();google::protobuf::io::ArrayInputStream inputStream(output.data(), output.size());google::protobuf::io::GzipInputStream gzipStream(&inputStream);person.ParseFromZeroCopyStream(&gzipStream)
这里有一个坑就是,压缩序列化的时候,必须进行 Close 或者 Flush 操作。

压缩效果可看出来,我随便构造了一个数据,正常序列化是 189 字节,ZLIB 压缩算法 是 95 字节,比 GZIP 压缩算法 103 字节稍微好一些。
六、最后
网上查看了不少文档,发现还是看源代码比较靠谱。
由于 protobuf 官方的样例是通过文件流的方式压缩序列化的,以至于整个互联网上所有的资料都是文件流压缩的教程。
甚至有些服务为了能够使用这个压缩功能,还专门先压缩到文件,然后读文件到内存。
由此也说明,官方对外提供文档的时候,一定要提供全。
不然就会像这个压缩功能,明明有直接压缩输出到内存的方法,所有人都使用文件这么蹩脚的方式来用这个功能。
思考题:你怎么看待这个现象呢?
《完》
-EOF-
题图:来自朋友圈。

上篇文章: leetcode 第 206 场算法比赛
长按二维码,一起成长学习


▲ 长按关注,天天成长
觉得有帮助可以点击好看与转发,谢谢!




