百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

ziplist vs 普通数组,redis hash 实现

mhr18 2024-11-21 17:52 24 浏览 0 评论

@[TOC](ziplist vs 普通数组以及redis hash 在ziplist的实现)

## zipList 特点

1. 需要连续的内存地址。同时也需要预分配地址。

2. 可以存不定长的数据,但是有长度限制。

3. 从数组尾巴插入,不会造成数组移位的情况。

4. 插入,删除,都有可能造成数组移位,而造成的大量内存copy 的行为,插入,删除平均时间复杂度为O(n), 最差的时间时间复杂度为O(n2)

5. 查询效率跟普通数组相同为O(n)

**ziplist的模型如下图:**


这是整个ziplist的结构最开头

zlbytes: 是一个4字节的无符号整数,来表示整个ziplist的长度,单位为字节,好处是当ziplist做扩容的时候,我们不需要遍历整个ziplist

zltail: zltail是记录最后一个entry的起始位置,这样的好处是在于不需要在做pop操作的时候便利整个ziplist。ziptail是一个

zllen: zllen 表示整个entry的个数(注意跟上面的单位字节做区分),当需要注意的当总的entry个数超过2^16-2个的时候,这个部分是2^16-1 ,即 11111111 11111111。 所以当zllen 是这个值的时候如果需要知道整个entry 的个数,则要遍历整个entry。

zlend: 是一个字节为255的特殊entry,来表示整个ziplist的结尾位置。

entry: 可以认为是一个可变长度的数组结构,具体的entry 内部构造下面会详解。单个entry的长度是不固定


* The general layout of the ziplist is as follows:
*
* <zlbytes> <zltail> <zllen> <entry> <entry> ... <entry> <zlend>
*
* NOTE: all fields are stored in little endian, if not specified otherwise.
*
* <uint32_t zlbytes> is an unsigned integer to hold the number of bytes that
* the ziplist occupies, including the four bytes of the zlbytes field itself.
* This value needs to be stored to be able to resize the entire structure
* without the need to traverse it first.
*
* <uint32_t zltail> is the offset to the last entry in the list. This allows
* a pop operation on the far side of the list without the need for full
* traversal.
*
* <uint16_t zllen> is the number of entries. When there are more than
* 2^16-2 entries, this value is set to 2^16-1 and we need to traverse the
* entire list to know how many items it holds.
*
* <uint8_t zlend> is a special entry representing the end of the ziplist.
* Is encoded as a single byte equal to 255. No other normal entry starts
* with a byte set to the value of 255.

上面是原文的注解


**entry的结构图如下:**


entry 分为3个部分,

prevlength: 表示前一个entry的长度, prevlength 可能占用1个字节或者4个字节,当前面entry小于254个字节时候用一个字节来表示

如果前面entry长度大于254的是第一个字节表示为 11111110 为标示位(这样就是区分了zlend这个字段),后面3位来表示具体前面entry的长度。

d

encoding: 主要分为两大类,第一类是数字,第二类是字符,具体我们下面分析

data:表示具体存进来的数据。


数字类型:encoding 会占用一个字节

111111110: 表示1个字节的整数

11000000: 表示2字节的整数

11110000: 表示3字节的整数

11010000: 表示4字节的整数

11100000: 表示8字节的整数

最后一个表示的是11110001到11111101范围内,则encoding 和data 会在一起,表示 1到13.

相关代码如下

