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

news2024/11/6 14:18:19

文章目录

  • 为啥需要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 * 定长,随机访问到特定的内存空间

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

又一部神作登场,MTC高分8.8认证,堪称年度佳片

威尼斯电影节上&#xff0c;布拉迪科贝特导演的新作《粗野派》大放异彩。这部电影&#xff0c;不仅在现场收获了观众的真诚掌声&#xff0c;甚至被不少专业影评人评为“2024年度最佳电影”&#xff0c;而这个评价背后&#xff0c;是一部作品真正打动人心的力量。 布拉迪科贝特&…

低代码平台如何通过AI赋能,实现更智能的业务自动化?

引言 随着数字化转型的加速推进&#xff0c;企业在日常运营中面临的业务复杂性与日俱增。如何快速响应市场需求&#xff0c;优化流程&#xff0c;并降低开发成本&#xff0c;成为各行业共同关注的核心问题。低代码平台作为一种能够快速构建应用程序的工具&#xff0c;因其可视化…

进程、孤儿进程、僵尸进程、fork、wait简介

进程相关概念 程序和进程 程序&#xff1a;是指编译好的二进制文件&#xff0c;在磁盘上&#xff0c;占用磁盘空间, 是一个静态的概念. 进程&#xff1a;一个启动的程序&#xff0c; 进程占用的是系统资源&#xff0c;如&#xff1a;物理内存&#xff0c;CPU&#xff0c;终端等…

已解决:VS2022一直显示编译中但无法运行的情况

本问题已得到解决&#xff0c;请看以下小结&#xff1a; 关于《VS2022一直显示编译中但无法运行的情况》的解决方案 记录备注报错时间2024年报错版本VS2022报错复现突然VS2022不能启动&#xff0c;一直显示编译中&#xff0c;取消重试无效&#xff0c;重新生成解决方案无效报错…

12. MapReduce全局计数器

一. 计数器概述 在执行MapReduce程序时&#xff0c;控制台的输出中一般会包含如下内容。 这些输出就是MapReduce的全局计数器的输出信息。计数器是用来记录job的执行进度和状态的&#xff0c;它的作用可以理解为日志&#xff0c;方便用户了解任务的执行状况&#xff0c;辅助…

Springboot集成阿里云通义千问(灵积模型)

我这里集成后&#xff0c;做成了一个工具jar包&#xff0c;如果有不同方式的&#xff0c;欢迎大家讨论&#xff0c;共同进步。 集成限制&#xff1a; 1、灵积模型有QPM(QPS)限制&#xff0c;每个模型不一样&#xff0c;需要根据每个模型适配 集成开发思路&#xff1a; 因有…

今年双11,拼多多吹“新”风

文 | 螳螂观察 作者 | 陈小江 这届双11真变了。 以前提到双11&#xff0c;不管平台、商家全都盯着价格。但今年不一样。这届双11给出了新解法——平台不再把“我的价格比你低”挂在嘴边&#xff0c;转而更关心消费者体验和为商家减负。 双11这艘大船&#xff0c;在航行到第…

005 IP地址的分类

拓扑结构如下 两台主机处于同一个网关下&#xff0c;通过ping命令检测&#xff0c;可以连通 &nbps; 拓扑结构如下 使用ping 检查两台电脑是否相通, 因为网络号不一样&#xff0c;表示两台电脑不在同一个网络&#xff0c;因此无法连通 拓扑结构如下 不在同一网络的PC要相…

记本地第一次运行seatunnel示例项目

前置 静态源码编译通过&#xff1a;https://blog.csdn.net/u011924665/article/details/143372464 参考 seatunnel官方的开发环境搭建文档&#xff1a;https://seatunnel.incubator.apache.org/zh-CN/docs/2.3.5/contribution/setup 安装scala 下载scala 去官网下载&…

《暗河传》 顺利杀青,苏棋演绎“千面鬼”慕婴引期待

近日&#xff0c;由龚俊、彭小苒、常华森、杨雨潼等一众优秀演员出演的古装武侠剧《暗河传》顺利杀青&#xff0c;00后小花苏棋饰演的“千面鬼”慕婴一角也收获了许多关注的目光。 《暗河传》凭借其精彩的剧情和庞大的粉丝基础&#xff0c;自开拍起便备受关注。在剧中&#xff…

