来啊!快看---redis存储结构、基本数据类型、基本命令

先送图,先看看redis在哪里
在这里插入图片描述
要问我看这个图片有啥用,我也不知道,送给你的爱要不要

Redis现在是比较流行的缓存数据库,一般刚接触的时候都会发现其可以存储字符串(string)、哈希表(hash)、列表(list)、集合(set)、有序集合(sorted set)等。redis是一个key-value存储,value可以包含上面列出的多种结构,但是key都是字符串。也就是说key是string类型,value为上面类型的一种。

redis的基本数据类型

一般来说,redis有5大基本数据类型:

  • 字符串string
  • 哈希hash
  • 字符串列表list
  • 字符串集合set 不重复,无序
  • 有序集合sortedset ,不重复,有序

可能有的人还会加上这么几种
HyperLogLog,bitMap,GeoHash,BloomFilter
我们就来说说基本的5种

Redis中每个对象都有一个redisObject结构,该结构中和保存数据相关的三种属性分别是存储数据的类型type、值的编码属性encoding和指针ptr属性:

typedef struct redisObject{
//类型
unsigned type:4;
 
//编码
unsigned encoding:4;
 
//指向底层实现数据结构的指针
void *ptr
 
//虚拟内存和其他信息等.....
}robj;

/* Object types*/ 上面的type是指不同的基本数据结构类型
#define REDIS_STRING 0
#define REDIS_LIST 1
#define REDIS_SET 2
#define REDIS_ZSET 3
#define REDIS_HASH 4

下面是encoding属性的值

编码常量对象的名称type值
REDIS_ENCODING_INT整数int
REDIS_ENCODING_EMBSTRembstr编码的简单动态字符串(SDS)list
REDIS_ENCODING_RAW简单动态字符串raw
REDIS_ENCODING_HT字典hashtable
REDIS_ENCODING_LINKEDLIST双端链表linkedlist
REDIS_ENCODING_ZIPLIST压缩列表ziplist
REDIS_ENCODING_INTSET整数集合intset
REDIS_ENCODING_SKIPLIST跳跃表和字典skiplist

1、简单字符集合SDS

Redis是C语言开发的,C语言自己就有字符类型,但是Redis却没直接采用C语言的字符串类型,而是自己构建了动态字符串(SDS)的抽象类型。
在这里插入图片描述
这样一句话,看似创建了一个下,其实是创建了两个SDS对象,一个key对象一个value对象,
就算是字符类型的List,也是由很多的SDS构成的Key和Value罢了。
SDS结构:

struct sdshdr{
 int len;
 int free;
 char buf[];
}

一张图让key浮出水面
在这里插入图片描述
SDS与C字符串的区别

  1. 计数方式不同:C字符串的长度计算是通过遍历之后得到的数据复杂度为O(n),SDS有一个专门存储字符串长度的位置len
  2. 杜绝缓冲区溢出:C字符串中两字符串拼接是直接在第一字符串后拼接上另一个字符串
    在这里插入图片描述
    在不计算内存空间的情况下有可能会发生空间溢出的情况
    而SDS会先比较free空间大小是否能放得下新的字符串,如果可以再进行拼接,如果不够的话则进行扩容
  3. 减少修改字符串时带来的内存重分配次数
  • 空间预分配(进行拼接添加的时候),在第一次创建字符串的时候,会分配合适的空间用来存放SDS对象,不过并不会分配多余的free空间(防止空间浪费),当需要进行拼接的时候会分配free空间来用,这里分配的free空间会比要拼接的字符串长度大(这是为了防止紧接着还会有拼接操作)
    在这里插入图片描述
  • 惰性空间释放(进行裁剪的时候),在进行字符串裁剪的时候,其空余出来的空间并不会马上被回收,而是会在free属性上进行记录长度,防止紧接着会有添加操作,如果这个空间不需要啦,会调用方法进行删除使free变为0
    在这里插入图片描述
  1. 二进制安全

我们看到上面字符串中会有很多‘\o’的空字符串穿插的中间,在C字符串中其是结束的标志,那么遇到这个‘\o’岂不是就代表这个字符串没有后面啦,
但是当我们存储一个二进制数据(图片、视频等)的时候会有很多的‘\o’存在,所以用C字符串就很不安全,而redis则不用考虑这个问题

2、list

看一下代码有助于理解结构:

//list的节点结构
typedef struct listNode { /*节点*/
struct listNode *prev;
struct listNode *next;
void *value; /*value用函数指针类型,决定了value可以是sds,list,set,dict等类型*/
} listNode;