/* Read integer encoded as 'encoding' from 'p' */
int64_t zipLoadInteger(unsigned char *p, unsigned char encoding) {
    int16_t i16;
    int32_t i32;
    int64_t i64, ret = 0;
    //#define ZIP_INT_8B 0xfe
    if (encoding == ZIP_INT_8B) {
        ret = ((int8_t*)p)[0];
    } 
    //#define ZIP_INT_16B (0xc0 | 0<<4)
    else if (encoding == ZIP_INT_16B) {
        memcpy(&i16,p,sizeof(i16));
        memrev16ifbe(&i16);
        ret = i16;
     //#define ZIP_INT_16B (0xc0 | 0<<4)   
    } else if (encoding == ZIP_INT_32B) {
        memcpy(&i32,p,sizeof(i32));
        memrev32ifbe(&i32);
        ret = i32;
     // ZIP_INT_24B (0xc0 | 3<<4) 
    } else if (encoding == ZIP_INT_24B) {
        i32 = 0;
        memcpy(((uint8_t*)&i32)+1,p,sizeof(i32)-sizeof(uint8_t));
        memrev32ifbe(&i32);
        ret = i32>>8;
        //ZIP_INT_64B (0xc0 | 2<<4)
    } else if (encoding == ZIP_INT_64B) {
        memcpy(&i64,p,sizeof(i64));
        memrev64ifbe(&i64);
        ret = i64;
        //ZIP_INT_IMM_MIN 0xf1    /* 11110001 */  ZIP_INT_IMM_MAX 0xfd    /* 11111101 */
    } else if (encoding >= ZIP_INT_IMM_MIN && encoding <= ZIP_INT_IMM_MAX) {
        //实际返回的是0-12因为还减去1
        //ZIP_INT_IMM_MASK 为00001111 ZIP_INT_IMM_MAX 0xfd    /* 11111101 */
        ret = (encoding & ZIP_INT_IMM_MASK)-1;
    } else {
        assert(NULL);
    }
    return ret;
}


字符串的encoding 主要由第一个字节的前两位来判断分别是00,01,10,为标示位然后econding由分别会占用1字节, 2字节和5字节。


  1. 前面两位00,表示这是一个长度小于63字节字符串,pppppp表示字符串的长度,单位为字节,比如 00111111,即表示后面字符串是63个字节。
  2. 前面两位01, 则后面14位来表示长度即0到2^14-1,总共两个字节
  3. 前面两位为10,但是第一个字节后6位不用来表示长度,即第一个8位为10000000,后面还有4个字节表示字符的具体长度

具体的代码如下

unsigned int zipStoreEntryEncoding(unsigned char *p, unsigned char encoding, unsigned int rawlen) {
    unsigned char len = 1, buf[5];
		//如果是string encoding
    if (ZIP_IS_STR(encoding)) {
        /* Although encoding is given it may not be set for strings,
         * so we determine it here using the raw length. */
        if (rawlen <= 0x3f) {
            if (!p) return len;
            buf[0] = ZIP_STR_06B | rawlen;
        } else if (rawlen <= 0x3fff) {
            len += 1;
            if (!p) return len;
            buf[0] = ZIP_STR_14B | ((rawlen >> 8) & 0x3f);
            buf[1] = rawlen & 0xff;
        } else {
            len += 4;
            if (!p) return len;
            buf[0] = ZIP_STR_32B;
            buf[1] = (rawlen >> 24) & 0xff;
            buf[2] = (rawlen >> 16) & 0xff;
            buf[3] = (rawlen >> 8) & 0xff;
            buf[4] = rawlen & 0xff;
        }
    } else {
        /* Implies integer encoding, so length is always 1. */
        if (!p) return len;
        buf[0] = encoding;
    }

    /* Store this length at p. */
    memcpy(p,buf,len);
    return len;
}

总的来说,是一个比较灵活节省内存的数据结构,在redis 数据结构里面hash,set,和zset 会使用到该数据结构

## 一般数组的特点:

1. 需要连续的内存地址。

2. 数组的元素是定长,需要一开始就要去做声明,所以会出现一个什么的情况了即使我们一个数组里面可能大多数都是小于100的数字,但是因为我们长度限定的问题,所以会有, 数组单位长度>= 数组元素的最大长度。

**整型数组**



**引用类型数组(在java 里面一切皆为引用):**



## hash的key和value 在ziplist的存储

首先我们看到官网的的描述:



我们拿redis 的hash 来举例, 首先redis 的hash 也是通过链地址法来实现,entry 即为链表中的对象所以在总的**总entry数量少于512个****且单个entry的key或者value 不超过64个字节**的时候,hash会使用ziplist 这种数据结构来存储。

那么疑问来了,如何在一个ziplist的里面来表现形式了, **key 和value 在ziplist 为两个相邻的元素**

比如 我们在redis 客户端执行 hset test1 123 456, 那么该内存的表现形式如下图:



其它相关参考地址:

https://www.jianshu.com/p/afaf78aaf615

相关推荐

