详解varint,zigzag编码, 以及在Go标准库中的实现

文章目录

  • 为啥需要varint编码
  • 为啥需要zigzag编码
  • varint
    • 编码
    • 解码
  • zigzag
    • 编码
    • 解码
  • 局限性

为啥需要varint编码

当我们用定长数字类型int32来表示整数时,为了传输一个整数1,我们需要传输00000000 00000000 00000000 00000001 32 个 bits,而有价值的数据只有 1 位。这就导致了大量的空间浪费,因为大部分字节并没有实际存储有效的信息

varint编码通过使用可变长度的字节序列来表示整数,根据数字的大小灵活占用的空间。这样使得小的整数可以用更少的字节表示,提高空间效率

下面是varint编码中,正数的大小和需要的字节数的关系

数字大小uvarint编码需要的字节数
<=1271
<=163832
<=20971513
<=2684354554

我们业务中大部分数据的size都 <= 16383,也就只用1或2个字节。相比于定长的int32,int64能节省不少空间

其设计原理为:

  • 每7bit 为一组:将整数的二进制按照每7个bit划分到一个byte中
  • 最高位表示是否还有下个字节:划分好的byte中,如果最高位为1,表示还有下个byte,否则当前byte是最后一个。最后一个字节的最高位为1,其他字节的最高位为0

在这里插入图片描述

例如:对于一个整数 500,它的二进制表示是 111110100。将其分为2组,即 111110100。然后在每组前面添加标志位,得到两个字节 1000001101110100,这两个字节就是 500 的 varint 编码。相比于用 int32 的 4 字节表示,节省了 50% 的存储空间


为啥需要zigzag编码

但如果是负数,那么继续采用Varint编码就没有任何压缩效果,甚至占用更多字节。因为负数的符号位最高位为1,也就是一定会用满最大的字节

ZigZag编码解决了varint对负数编码效率低的问题,其原理为:

  • 对于正数 n,会将其映射为 2 * n。例如整数 2,经过 zigzag 编码之后变成 4
  • 对于负数 -n 来说,会将其映射为 2 * n-1。例如负数 -3,经过 zigzag 编码之后变成了 2 * 3 - 1 = 5
nzigzag编码后
00
-11
12
-23
24
-35
36

例如:举个极端的例子-1,如果不用zigzag编码,直接用varint,那么会用10个字节。如果先zigzag变成1,再varint,只会用1个字节

接下来阅读golang标准库中如何对varint和zagzig进行编码和解码的


varint

编码

将x以varint的形式写入buf中,返回写了多少个字节

由于是每7位用一个字节存储,那么只要大于等于10000000,也就是需要超过7位,就需要先把低7位存到buf[i]中

for循环中:buf[i] = byte(x) | 10000000,这行是保留低7位,并且把buf[i]的第8位强制置为1

最后一个字节的最高位为0

func PutUvarint(buf []byte, x uint64) int {
	i := 0
    // Ox80 = 10000000
	for x >= 0x80 {
        // buf[i] = x的低8位 | 10000000
		buf[i] = byte(x) | 0x80
        // 移除低7位
		x >>= 7
        // 需要用到的字节数 + 1
		i++
	}

    // 最后一个字节
	buf[i] = byte(x)
	return i + 1
}

解码

传入需要解码的字节序列,返回解码后的数字,以及其占用了字节序列中前多少字节

func Uvarint(buf []byte) (uint64, int) {
	var x uint64
	var s uint
	for i, b := range buf {
		if i == MaxVarintLen64 {
			return 0, -(i + 1) // overflow
		}
        // b < 10000000,也最高位为0,代表是就是最后一个字节
		if b < 0x80 {
			if i == MaxVarintLen64-1 && b > 1 {
				return 0, -(i + 1) // overflow
			}

            // x | 最高的7位,返回
			return x | uint64(b)<<s, i + 1
		}
        // 最高位为1,表示后面还有字节
        // 提取这个字节的前7位:b & 01111111
		x |= uint64(b&0x7f) << s
		s += 7
	}
	return 0, 0
}

注意:如果要解码成uint64,一共64位,按7位一组,最多10组且第10组最大为1(第64位)

对应到源码中判断,如果超过10组或第10组大于1了,就返回溢出


zigzag

编码

我们知道在zigzag编码中:

  • 如果x是正数,按照2 * x的varint编码

  • 如果x是负数,假设其值为-n,就按照2 * n - 1的varint编码

对照源码看看:

func PutVarint(buf []byte, x int64) int {
	ux := uint64(x) << 1
	if x < 0 {
		ux = ^ux
	}
	return PutUvarint(buf, ux)
}

如果x是正数,等于x << 1的varint编码,没问题

如果x是负数,这里的操作是 x << 1,再按位取反

go中x = ^x代表对x按位取反

这就有些难以理解了,为啥 ^(x << 1)等于 2 * n - 1

我们先看看负数的二进制怎么表示

要计算-n的二进制表示,先计算n-1,再按位取反

