Redis底层数据结构之String

文章目录

      • 1.概述
      • 2.RedisObject
      • 3.SDS
      • 4.SDS的优势
      • 5.String相关的指令
        • 5.1 set
        • 5.2 get
        • 5.3 append
        • 5.4 incr/incrby
        • 5.5 decr/decrby

1.概述

Redis是由C语言编写的。在C语言中,字符串标准形式是以空字符\0 作为结束符的,但是Redis里的字符串并没有沿用C语言的字符串。主要原因是如果要获取字符串的长度,C语言中调用strlen函数的时间复杂度O(N),而Redis中改造后的字符串,获取长度的时间复杂度为O(1)。

2.RedisObject

在了解String数据结构之前,先来了解一下RedisObject。

RedisObject定义了对象头的基本信息,对象头的结构如下:

struct RedisObject{
  int4 type;         // 4bits   类型
  int4 encoding;     // 4bits   存储形式
  int24 lru;         // 24bits  LRU时间
  int32 refcount;    // 4bytes  引用计数
  void *ptr;         // 8bytes  指向对象的指针
}

不同的对象具有不同的类型type,例如string,list,set,hash,zset。同一个类型的type会有不同的存储形式encoding。为了记录对象的LRU信息,使用24个bit来记录LRU信息。每个对象都有个引用计数,当引用计数为零时,对象会被销毁,内存被回收。ptr指针指向对象内容的具体位置。这样一个RedisObject对象头需要占据16字节的存储空间。

3.SDS

Redis中的字符串称为Simple Dynamic String,简称SDS,与C语言中原始的字符串相比,SDS多了一个头部信息,头部信息里面包含的内容如下:

struct sdsshr{
    T len;             //数组长度      1byte      
    T capacity;        //数组容量      1byte
    unsigned  flags;   //sdshdr类型    1byte
    char content[];    //数组内容      长度为capacity
}

buf[]存放真实的字符串内容,alloc表示所分配的buf的空间长度,len表示字符串的实际长度。所以Redis中的字符串是一个动态字符串,类似于Java当中的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配。也就可以通过O(1)的时间复杂度直接得到字符串的实际长度。

Redis的数据是存储在内存当中的,为了追求对内存的极致优化,对于不同长度的字符串,Redis底层会采用不同的结构体来表示。这也是为什么len和capacity要使用范型T来定义。在字符串比较短的时候,可以使用byte和short来表示。

三种底层编码方式如下:

  • 当存储的字符串全是数字时,此时使用int方式来存储
  • 当存储的字符串长度小于等于44字符时,使用embstr方式来存储
  • 当存储的字符串长度大于44字符时,使用raw方式来存储

对于embstr和raw这两种类型来说,存储方式存在着差异。对于embstr类型,它将RedisObject对象头和SDS对象在内存中地址是连在一起的,对于raw类型,二者的地址不是连续的。

Redis底层数据结构之String_第1张图片

疑问:为什么阈值是44个字符呢?

解答:

根据上面的RedisObject和SDS的数据结构可以得出的结论是一个字符串至少会占用19个字节的空间大小(16+3)。

而内存分配器jemalloc、tcmalloc等分配内存大小的单位都是2/4/8/16/32/64字节等,即2的n次方。为了容纳一个完整的embstr对象,最少会被分配到32字节的空间。如果稍长一些就是64个字节了。Redis就定义了大于64字节的就属于是大字符串。

64 -19 = 45。剩余可以存放字符串的空间也就是45个字节了。而字符串又是以NULL结尾的,占据了1个字节,所以embstr形式最大能容纳的字符串长度就是44个字节。

4.SDS的优势

相比较于C语言中的原始字符串而言,优势如下:

  1. 长度获取

    SDS中有len这个属性,所以可以O(1)的时间复杂度获得长度,而传统的C语言使用strlen需要O(N)

  2. 避免频繁的内存分配

    sds每次进行内存分配时,都会通过内存的预分配来减少因为修改字符串而引发的内存重分配次数。这个原理参考Java中的ArrayList。

  3. 缓冲区溢出

    缓冲区溢出是指当某个数据超过了处理程序限制的范围时候,程序出现异常操作。C语言中,由编码者来分配字符串的内存,当出现内存分配不足的时候就会出现缓存区溢出的问题。而SDS的修改函数在修改前会判断内存,动态的分配内存,避免了缓冲区溢出的问题。

  4. 二进制安全

    对于原始的C语言而言,它通过判断字符串是否存在空字符\0来确定是否已经是字符串的结尾。这也导致了无法存储纯二进制字符串,例如某些图片或者视频等二进制文件中存在\0,就会出现问题。SDS是通过len这个字段的值来判断是否已经到结尾了,所以具备了二进制安全的特性。

5.String相关的指令

介绍常用的一些指令

5.1 set

使用方法:set key value

若key已经持有其他值,则set 会覆盖旧值

可选参数如下:

  • ex second:设置键的过期时间为second秒。

    set key value ex second 效果等同于 settee key second value

    Redis底层数据结构之String_第2张图片

  • px millisecond: 设置键的过期时间为millisecond毫秒。

    set key value px millisecond 效果等同于 psetex key millisecond value。

    用法和上面类似

  • nx :只有键不存在时,才对键进行设置操作。

    set key value nx 效果等同于 setnx key value。

    Redis底层数据结构之String_第3张图片

  • xx:只有键已经存在时,才对键进行设置操作。

    set key value xx

    Redis底层数据结构之String_第4张图片

5.2 get

  • get

    使用方法:get key

    返回key对应的value值

  • getrange

    使用方法:getrange key start end

    返回key中字符串值的子字符串,截取范围由start和end偏移量决定(包括start和end在内)

    偏移量可以使用负数,类似python中的从后往前数,-1表示最后一个,-2表示倒数第二个

    Redis底层数据结构之String_第5张图片

    超过value本身范围的话,超出部分自动忽略

  • getset

    使用方法:getset key value

    将给定的key的值设为value,并返回key的旧值

    当key存在但不是字符串类型时,返回错误。

    若key没有旧值返回nil。

5.3 append

使用方法:append key value

在原先的key对应的value的基础上追加上新的value值

Redis底层数据结构之String_第6张图片

5.4 incr/incrby

  • Incr

    使用方法:incr key

    将key中存储的数字值增一

    若key不存在,那么key的值会被县初始化为0,然后再执行incr操作

    Redis底层数据结构之String_第7张图片

    如果值包含错误的类型,也就是value并不能表示数字,那么会返回错误

    Redis底层数据结构之String_第8张图片

  • incrby

    使用方法:incrby key increment

    将key中存储的数字值增加increment

    若key不存在,那么key的值会被县初始化为0,然后再执行incrby操作

    Redis底层数据结构之String_第9张图片

    如果值包含错误的类型,也就是value并不能表示数字,那么会返回错误

5.5 decr/decrby

  • decr

    使用方法:decr key

    将key中存储的数字值减1

    若key不存在,则初始化为0,然后再执行decr操作

    如果值包含错误的类型,也就是value并不能表示数字,那么会返回错误

  • decrby

    使用方法:decr key decrement

    将key中存储的数字值减少decrement

    若key不存在,则初始化为0,然后再执行decrby操作

    如果值包含错误的类型,也就是value并不能表示数字,那么会返回错误

操作和上面的incr/incrby类型。

你可能感兴趣的:(#,Redis,redis,数据结构,string)