【推荐】一个开源免费、AI 驱动的智能数据管理系统,支持多数据库

如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!.前言在当今数据驱动的时代,高效、智能地管理数据已成为企业和个人不可或缺的能力。为了满足这一需求,我们推出了这款开...

Pure Storage推出统一数据管理云平台及新闪存阵列

PureStorage公司今日推出企业数据云(EnterpriseDataCloud),称其为组织在混合环境中存储、管理和使用数据方式的全面架构升级。该公司表示,EDC使组织能够在本地、云端和混...

对Java学习的10条建议(对java课程的建议)

不少Java的初学者一开始都是信心满满准备迎接挑战,但是经过一段时间的学习之后,多少都会碰到各种挫败,以下北风网就总结一些对于初学者非常有用的建议,希望能够给他们解决现实中的问题。Java编程的准备:...

SQLShift 重大更新:Oracle→PostgreSQL 存储过程转换功能上线!

官网:https://sqlshift.cn/6月,SQLShift迎来重大版本更新!作为国内首个支持Oracle->OceanBase存储过程智能转换的工具,SQLShift在过去一...

JDK21有没有什么稳定、简单又强势的特性?

佳未阿里云开发者2025年03月05日08:30浙江阿里妹导读这篇文章主要介绍了Java虚拟线程的发展及其在AJDK中的实现和优化。阅前声明:本文介绍的内容基于AJDK21.0.5[1]以及以上...

「松勤软件测试」网站总出现404 bug?总结8个原因,不信解决不了

在进行网站测试的时候,有没有碰到过网站崩溃,打不开,出现404错误等各种现象,如果你碰到了,那么恭喜你,你的网站出问题了,是什么原因导致网站出问题呢,根据松勤软件测试的总结如下:01数据库中的表空间不...

Java面试题及答案最全总结(2025版)

大家好,我是Java面试陪考员最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Spring、MySQL、JVM、Redis、Linux、Sprin...

数据库日常运维工作内容(数据库日常运维 工作内容)

#数据库日常运维工作包括哪些内容?#数据库日常运维工作是一个涵盖多个层面的综合性任务,以下是详细的分类和内容说明:一、数据库运维核心工作监控与告警性能监控:实时监控CPU、内存、I/O、连接数、锁等待...

分布式之系统底层原理(上)(底层分布式技术)

作者:allanpan,腾讯IEG高级后台工程师导言分布式事务是分布式系统必不可少的组成部分,基本上只要实现一个分布式系统就逃不开对分布式事务的支持。本文从分布式事务这个概念切入,尝试对分布式事务...

oracle 死锁了怎么办?kill 进程 直接上干货

1、查看死锁是否存在selectusername,lockwait,status,machine,programfromv$sessionwheresidin(selectsession...

SpringBoot 各种分页查询方式详解(全网最全)

一、分页查询基础概念与原理1.1什么是分页查询分页查询是指将大量数据分割成多个小块(页)进行展示的技术,它是现代Web应用中必不可少的功能。想象一下你去图书馆找书,如果所有书都堆在一张桌子上,你很难...

《战场兄弟》全事件攻略 一般事件合同事件红装及隐藏职业攻略

《战场兄弟》全事件攻略,一般事件合同事件红装及隐藏职业攻略。《战场兄弟》事件奖励,事件条件。《战场兄弟》是OverhypeStudios制作发行的一款由xcom和桌游为灵感来源,以中世纪、低魔奇幻为...

LoadRunner(loadrunner录制不到脚本)

一、核心组件与工作流程LoadRunner性能测试工具-并发测试-正版软件下载-使用教程-价格-官方代理商的架构围绕三大核心组件构建,形成完整测试闭环:VirtualUserGenerator(...

Redis数据类型介绍(redis 数据类型)

介绍Redis支持五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sortedset:有序集合)。1、字符串类型概述1.1、数据类型Redis支持...

RMAN备份监控及优化总结(rman备份原理)

今天主要介绍一下如何对RMAN备份监控及优化,这里就不讲rman备份的一些原理了,仅供参考。一、监控RMAN备份1、确定备份源与备份设备的最大速度从磁盘读的速度和磁带写的带度、备份的速度不可能超出这两...

取消回复欢迎 发表评论: