LevelDB 源码解析之 Varint 编码
GitHub: https://github.com/storagezhang
Emai: debugzhang@163.com
华为云社区: https://bbs.huaweicloud.com/blogs/253047
LevelDB: https://github.com/google/leveldb
Varint 编码
LevelDB 内部采用变长编码,对数据进行压缩,减少存储空间,再采用 CRC 校验数据。
整型数据是以 32(64) 位来表示的,以 32 位为例,存储需要 4 个字节。
如果一个整数的大小在 256 以内,那么只需要一个字节就可以存储这个整数,可以节省 3 个字节。
Varint 就是根据这种思想来序列化整数的,它是一种使用一个或多个字节序列化整数的方法,会把整型数据编码为变长字节。
Varint 中的每个字节都设置为最高有效位:
如果该位为 0,表示结束,当前字节的剩余 7 位就是该数据的表示。
- 表示整数 1,需要一个字节:0000 0001
如果该位为 1,表示后续的字节也是该整型数据的一部分;
- 表示整数 300,需要两个字节:1010 1100 0000 0010
这也表示 Varint 编码后是按小端排序的。
字节顺序,又称端序或尾序(英语:Endianness),在计算机科学领域中,指电脑内存中或在数字通信链路中,组成多字节的字的字节的排列顺序。
字节的排列方式有两个通用规则。例如,将一个多位数的低位放在较小的地址处,高位放在较大的地址处,则称小端序;反之则称大端序。在网络应用中,字节序是一个必须被考虑的因素,因为不同机器类型可能采用不同标准的字节序,所以均按照网络标准转化。
因此,32 位整型数据经过 Varint 编码后占用 1~5 个字节(5 * 8 - 5 > 32),64 位整型数据编码后占用 1~10 个字节(10 * 8 - 10 > 64)。
在实际场景中,由于小数字的使用率远远高于大数字,所以在大部分场景中,通过 Varint 编码的数据都可以起到很好的压缩效果。
编码实现
EncodeVarint64
将 uint64_t
编码为 Varint 类型的字节流:
char* EncodeVarint64(char* dst, uint64_t v) {
static const int B = 128;
uint8_t* ptr = reinterpret_cast<uint8_t*>(dst);
while (v >= B) {
// B=128=0x80, v|B 表示在最高位上加 1
// *ptr 是 uint8_t 类型的,即每次取下 7 位数据
*(ptr++) = v | B;
// 右移 7 位, 继续处理后面的数据
v >>= 7;
}
// 处理最后一个字节的小于 128 的数据
*(ptr++) = static_cast<uint8_t>(v);
return reinterpret_cast<char*>(ptr);
}
EncodeVarint32
将 uint32_t
编码为 Varint 类型的字节流,其实现与 EncodeVarint64
类似,但是可能因为最多 5 个字节,所以是硬编码的:
char* EncodeVarint32(char* dst, uint32_t v) {
uint8_t* ptr = reinterpret_cast<uint8_t*>(dst);
static const int B = 128;
if (v < (1 << 7)) {
// v < 0x80,可以用 7 位表示,占一个字节
*(ptr++) = v;
} else if (v < (1 << 14)) {
// 0x80 <= v < 0x4000,可以用 14 位表示,占两个字节
*(ptr++) = v | B;
*(ptr++) = v >> 7;
} else if (v < (1 << 21)) {
// 0x4000 <= v < 0x200000,可以用 21 位表示,占三个字节
*(ptr++) = v | B;
*(ptr++) = (v >> 7) | B;
*(ptr++) = v >> 14;
} else if (v < (1 << 28)) {
// 0x200000 <= v < 0x10000000,可以用 28 位表示,占四个字节
*(ptr++) = v | B;
*(ptr++) = (v >> 7) | B;
*(ptr++) = (v >> 14) | B;
*(ptr++) = v >> 21;
} else {
// 0x10000000 <= v < 0x100000000,可以用 35 位表示,占五个字节
*(ptr++) = v | B;
*(ptr++) = (v >> 7) | B;
*(ptr++) = (v >> 14) | B;
*(ptr++) = (v >> 21) | B;
*(ptr++) = v >> 28;
}
return reinterpret_cast<char*>(ptr);
}
解码实现
解码就是编码的逆过程,同样是利用位运算进行。
GetVarint64Ptr
将输入的 Varint 类型字节流转换成 uint64_t
整型数据:
const char* GetVarint64Ptr(const char* p, const char* limit, uint64_t* value) {
uint64_t result = 0;
for (uint32_t shift = 0; shift <= 63 && p < limit; shift += 7) {
uint64_t byte = *(reinterpret_cast<const uint8_t*>(p));
p++;
if (byte & 128) {
// byte & 0x80 判断最高有效位为 1
// byte & 0x7f:获取 7 位有效数据
// (b & 0x7F) << shift:Varint 编码是小端排序,每处理一个数据,都需要向高位移动 7 位
// result | ((byte & 127) << shift):连接高位数据和低位数据
result |= ((byte & 127) << shift);
} else {
// byte & 0x80 判断最高有效位为 0,最后 7 位数据
result |= (byte << shift);
*value = result;
return reinterpret_cast<const char*>(p);
}
}
return nullptr;
}
GetVarint32Ptr
与GetVarint64Ptr
算法相同,唯一的区别在于对小于 128 的数据进行特判,如果小于则直接返回结果,这样设计的原因是大部分数字都比 128 小,可以通过内联函数提高计算效率。
inline const char* GetVarint32Ptr(const char* p, const char* limit,
uint32_t* value) {
if (p < limit) {
uint32_t result = *(reinterpret_cast<const uint8_t*>(p));
if ((result & 128) == 0) {
*value = result;
return p + 1;
}
}
return GetVarint32PtrFallback(p, limit, value);
}
const char* GetVarint32PtrFallback(const char* p, const char* limit,
uint32_t* value) {
uint32_t result = 0;
for (uint32_t shift = 0; shift <= 28 && p < limit; shift += 7) {
uint32_t byte = *(reinterpret_cast<const uint8_t*>(p));
p++;
if (byte & 128) {
result |= ((byte & 127) << shift);
} else {
result |= (byte << shift);
*value = result;
return reinterpret_cast<const char*>(p);
}
}
return nullptr;
}
LevelDB 源码解析之 Varint 编码的更多相关文章
- LevelDB 源码解析之 Random 随机数
GitHub: https://github.com/storagezhang Emai: debugzhang@163.com 华为云社区: https://bbs.huaweicloud.com/ ...
- Leveldb源码解析之Bloom Filter
Bloom Filter,即布隆过滤器,是一种空间效率很高的随机数据结构. 原理:开辟m个bit位数组的空间,并全部置零,使用k个哈希函数将元素映射到数组中,相应位置1.如下图,元素K通过哈希函数h1 ...
- LevelDB 源码解析之 Arena
GitHub: https://github.com/storagezhang Emai: debugzhang@163.com 华为云社区: https://bbs.huaweicloud.com/ ...
- Alink漫谈(十八) :源码解析 之 多列字符串编码MultiStringIndexer
Alink漫谈(十八) :源码解析 之 多列字符串编码MultiStringIndexer 目录 Alink漫谈(十八) :源码解析 之 多列字符串编码MultiStringIndexer 0x00 ...
- 源码解析-Volley(转自codeKK)
Volley 源码解析 本文为 Android 开源项目源码解析 中 Volley 部分项目地址:Volley,分析的版本:35ce778,Demo 地址:Volley Demo分析者:grumoon ...
- 实战录 | Kafka-0.10 Consumer源码解析
<实战录>导语 前方高能!请注意本期攻城狮幽默细胞爆表,坐地铁的拉好把手,喝水的就建议暂时先别喝了:)本期分享人为云端卫士大数据工程师韩宝君,将带来Kafka-0.10 Consumer源 ...
- Android AsyncTask 源码解析
1. 官方介绍 public abstract class AsyncTask extends Object java.lang.Object ↳ android.os.AsyncTask&l ...
- LevelDB源码剖析
LevelDB的公共部件并不复杂,但为了更好的理解其各个核心模块的实现,此处挑几个关键的部件先行备忘. Arena(内存领地) Arena类用于内存管理,其存在的价值在于: 提高程序性能,减少Heap ...
- Volley 源码解析
Volley 源码解析 1. 功能介绍 1.1. Volley Volley 是 Google 推出的 Android 异步网络请求框架和图片加载框架.在 Google I/O 2013 大会上发布. ...
随机推荐
- learning 2018
learning 2018 https://scotch.io/courses https://laod.cn/hosts/ipv6-dns.html xgqfrms 2012-2020 www.cn ...
- write a node cli tools, step by step
write a node cli tools, step by step how to write a node cli tools node cli tools, step by step, nod ...
- egg.js in action
egg.js in action fullstack https://github.com/eggjs/egg/ cli config router service midlewares HTTP C ...
- eui & search select
eui & search select https://element.eleme.io/#/zh-CN/component/select demo <template> < ...
- 智能合约稳定币USDN的价值在哪里?
近几年来,区块链和数字货币市场快速发展,客观上需要价格相对稳定的交易媒介和贮藏手段,从而推动以链上资产或链下资产抵押型稳定币和算法型稳定币出现,以实现币价相对稳定的数字货币.市场上开始出现了诸如USD ...
- 一文助你了解NGK商城
按照NGK的发展逻辑,将会在2021年上半年上线链商商城,解决传统消费行业真伪难辨的弊端,之后,将会推出小额支付功能,让NGK真正成为结算中的数字资产,目前两者落地性应用已经在开发内测阶段中,期初的部 ...
- 02.Fancy Indexing
import numpy as np x = np.arange(16) index = [3,5,8] x[index] array([3, 5, 8]) X = x.reshape(4,-1) X ...
- ADT基础(一)—— List,Stack,and Queue
ADT基础(一)-- List,Stack,and Queue 1 List 表示 数组:易于search,难于insert和remove 链表:难于search,易于insert和remove // ...
- linux 安装软件步骤
1,下载wget http://www.erlang.org/download/otp_src_R16B02.tar.gz2,解压 tar -zxvf otp_src_R16B02.tar.gz3,编 ...
- go语言-csp模型-并发通道
[前言]go语言的并发机制以及它所使用的CSP并发模型 一.CSP并发模型 CSP模型是上个世纪七十年代提出的,用于描述两个独立的并发实体通过共享的通讯 channel(管道)进行通信的并发模型. C ...