redis数据结构源码分析——string

前面的文章大体讲解了redis的几种数据类型,针对设计表巧妙的数据类型,后续会出几篇文章单独讲解下,那么本篇文章针对string的源码进行讲解。

文章目录

  • 字符串的三种编码
  • sds结构
  • sds的设计思想和优势
  • sds API解析
    • sdsnewlen(创建字符串)
    • sdsfree(释放字符串)
    • sdscatlen(拼接字符串)
    • sdsMakeRoomFor(SDS扩容)

字符串的三种编码

int:整型
redis数据结构源码分析——string
raw:普通字符串

#define OBJ_ENCODING_RAW 0     /* Raw representation */
#define OBJ_ENCODING_INT 1     /* Encoded as integer */
#define OBJ_ENCODING_HT 2      /* Encoded as hash table */
#define OBJ_ENCODING_ZIPMAP 3  /* No longer used: old hash encoding. */
#define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */
#define OBJ_ENCODING_ZIPLIST 5 /* No longer used: old list/hash/zset encoding. */
#define OBJ_ENCODING_INTSET 6  /* Encoded as intset */
#define OBJ_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
#define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of listpacks */
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */
#define OBJ_ENCODING_LISTPACK 11 /* Encoded as a listpack */

可以看到,这其中,int,embstr,raw都属于字符串类型。
在object类中可以看到,如果长度小于44字节,则按embstr方式编码,否则按raw方式编码

#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
robj *createStringObject(const char *ptr, size_t len) {
    if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
        return createEmbeddedStringObject(ptr,len);
    else
        return createRawStringObject(ptr,len);
}

sds结构

那么我们都知道,string类型低层用的是sds来存储的,以下为sds的类型。

struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

首先解析下几个属性:
len:已使用长度
alloc:总长度
flags:标识,低三位表示类型,高五位预留
buf:柔性数组,存放数据用的
free=alloc-len
这里可以看到,这几种类型的内部属性其实都是一样的,唯一区别在于len和alloc所占的字节数。
以sdshdr16为例,我们看下大致结构图:
在这里插入图片描述

sds的设计思想和优势

1、在创建的时候返回buf指针,从外界可以直接访问sds的数据
2、可以通过len、alloc做到空间预留,可以快速取值,能达到O(1)的时间复杂度
3、根据不同的长度创建不同的sds,节省空间
4、二进制安全,可以存储二进制数据(在C中,一般字符串都是以“\0”结尾,作为C没办法存储二进制数据,但sds还可以通过len截串,找到数据。)
5、自动重新分配内存(sdsMakeRoomFor中体现),能杜绝缓冲区溢出

sds API解析

那么了解了sds结构后,我们来大致看一下sds的API

sdsnewlen(创建字符串)

首先从字符串的创建入手
代码较长,提取了重点说下,大致流程如下:
1、根据不同的长度选择不同的sds类型
2、如果是type5并且初始长度为0,强制转化成type8
3、计算不同type需要的长度,根据长度申请空间
4、根据不同类型给sdshdr属性赋初值
5、返回sds(sds.buf)

sdsfree(释放字符串)

void sdsfree(sds s) {
    if (s == NULL) return;
    s_free((char*)s-sdsHdrSize(s[-1]));
}

大致流程:
1、通过对s的偏移,计算出sds结构体的首地址
2、调用s_free释放内存
这里的第一步讲解下,也就是s_free后的括号里的内容。首先我们的入参s,是上文所说的sds中的buf[],因此,我们释放的时候,要找到首地址,才能做释放操作。其中,(char*)s是s的位置,而sdsHdrSize(s[-1])头的位置,这里为什么是减呢,因为s的位置在flags之后,要找到首地址,就要向左移动,所以是减。

sdscatlen(拼接字符串)

sds sdscatlen(sds s, const void *t, size_t len) {
    size_t curlen = sdslen(s);

    s = sdsMakeRoomFor(s,len);
    if (s == NULL) return NULL;
    memcpy(s+curlen, t, len);
    sdssetlen(s, curlen+len);
    s[curlen+len] = '\0';
    return s;
}

大致流程:
1、调用sdsMakeRoomFor(这是个扩容方法,如果不需要扩容就直接返回,需要扩容就返回扩容后的字符串)
2、判断入参中的sds是否为空,为空则返回
3、调用memcpy(字符串拼接)
4、加上结束符“\0”
这里的sdsMakeRoomFor比较重要,单独说下:

sdsMakeRoomFor(SDS扩容)