//下面是list结构的代码
typedef struct list { /*链表结构*/
listNode *head; /*头节点*/
listNode *tail; /*尾节点*/
/*类似java类里的的方法,方便调用*/
void *(*dup)(void *ptr); /*复制节点*/ //说实话,我不是很懂这个函数指针的意思,如有清楚地可以给我留言,谢谢。
void (*free)(void *ptr); /*释放节点*/
int (*match)(void *ptr, void *key); /*匹配节点,返回key值得index,但是我不清楚他在那里实现的*/
unsigned long len; /*记录链表的长度*/
} list;

什么?代码没看懂,没事,看一下下面的图片一目了然

Redis中,list的实现是一个双端链表,这样可以方便的获取其前后的节点值,方便之后对节点的查找;Redis通过list来对listNode进行持有,分别记录list的头尾节点和list长度,可在O(1)的时间复杂度上进行查找;
在这里插入图片描述

另外,list还实现了迭代器对链表进行遍历,可正向可反向,非常方便,代码如下;
typedef struct listIter {
listNode *next;
int direction; //标注迭代器的运行方向
} listIter;

list在Redis中运用相当广泛,除了实现列表外,发布和订阅、慢查询、监视器等功能也使用了链表来获取,另外,Redis服务器还使用链表来持有 多个客户端的状态信息,以及用链表来构建客户端输出缓冲区。

3、dict字典

字典结构是整个Redis的核心数据结构,基本上是其内部结构的缩影。