推荐一个没有广告,可以白嫖的产品宣传册转换翻页电子书的网站

​随着数字化时代的到来&#xff0c;传统的纸质宣传册逐渐被电子书所取代。为了满足企业和个人对高效、便捷的电子宣传册制作需求&#xff0c;许多在线平台应运而生。今天&#xff0c;就让我为您推荐一个无需广告干扰、完全免费使用的在线宣传册转换翻页电子书网站——【FLBOOK…

QT 从ttf文件中读取图标

最近在做项目时&#xff0c;遇到需要显示一些特殊字符的需求&#xff0c;这些特殊字符无法从键盘敲出来&#xff0c;于是乎&#xff0c;发现可以从字体库文件ttf中读取显示。 参考博客&#xff1a;QT 图标字体类IconHelper封装支持Font Awesome 5-CSDN博客 该博客封装的很不错…

[Linux关键词]unmask,mv,dev/pts,stdin stdout stderr,echo

希望你开心&#xff0c;希望你健康&#xff0c;希望你幸福&#xff0c;希望你点赞&#xff01; 最后的最后&#xff0c;关注喵&#xff0c;关注喵&#xff0c;关注喵&#xff0c;大大会看到更多有趣的博客哦&#xff01;&#xff01;&#xff01; 喵喵喵&#xff0c;你对我真的…

人工智能与伦理:我们应该如何平衡科技与人性?

内容概要 在这个瞬息万变的时代&#xff0c;人工智能的迅猛发展让我们面对前所未有的伦理困境。科技进步带来了便利&#xff0c;但同时也亟需我们反思如何对待人性。尤其是在实现算法透明性时&#xff0c;我们要确保每一个决策背后都能被理解与追溯&#xff0c;这不仅是对技术…

聚水潭数据集成到MySQL的技术实操与解决方案

聚水潭数据集成到MySQL的技术案例分享 在现代企业的数据管理过程中&#xff0c;如何高效、可靠地实现不同系统之间的数据对接是一个关键问题。本案例将聚焦于“聚水谭-仓库查询单-->BI邦盈-仓库表”的数据集成方案&#xff0c;详细探讨如何通过轻易云数据集成平台&#xff…

Mybatis-03.入门-配置SQL提示

一.配置SQL提示 目前的Springboot框架在mybatis程序中编写sql语句并没有给到任何的提示信息&#xff0c;这对于开发者而言是很不友好的。因此我们需要配置SQL提示。 配置SQL提示 这样再去写SQL语句就会有提示了。 但是会发现指定表名时并没有给出提示。这是因为&#xff1a…

计算机视觉中的点算子:从零开始构建

Hey小伙伴们&#xff01;今天我们要聊的是一个非常基础但极其重要的计算机视觉技术——点算子&#xff08;Point Operators&#xff09;。点算子主要用于对图像的每个像素进行独立的处理&#xff0c;比如亮度调整、对比度增强、灰度化等。通过这些简单的操作&#xff0c;我们可…

计算机网络:网络层 —— IPv4 协议的表示方法及其编址方法

文章目录 IPv4IPv4的表示方法IPv4的编址方法分类编址A类地址B类地址C类地址可指派的地址数量一般不使用的特殊IPv4地址 划分子网编址子网掩码默认子网掩码 无分类编址方法地址掩码斜线记法无分类域间路由选择 CIDR IPv4 IPv4&#xff08;Internet Protocol version 4&#xff…

excel自定义导出实现(使用反射)

前言 项目中接到需求&#xff0c;需要对导出的字段进行自定义导出 &#xff0c;用户可在前端选择自定义导出的字段&#xff08;如图&#xff09;&#xff0c;实现过程做以下记录&#xff0c;仅供参考&#xff1b; 思路 跟前端约定好所有要导出的字段名称(headName)跟对应的…

练习LabVIEW第十七题

学习目标&#xff1a; 刚学了LabVIEW&#xff0c;在网上找了些题&#xff0c;练习一下LabVIEW&#xff0c;有不对不好不足的地方欢迎指正&#xff01; 第十七题&#xff1a; 编写一个程序,用labview的信号生成函数产生一个三角波并显示在chart上,在编写例外一个程序读出数据…