sds _sdsMakeRoomFor(sds s, size_t addlen, int greedy) {
    void *sh, *newsh;
    size_t avail = sdsavail(s);
    size_t len, newlen, reqlen;
    char type, oldtype = s[-1] & SDS_TYPE_MASK;
    int hdrlen;
    size_t usable;

    /* Return ASAP if there is enough space left. */
    if (avail >= addlen) return s;

    len = sdslen(s);
    sh = (char*)s-sdsHdrSize(oldtype);
    reqlen = newlen = (len+addlen);
    assert(newlen > len);   /* Catch size_t overflow */
    if (greedy == 1) {
        if (newlen < SDS_MAX_PREALLOC)
            newlen *= 2;
        else
            newlen += SDS_MAX_PREALLOC;
    }

    type = sdsReqType(newlen);

    /* Don't use type 5: the user is appending to the string and type 5 is
     * not able to remember empty space, so sdsMakeRoomFor() must be called
     * at every appending operation. */
    if (type == SDS_TYPE_5) type = SDS_TYPE_8;

    hdrlen = sdsHdrSize(type);
    assert(hdrlen + newlen + 1 > reqlen);  /* Catch size_t overflow */
    if (oldtype==type) {
        newsh = s_realloc_usable(sh, hdrlen+newlen+1, &usable);
        if (newsh == NULL) return NULL;
        s = (char*)newsh+hdrlen;
    } else {
        /* Since the header size changes, need to move the string forward,
         * and can't use realloc */
        newsh = s_malloc_usable(hdrlen+newlen+1, &usable);
        if (newsh == NULL) return NULL;
        memcpy((char*)newsh+hdrlen, s, len+1);
        s_free(sh);
        s = (char*)newsh+hdrlen;
        s[-1] = type;
        sdssetlen(s, len);
    }
    usable = usable-hdrlen-1;
    if (usable > sdsTypeMaxSize(type))
        usable = sdsTypeMaxSize(type);
    sdssetalloc(s, usable);
    return s;

大致流程:
1、获取当前可用空间长度avail,若大于等于新增长度addlen,说明无需扩容,直接返回
2、若avail小于addlen,s的长度加上addlen小于1M(代码中的SDS_MAX_PREALLOC就是1M),那么按新长度的2倍扩容
3、若avail小于addlen,s的长度加上addlen大于1M,那么按新长度加1M
4、根据新长度选择sds类型,若sds类型和原类型相同,则调用s_realloc_usable,扩大柔性数组
5、如果sds类型和原类型不同,则调用s_malloc_usable,重新申请内存,把原buf内容移动到新位置
6、对新串的属性进行赋值,返回。

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

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

相关文章

Linux源码解读

Linux内核源码是一个开源的操作系统内核&#xff0c;由著名的开发者林纳斯托瓦兹(Linus Torvalds)于1991年在芬兰赫尔辛基大学发布。Linux内核的源代码由一系列的C语言程序文件组成&#xff0c;这些文件包含了操作系统内核所需的所有功能&#xff0c;包括内存管理、进程调度、文…

嘴尚绝:卤味市场未来发展潜力无限,谁将成为下一个风口?

随着人们生活水平的提高&#xff0c;卤味作为一种美味的小吃&#xff0c;越来越受到消费者的喜爱。在餐饮市场上&#xff0c;卤味市场也呈现出越来越繁荣的景象。那么&#xff0c;卤味市场未来发展如何呢&#xff1f;今天&#xff0c;我们就来探讨一下这个问题。 一、消费升级推…

【漏洞复现】Hikvision SPON IP网络对讲广播系统存在命令执行漏洞CVE-2023-6895

漏洞描述 Hikvision Intercom Broadcasting System是中国海康威视(Hikvision)公司的一个对讲广播系统。 Hikvision Intercom Broadcasting System是中国海康威视(Hikvision)公司的一个对讲广播系统。Hikvision Intercom Broadcasting System 3.0.3_20201113_RELEASE(HIK)版…

【Vue3】2-3 : 选项式API的编程风格与优势

本书目录&#xff1a;点击进入 一、选项式API - 三大优势 ▶ 只有一个参数&#xff0c;不会出现参数顺序的问题&#xff0c;随意调整配置的位置 传入的是一个对象&#xff0c;没有参数顺序问题 对比反面教材&#xff1a; ▶ 非常清晰&#xff0c;语法化特别强 ▶ 非常…

轨迹合并 合并轨迹

搜索微信小程序 merge gpx

Vue3-44-Pinia- 安装步骤

介绍 本文介绍 在 vue3 中 安装 Pinia 的步骤 安装步骤 1、npm 安装 npm install pinia》 安装完成后可以看到 package.json 中添加了 pinia 的依赖信息 2、main.ts 中配置 // 引入 vue实例创建方法 import { createApp } from vue// 引入pinia import { createPinia } fro…

Linux查找命令@which、find

目录 which概念语法作用 find概念语法按文件名查找按文件大小查找 作用演示一演示二演示三 通配符 总结 which 概念 which 是一个常用的 Linux/Unix 命令&#xff0c;用于查找并显示指定命令的绝对路径。 语法 which 要查找的命令 》无参数。 》 which后面&#xff0c;跟要查…

使用Adobe Acrobat Pro DC给pdf文件填加水印

前言 GPT4的官方售价是每月20美元&#xff0c;很多人并不是天天用GPT&#xff0c;只是偶尔用一下。 如果调用官方的GPT4接口&#xff0c;就可以按使用量付费&#xff0c;用多少付多少&#xff0c;而且没有3个小时内只能提问50条的使用限制。 但是对很多人来说调用接口是比较麻烦…

Windows本地部署WampServer环境并实现远程访问服务界面

文章目录 前言1.WampServer下载安装2.WampServer启动3.安装cpolar内网穿透3.1 注册账号3.2 下载cpolar客户端3.3 登录cpolar web ui管理界面3.4 创建公网地址 4.固定公网地址访问 前言 Wamp 是一个 Windows系统下的 Apache PHP Mysql 集成安装环境&#xff0c;是一组常用来…

Cocos Creator 3.8 开发2D水面波纹Shader

使用cocos Creator 3.8做了一个游戏开中常用的2D的波浪水面,把技术点给记录一下&#xff0c;并提供完整的Shader代码。先上效果: 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 2D 波浪的基本技术原理 2D 水面波纹的主要原理就是给定一个正选波的边界&…

1.框架介绍项目环境配置与项目启动!

目录 1.框架开发方向:2.项目启动与环境搭建 1.框架开发方向: 1.前后端分离项目 2.纯后端项目 3.移动端开发uni-app(ios、Android、H5、微信小程序) 4.内容管理系统2.项目启动与环境搭建 1.安装node.js 下载地址可以用nvm安装 便于运行前端项目https://blog.csdn.net/qq_58647…

Android Studio安卓读写NFC Ntag标签源码

本示例使用的发卡器&#xff1a; https://item.taobao.com/item.htm?spma1z10.5-c-s.w4002-21818769070.11.3513789erHXVGx&id615391857885 <?xml version"1.0" encoding"utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout x…

数仓建设学习路线(二)模型建设(1)

OLTP VS OLAP OLTP 概念 全称OnLine Transaction Processing&#xff0c;中文名联机事务处理系统&#xff0c;主要是执行基本日常的事务处理&#xff0c;比如数据库记录的增删查改,例如mysql、oracle。 OLAP 概念 全称OnLine Analytical Processing&#xff0c;中文名联机…

【C语言】一种状态超时阻塞循环查询的办法

【C语言】一种状态超时阻塞循环查询的办法 文章目录 【C语言】一种状态超时阻塞循环查询的办法1.方法12.方法21.方法1 static void wait_notify_async(notify_type_t notify_type) {static rt_tick_t exit_tick;exit_tick = rt_time_get_msec();lb_int32 notify_success = RT_F…

有没有比较好的制造业工单管理系统?

制造业公司由于要处理大量的售前售后工作&#xff0c;常常会使用不同的管理系统来协助管理&#xff0c;比如客户管理用的crm系统&#xff0c;人事管理的HR系统&#xff0c;设备管理和报修管理的工单系统等等。不同类型的系统&#xff0c;都有做得比较好的行业佼佼者&#xff0c…

哈夫曼编码理解

今天学到了哈夫曼编码&#xff0c;简单理解记忆一下。 举个例子: 这里有个文本 aaaabbbcce其中a出现的概率为0.4&#xff0c;b为0.3&#xff0c;c为0.2&#xff0c;d为0.1 首先我们先定义两个规则&#xff1a; 1.上支路为0&#xff0c;下支路为1 2.概率相等时&#xff0c;合并…

请问下大家PMP证书值得考嘛?

做项目的去考&#xff0c;项目经理、产品经理这些&#xff0c;或者有往项目管理领域发展的去考。其他行业有空可以学习下 不一定要考证了。 PMP证书更多的是“敲门砖”作用&#xff0c;大部分公司招聘的门槛都要去了这个证书。 当然现在PMP管理模式也很热门&#xff0c;各大企…

2019数据结构----单链表真题

思路&#xff1a; (1)找到中间节点,将原链表一分为二 (2)后半段链表原地逆置 (3)合并链表 #include <stdio.h> #include <stdlib.h>//定义节点类型 typedef struct LNode {int data;//数据域struct LNode *next;//指针域 } LNode, *LinkList;void tailList(Link…

Mysql 下载与安装教程(详细介绍与总结)

一&#xff1a;版本介绍 首先&#xff0c;我们需要先进入官网进行下载&#xff0c;在官网中有好几个版本&#xff0c;那么这里我分别简述一下MySQL各个版本区别&#xff1a; 1&#xff1a;企业版&#xff0c;MySQL Enterprise Edition 需要付费的&#xff0c;可以免费试用30天…

安全典型配置(六)配置IPSG限制非法主机访问内网案例(静态绑定)

相关文章学习&#xff1a; 安全典型配置&#xff08;一&#xff09;使用ACL限制FTP访问权限案例 安全典型配置&#xff08;二&#xff09;使用ACL限制用户在特定时间访问特定服务器的权限案例 安全典型配置&#xff08;三&#xff09;使用ACL禁止特定用户上网案例安全典型配置…