对于其结构下面给出代码和图片
这里简单说一下,dict持有两个dictht结构,一个用来存储数据,一个用来在rehash时使用,而dictht 数组是dictEntry 节点的持有者,并且在dictht 下仿佛是一个hashMap结构(不熟悉hashmap的话,可以看一下这个

//dictEntry是最核心的字典结构的节点结构,它保存了key和value的内容;另外,next指针是为了解决hash冲突,字典结构的hash冲突解决方法是拉链法,对于hashcode重复的节点以链表的形式存储。
typedef struct dictEntry {
void *key;
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next;
} dictEntry;


//dictht是节点dictEntry的持有者,将dictEntry结构串起来,table就是hash表,其实dictEntry *table[]这样的书写方式更容易理解些,size就是table数组的长度,used标志已有节点的数目。
typedef struct dictht {
dictEntry **table;
unsigned long size;
unsigned long sizemask; /*hash表的掩码,总是size-1,用于计算hash表的索引值*/
unsigned long used;
} dictht;


//dict是最外层的字典结构的接口形式,type标志类型,privdata标志其私有数据,
//dict持有两个dictht结构,一个用来存储数据,一个用来在rehash时使用,rehashidx标志是否正在rehash(因为Redis中rehash是一个渐近的过程,正在rehash的时候rehashidx记录rehash的阶段,否则为-1)。
//注:rehash是一个为了让负载因子(load_factor=used/size)控制在一个合理的范围内而重新分配内存和扩展结构的过程。
//iterators是一个迭代器,用于记录当前迭代的数目
typedef struct dict {
dictType *type;
void *privdata;
dictht ht[2];
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
int iterators; /* number of iterators currently running */
} dict;

在这里插入图片描述

4、intset整数集合

typedef struct intset { /*整数集合的数据结构*/
uint32_t encoding; //编码方式
uint32_t length;
int8_t contents[];
} intset;

当一个集合元素只有整数并且数量元素不多的时候,可以选择用整数集合来作为其底层实现。整数集合的数据结构如上所示。
重点说一下这个contents数组,它存储集合中的内容,并且以从小到大的顺序排列,并保证其没有重复的元素。

虽然定义中其类型为int8_t,但具体编码方式还是取决于encoding。
在这里插入图片描述

当最大的数在以上取值范围之内是便会升级到这个更大范围的数据类型,但是如果移除了这个最大取值,不会降级。
分范围定义其类型有两个好处:提高其灵活性,节约内存。但是也增加了升级的开销。
在Redis 中
整数集合的应用范围不是很广,只在实现集合时用到。

5、zskiplist(跳跃表)实现有序链表zset

我们之前学习过“跳表”结构,这里就到了时机运用的时刻
看一下令人头疼的代码:

//zskiplistNode是跳跃表的节点结构,obj指针指向存储具体对象的地址,score标志分数。
typedef struct zskiplistNode {
robj *obj; //存储对象的指针
double score; //分数
struct zskiplistNode *backward; //后退指针,每次只能退一步
struct zskiplistLevel {
struct zskiplistNode *forward; //前进指针,每次可以跳跃好几步
unsigned int span; //这个就是决定前进指针能跳跃几步的跨度标志
} level[];
} zskiplistNode;

//zskiplist持有节点,并记录头结点和尾节点以及长度,level记录层数最大的节点的层数,也就是zskiplistNode中最大的level.size。

typedef struct zskiplist {
struct zskiplistNode *header, *tail;
unsigned long length;
int level;
} zskiplist;

什么是跳跃表?
它其实是一种随机化的数据结构,一个多层的有序链表,一种基于概率统计的插入算法。按照其score分数进行排序,分数越高越往后

redis中的zset为什么不使用红黑树而使用跳跃表

  1. 首先,在做范围查询的时候,平衡树的操作要比跳跃表复杂。因为平衡树,在查询到最小值的时候,还需要采用中序遍历去查询最大值。而skipList只需要在找到最小值后,对第一层的链表(也就是最底层的链表)进行若干次遍历即可。
  2. 平衡树的删除和插入,需要对子树进行相应的调整,操作复杂。而skiplist只需要修改相邻的节点即可。
  3. 在做查询操作的时候,skiplist和平衡树都是O(logN)的时间复杂度。
  4. 从整体上来看,skiplist算法实现的难度要低于平衡树。

你等待已久的图片来啦
在这里插入图片描述

6、ziplist(压缩表)

ziplist是一个编码后的列表,是由一系列特殊编码的连续内存块组成的顺序型数据结构,特殊的设计使得内存操作非常有效率,此列表可以同时存放字符串和整数类型,列表可以在头尾各边支持推加和弹出操作在O(1)常量时间,但是,因为每次操作涉及到内存的重新分配释放,所以加大了操作的复杂性

typedef struct zlentry {
//prevrawlen为上一个数据结点的长度,prevrawlensize为记录该长度数值所需要的字节数
unsigned int prevrawlensize, prevrawlen;
//len为当前数据结点的长度,lensize表示表示当前长度表示所需的字节数
unsigned int lensize, len;
//数据结点的头部信息长度的字节数
unsigned int headersize;
//编码的方式
unsigned char encoding;
//数据结点的数据(已包含头部等信息),以字符串形式保存
unsigned char *p;
} zlentry;

zlentry是实际存储数据的节点。一个ziplist可以有多个zlentry节点,具体形式如下:
在这里插入图片描述

压缩表之所以成为压缩表,是因为它起到了一定的压缩功能,对于其他的数据结构为了快速定位,使用了大量的指针结构,这样对于长度较大的数据优势明显,但是对于长度非常小的数据,比如说一个表里的每一个数据长度都很短,但是数据量并不小,这样的话,就会出现大量的指针结构,造成内存浪费,而压缩表则分配了一块连续内存来存储,就避免了大量的指针结构,节省了内存。另外,ziplist也使用了动态分配内存的方法,也一定程度上避免了内存的浪费。下图(此图来自书本)是内存的每块代表的含义:

在这里插入图片描述
压缩表在Redis中的应用只存在于hash和list结构的实现中,为了在存储时节省内存。

redis的简单操作语句

1.String类型的数据存储获取

set key value:设置key的值为value,若存在则覆盖,不存在则自动创建decrby
get key:获取key的值,不存在返回nil表示为空,数据若不为String也回返回错误信息
getset key value:首先获取key的值再对其进行修改 del key:删除key及其数据
incr key:对key的数据进行加一操作,只能对满足Integer的数据起作用。若值不存在,那么初始化为0
decr key:对key的数据进行减一操作,只能对满足Integer的数据起作用
incrby key increment(具体数字):对key值增加
increment decrby key decrment(具体数字):对key值减少
decrement append key value:在末尾添加数据,若key不存在则新建

2.hash类型数据(即键值对形式)

hset key filed value:修改key下filed的value,若不存在则自动创建
hget key filed:获取key下filed的值
hmget key filed1 filed2 filed3…:获取key下的多个filed值 hincr
hgetall key:获取所有key中filed的值,这里不会显示filed,只有value
hdel key filed1 filed2 …:删除key下的filed,可同时多个删除
del key:删除整个key中内容
hincrby key filed incrment:增加数字
hexsit key filed:是否存在
hlen key:key中有几个filed
hkeys key:显示所有key

3.list类型

该数据结构是一个**双向链表,**有头插和尾插两种方式。输出的过程遵从栈的方式

lpush key value1 value2…:使用头插法插入数据
rpush key value1 value2…:使用尾插法插入数据
lrange key start end:显示list,从头到尾,strat表示开始显示位置最小0,end表示结束位置,-1表示末尾,-2表示末尾第二个
lpop key:从头部弹出元素
rpop key:从尾部弹出元素
llen key:获取list中的个数

4.set集合数据类型

set集合与list的最大区别是,set的无序的,取出数据的顺序是不可知的,其次set集合中不允许出现相同的value

sadd key value1 value2 …:添加数据
srem key value1 value2…:移出指定的数据
sinter key1 key2 key3:集合的交集
sunion key1 key2 key3:集合的并集

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值