那么反过来,给定一个负数的二进制x,怎么得到n是多少(也就是负多少)?就是把上面的操作反过来,先按位取反,再+1,也就是n = ^x + 1

我们目的是要得到 n * 2 - 1 ,把上面的式子带进去,得到 2 * (^x + 1) - 1 = 2 * ^x + 1

而对一个数先取反,再乘2,再加1,等于对这个数先乘2,再取反

2 * ^x + 1 = ^(2 * x)

举个例子,假设x = 10010:
在这里插入图片描述

为啥这个等式成立呢?因右边2 * x后,最低位变成0了,此时再取反的到的值,相比于先对x取反再*2得到的值来说,最低位多了个1。也就是后取反的话,比先取反多把末尾的0翻转成1了

于是得到n * 2 - 1 = ^(2 * x),对应了源码中对负数的编码


解码

如果varint中存的是偶数,那么原始值就是正数,值为ux / 2

如果varint中存的是奇数,那么原始值就是负数,那么值是多少呢?

ux / 2得到的值是n-1,最终要得到-n

我们先看n怎么得到-n?n-1再按位取反

而现在已经有n-1了,直接按位取反即可

func Varint(buf []byte) (int64, int) {
	ux, n := Uvarint(buf) 
	x := int64(ux >> 1)
    // 负数 x = n-1,要得到-n,按位取反即可
	if ux&1 != 0 {
		x = ^x
	}
	return x, n
}

局限性

注意不是所有场景都适合用varint编码:

  1. 当数值比较大时:例如用满了int64的64位,那么在varint中会用到10位,反而比定长编码多了用了20%的空间
  2. 需要随机访问时:例如一个varint数组,要随机访问下标i的值。此时就不适合用任何变长编码的数据了。因为要随机访问的前提是每个元素的长度是定长的,这样才能根据公式 i * 定长,随机访问到特定的内存空间

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/902718.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Oracle CONNECT BY、PRIOR和START WITH关键字详解

Oracle CONNECT BY、PRIOR和START WITH关键字详解 1. 基本概念2. 数据示例3. SQL示例3.1. 查询所有员工及其上级3.2. 显示层次结构3.3. 查询特定员工的子级 4. 结论 在Oracle数据库中&#xff0c;CONNECT BY、PRIOR和START WITH关键字主要用于处理层次结构数据&#xff0c;例如…

PostgreSQL的学习心得和知识总结(一百五十六)|auto_explain — log execution plans of slow queries

目录结构 注&#xff1a;提前言明 本文借鉴了以下博主、书籍或网站的内容&#xff0c;其列表如下&#xff1a; 1、参考书籍&#xff1a;《PostgreSQL数据库内核分析》 2、参考书籍&#xff1a;《数据库事务处理的艺术&#xff1a;事务管理与并发控制》 3、PostgreSQL数据库仓库…

基于 Python 的机器学习模型部署到 Flask Web 应用:从训练到部署的完整指南

目录 引言 技术栈 步骤一&#xff1a;数据预处理 步骤二&#xff1a;训练机器学习模型 步骤三&#xff1a;创建 Flask Web 应用 步骤四&#xff1a;测试 Web 应用 步骤五&#xff1a;模型的保存与加载 保存模型 加载模型并在 Flask 中使用 步骤六&#xff1a;Web 应用…

在xml 中 不等式 做转义处理的问题

对于这种要做转义处理&#xff0c;<![CDATA[ < ]]>

图文详解ChatGPT-o1完成论文写作的全流程

学境思源&#xff0c;一键生成论文初稿&#xff1a; AcademicIdeas - 学境思源AI论文写作 本月中旬OpenAI发布了OpenAI o1系列新的AI模型。 据OpenAI介绍&#xff0c;这些模型旨在花更多时间思考后再做出反应&#xff0c;就像人一样。通过训练&#xff0c;它们学会改进思维过…

如何制定有效的学习计划

文章目录 第一章&#xff1a;目标设定1.1 目标的重要性1.2 SMART原则1.3 目标设定公式 第二章&#xff1a;时间管理2.1 时间的重要性2.2 制定时间表2.3 时间管理公式2.4 番茄工作法2.5 时间分配公式 第三章&#xff1a;学习策略3.1 学习方法3.2 学习材料的选择3.3 学习效果公式…

Kaggle竞赛——灾难推文分类(Disaster Tweets)

目录 1. 准备工作2. 资源导入3. 数据处理4. 绘制词云图5. 数据可视化5.1 词数和字符数可视化5.2 元特征可视化5.3 类别可视化 6. 词元分析6.1 一元语法统计6.2 多元语法统计 7. 命名实体识别8. 推文主题提取9. 构建模型9.1 数据划分与封装9.2 模型训练与验证 10. 模型评估11. 测…

【Linux】文件IO深度解析:文件描述符与重定向的奥秘

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;Linux 目录 一&#xff1a;&#x1f525; C语言中文件IO操作 &#x1f95d; 1.C语言中的开关读写文件&#x1f98b; 1.1 fopen()&#x1f98b; 1.2 fclose()&#x1f98b; 1.3 fwrite()&#x1f98…

内容安全与系统构建加速,助力解决生成式AI时代的双重挑战

内容安全与系统构建加速&#xff0c;助力解决生成式AI时代的双重挑战 0. 前言1. PRCV 20241.1 大会简介1.2 生成式 Al 时代的内容安全与系统构建加速 2. 生成式 AI2.1 生成模型2.2 生成模型与判别模型的区别2.3 生成模型的发展 3. GAI 内容安全3.1 GAI 时代内容安全挑战3.2 图像…

面试宝典(五):用三个线程按顺序循环打印123三个数字,比如123123123

要使用三个线程按顺序循环打印123三个数字&#xff0c;势必要控制线程的执行顺序&#xff0c;可以使用java.util.concurrent包中的Semaphore类来控制线程的执行顺序。 代码示例 import java.util.concurrent.Semaphore;public class SequentialPrinting123 {private static Se…

第T8周:猫狗识别

>- **&#x1f368; 本文为[&#x1f517;365天深度学习训练营](https://mp.weixin.qq.com/s/0dvHCaOoFnW8SCp3JpzKxg) 中的学习记录博客** >- **&#x1f356; 原作者&#xff1a;[K同学啊](https://mtyjkh.blog.csdn.net/)** &#x1f37a; 要求&#xff1a; 了解mode…

离线电脑 Visual Studio Community 2017:您的许可证已过期

VS 2017社区版&#xff0c;打开后提示&#xff1a; “您的许可证已过期&#xff0c;必须进行更新。请确保已连接Internet&#xff0c;然后检查更新的许可证以继续使用本产品” 解决办法&#xff1a; &#xff08;1&#xff09;在另一台可以联网的电脑上&#xff0c;更新VS20…

8.Linux按键驱动-中断下半部

1.编程思路 1.1在gpio结构体中添加tasklet_struct结构体 1.2在probe函数中初始化tasklet结构体 1.3在中断服务程序中调度tasklet 1.4在这个函数中执行其它任务 2.代码&#xff1a; 应用程序和Makefile和上节一致 https://blog.csdn.net/weixin_40933496/article/details/1…

通过call指令来学习指令摘要表的细节

E8 cw cw 表示E8后面跟随2 字节 (什么数不知道) rel16 指在与指令同一代码段内的相对地址偏移 D ,指向Instruction Operand Encoding 表中的D列, 他告诉我们 操作数1 是一个0FFSET N.S. 在64位模式下&#xff0c;某些指令需要使用“地址覆盖前缀”&#xff08;address over…

RL学习笔记-马尔可夫过程

参考资料&#xff1a;蘑菇书、周博磊老师课程 在强化学习中&#xff0c;智能体与环境交互是通过马尔可夫决策过程来表示的&#xff0c;因此马尔可夫决策过程是强化学习的基本框架。 马尔可夫性质 指一个随机过程在给定现在状态及所有过去状态情况下&#xff0c;其未来状态的条件…

Golang | Leetcode Golang题解之第506题相对名次

题目&#xff1a; 题解&#xff1a; var desc [3]string{"Gold Medal", "Silver Medal", "Bronze Medal"}func findRelativeRanks(score []int) []string {n : len(score)type pair struct{ score, idx int }arr : make([]pair, n)for i, s : …

BERT语言模型详解【Encoder-Only】

NLP-大语言模型学习系列目录 一、注意力机制基础——RNN,Seq2Seq等基础知识 二、注意力机制【Self-Attention,自注意力模型】 三、Transformer图文详解【Attention is all you need】 四、大语言模型的Scaling Law【Power Low】 五、大语言模型微调方法详解【全量微调、PEFT、…

Android Studio 导入/删除/新建库的模块(第三方项目) - Module

文章目录 一、导入module项目 Module空项目如何导入Project工程项目二、删除module项目三、新建module项目(不常用) 一、导入module项目 首先&#xff0c;你必须要有一个工程(Project),才可以打开项目(Module) 第一步骤&#xff1a;右键项目依次点击 New -> Module 1、工…

LLM | 论文精读 | 基于大型语言模型的自主代理综述

论文标题&#xff1a;A Survey on Large Language Model based Autonomous Agents 作者&#xff1a;Lei Wang, Chen Ma, Xueyang Feng, 等 期刊&#xff1a;Frontiers of Computer Science, 2024 DOI&#xff1a;10.1007/s11704-024-40231-1 一、引言 自主代理&#xff08;…

AI 提示词(Prompt)入门 :ChatGPT 4.0 高级功能指南

这段时间 GPT4 多了很多功能&#xff0c;今天主要是增加了 GPTs Store 的介绍和 创建 GPTs 的简单方法&#xff0c;那么我们开始吧&#xff0c;文末有彩蛋。 这里主要讲解如下几个点&#xff1a; 1&#xff1a; ChatGPT 4.0 插件的使用 2&#xff1a;ChatGPT 4.0 高级数据分…