Redis
一.Redis SDS
1.C语言存放字符串和sds存放字符串的区别
1).C语言存放字符串的三种形态
2).sds存放字符串
2.SDS API的学习与源码分析
1).SDS的结构
typedef char* sds;
struct sdshdr
{
int len;//字符串长度 不包括\0
int free;//未使用的字节数
char buf[];//柔性数组 (不占空间)
};
2).SDS的优势
1)常数复杂度获取字符串长度:O(1)
C字符串获取字符串长度时间复杂度为O(N),使用SDS可以确保获取字符串长度的操作不会成为Redis的性能瓶颈
2)杜绝缓冲区溢出
C字符串不记录自身长度和空闲空间,容易造成缓冲区溢出,使用SDS则不会,SDS拼接字符串之前会先通过free字段检测剩余空间能否满足需求,不能满足需求的就会扩容
3)减少修改字符串时带来的内存重分配次数
使用C字符串的话:
每次对一个C字符串进行增长或缩短操作,长度都需要对这个C字符串数组进行一次内存重分配,比如C字符串的拼接,程序要先进行内存重分配来扩展字符串数组的大小,避免缓冲区溢出,又比如C字符串的缩短操作,程序需要通过内存重分配来释放不再使用的那部分空间,避免内存泄漏
使用SDS的话:
通过SDS的len属性和free属性可以实现两种内存分配的优化策略:空间预分配和惰性空间释放
1.针对内存分配的策略:空间预分配
在对SDS的空间进行扩展的时候,程序不仅会为SDS分配修改所必须的空间,还会为SDS分配额外的未使用的空间
这样可以减少连续执行字符串增长操作所需的内存重分配次数,通过这种预分配的策略,SDS将连续增长N次字符串所需的内存重分配次数从必定N次降低为最多N次,这是个很大的性能提升!
2.针对内存释放的策略:惰性空间释放
在对SDS的字符串进行缩短操作的时候,程序并不会立刻使用内存重分配来回收缩短之后多出来的字节,而是使用free属性将这些字节的数量记录下来等待将来使用,通过惰性空间释放策略,SDS避免了缩短字符串时所需的内存重分配次数,并且为将来可能有的增长操作提供了优化!
4)二进制安全
为了确保数据库可以二进制数据(图片,视频等),SDS的API都是二进制安全的,所有的API都会以处理二进制的方式来处理存放在SDS的buf数组里面的数据,程序不会对其中的数据做任何的限制,过滤,数据存进去是什么样子,读出来就是什么样子,这也是buf数组叫做字节数组而不是叫字符数组的原因,以为它是用来保存一系列二进制数据的
通过二进制安全的SDS,Redis不仅可以保存文本数据,还可以保存任意格式是二进制数
3).SDS的主要API及其源码解析
1)出错函数
static void sdsOomAbort()
{
fprintf(stderr, "SDS:out of memory\n");
abort();
//exit(EXIT_FAILURE);
//_exit();三种退出方案总结
}
2)创建一个包含给定C字符串的sds,O(N)N为给定C字符串的长度
sds sdsnewlen(const void* init, size_t initlen)
// 无类型指针 无符号整型值,不可能存在负数
{
struct sdshdr* sh = NULL;
sh = (struct sdshdr*)malloc(sizeof(struct sdshdr) + initlen + 1);//多余一字节用来存放‘\0’
// zmalloc开辟结构体空间时 需要对齐
if (NULL == sh)
{
sdsOomAbort();
}
sh->len = initlen;
//新sds不预留任何空间
sh->free = 0;
if (initlen != 0)
{
// 如果有指定初始化内容,将他们复制到sdshdr的buf中
if (init != NULL)
{// 目的 目的大小 原指针 元指针指向空间的大小
memcpy_s(sh->buf, initlen,init, initlen);
}
else
{
memset(sh->buf, 0, initlen);
}
}
sh->buf[initlen] = '\0';
//返回 buf 部分,而不是整个 sdshdr,因为sds是char指针类型的别名
return (char*)sh->buf;
}
//创建字符串
功能:
1.根据给定字符串 init ,创建一个包含同样字符串的 sds
参数
2.init :如果输入为 NULL ,那么创建一个空白 sds
否则,新创建的 sds 中包含和 init 内容相同字符串
3.返回值
sds :创建成功返回 sdshdr 相对应的 sds
创建失败返回 NULL
复杂度
T = O(N)
sds sdsnew(const char* init)
{
size_t initlen = (init == NULL) ? 0 : strlen(init);
return sdsnewlen(init, initlen);
}
3)创建空串
sds sdsempty()
{
return sdsnewlen("", 0);
}
4)返回已使用空间字节数
size_t sdslen(const sds s)
{
//return strlen(s);O(n)
// 再强转 先指向头部
struct sdshdr* sh = (struct sdshdr*)(s - sizeof(struct sdshdr));
return sh->len;
}
5)返回未使用空间字节数
size_t sdsavail(sds s)
{
struct sdshdr* sh = (struct sdshdr*)(s - sizeof(struct sdshdr));
return sh->free;
}
6)创建一个副本 克隆函数
sds sdsdup(const sds s)
{
return sdsnewlen(s, strlen(s));
}
7)释放函数
void sdsfree(sds s)
{
if (NULL == s)exit(1);
free(s - sizeof(struct sdshdr));
}
8)扩展函数 对S指向的空间进行追加
static sds sdsMakeRoomFor(sds s, size_t addlen)
{
struct sdshdr* sh=NULL, * newsh=NULL;
size_t free = sdsavail(s);
size_t len, newlen;
if (free >= addlen)return s;
len = sdslen(s);//只能对动态字符串使用
sh = (struct sdshdr*)(s - sizeof(struct sdshdr));
newlen = (len + addlen) * 2;//二分查找*0.618更好
newsh = (struct sdshdr*)realloc(sh, sizeof(struct sdshdr) + newlen + 1);
//复制成功之后会将原有的值销毁
if (NULL == newsh)
{
sdsOomAbort();
}
newsh->free = newlen - len;
return newsh->buf;
}
9)字符串拼接函数
sds sdscatlen(sds s, const void* src, size_t len)
{
struct sdshdr* sh = NULL;
size_t curlen = sdslen(s);
s = sdsMakeRoomFor(s, len);
if (NULL == s)return NULL;
sh = (struct sdshdr*)(s - sizeof(struct sdshdr));
memcpy_s(s + curlen, sh->free, src, len);
sh->len = curlen + len;
sh->free = sh->free - len;
s[curlen + len] = '\0';
return s;
}
**//将C字符串拼接到SDS字符串的末尾**
sds sdscat(sds s, const char* src)
{
return sdscatlen(s, src, strlen(src));
}
**//将给定SDS字符串拼接到另一个SDS字符串的末尾**
sds sdscatsds(sds s,const sds t)
{
return sdscatlen(s, t, sdslen(t));
}
10)将给定的C字符串复制到SDS里面,覆盖SDS原有的字符串
sds sdscpylen(sds s, const char* t, size_t len)
{
struct sdshdr* sh = (struct sdshdr*)(s - (sizeof(struct sdshdr)));
size_t totlen = sh->len + sh->free;
if (totlen < len)
{
s = sdsMakeRoomFor(s, len - sh->len);
if (s == NULL)return NULL;
sh = (struct sdshdr*)(s - (sizeof(struct sdshdr)));
totlen = sh->free + sh->free;
}
memcpy(s, t, len);
s[len] = '\0';
sh->len = len;
sh->free = totlen - len;
return s;
}
sds sdscpy(sds s,const char *t)
{
return sdscpylen(s,t,strlen(t));
}
11)用空字符串将SDS扩展至给定长度
sds sdsgrowzero(sds s, size_t len)
{
struct sdshdr* sh = (struct sdshdr*)(s - (sizeof(struct sdshdr)));
size_t totlen, curlen = sh->len;//总长度 已经使用的长度
if (len <= curlen)return s;
// 如果 len 比字符串的现有长度小,
// 那么直接返回,不做动作
s = sdsMakeRoomFor(s, len - curlen);
if (NULL == s)return NULL;
sh = (struct sdshdr*)(s - (sizeof(struct sdshdr)));
//将s+curlen空间的前(len-curlen+1)个字节用零填充
memset(s + curlen, 0, (len - curlen + 1));
totlen = sh->len + sh->free;
sh->len = len;
sh->free = totlen - sh->len;
return s;
}
12)保留SDS给定区间内的数据,不在区间内的数据会被覆盖或清除
/*
- 按索引对截取 sds 字符串的其中一段
- start 和 end 都是闭区间(包含在内)
- 索引从 0 开始,最大为 sdslen(s) - 1
- 索引可以是负数, sdslen(s) - 1 == -1
- 复杂度
- T = O(N)
/
/ - s = sdsnew(“Hello World”);
- sdsrange(s,1,-1); => “ello World”
*/
sds sdsrange(sds s,int start,int end)
{
struct sdshdr* sh = (struct sdshdr*)(s - (sizeof(struct sdshdr)));
size_t newlen, len = sdslen(s);
if (len == 0)return;
if (start < 0)
{
start = len + start;
if (start < 0)start = 0;
}
if (end < 0)
{
end = len + end;
if (end < 0)end = 0;
}
newlen = (start > end) ? 0 : (end - start) + 1;
if (newlen != 0)
{
//需要截取的起点大于等于有符号的len 那么新的sds的len=0
if (start >= (signed)len)
{
newlen = 0;
}
//终点超出了有符号的len 终点就是len-1
else if (end >= (signed)len)
{
end = len - 1;
newlen = (start > end) ? 0 : (end - start) + 1;
}
}
else
{
start = 0;
}
// 如果有需要,对字符串进行移动
if (start && newlen)memmove(sh->buf, sh->buf + start, newlen);
sh->buf[newlen] = 0;
sh->free = sh->free + (sh->len - newlen);
sh->len = newlen;
}
13)接受一个SDS和一个C字符串作为参数,从SDS中移除所有在C字符串中出现过的字符
/*
- 对 sds 左右两端进行修剪,清除其中 cset 指定的所有字符
- 在头部遇到某个字符不属于cset,则头部清除停下
- 在尾部遇到某个字符不属于cset,则尾部清除停下
- 比如 sdsstrim(xxyayabcycyxy, “xy”) 将返回 “ayabcyc”
- 复杂性:
- T = O(M*N),M 为 SDS 长度, N 为 cset 长度。
sds sdstrim(sds s,const char *cest)
{
struct sdshdr* sh = (struct sdshdr*)(s - (sizeof(struct sdshdr)));
char* start, * end, * sp, * ep;
size_t len;
sp = start = s;
ep = end = s + sdslen(s) - 1;
while (sp < end && strchr(cest, *sp))sp++;//从头部开始清除,遇到第一个不属于cset里面的字符则停止头部清除工作
while (ep > start && strchr(cest, *ep))ep--;//从尾部开始清除,遇到第一个不属于cset里面的字符则停止尾部清除工作
// 计算 trim 完毕之后剩余的字符串长度
len = (sp > ep) ? 0:((ep - sp) + 1);
if (sh->buf != sp)memmove(sh->buf, sp, len);
sh->buf[len] = '\0';
sh->free = sh->free + (sh->len - len);
sh->len = len;
return s;
}
14)对比两个字符串是否相同
int sdscmp(const sds s1, const sds s2)
{
size_t l1, l2, minlen;
int cmp;
l1 = sdslen(s1);
l2 = sdslen(s2);
minlen = (l1 < l2) ? l1 : l2;
//首先s1和s2的第一个字符相比较,若差值为零,则继续比较下一个字符
cmp = memcmp(s1, s2, minlen);
if (cmp == 0)return l1 - l2;
return cmp;
}
15)清空SDS保存的字符串内容
sds sdsclear(sds s, size_t len)
{
struct sdshdr* sh = (struct sdshdr*)(s - sizeof(struct sdshdr));
sh->free += sh->len;
sh->len = 0;
sh->buf[0] = '\0';
}
二.Redis链表
结构图
源码分析:
1链表结构·:
typedef struct listNode
{
struct listNode* prev;
struct listNode* next;
void* value;
}listNode;
typedef struct listIter//
{
listNode* next;
int direction;//0从头到尾 1从尾到头
}listIter;
typedef struct list
{
listNode* head;
listNode* tail;
unsigned int len;//节点个数
void* (*dup)(void* ptr);//复制
void(*free)(void* ptr);//释放
int (*match)(void* ptr, void* key);//比较
}list;
list结构为链表提供了表头指针head、表尾指针tail,以及链表长度计数器len,而dup、free和match成员则是用于实现多态链表所需的类型特定函数:
·dup函数用于复制链表节点所保存的值;
·free函数用于释放链表节点所保存的值;
·match函数则用于对比链表节点所保存的值和另一个输入值是否相等
2.功能实现
1)//构建列表
数据赋零 指针赋空
list* listCreate()
{
struct list* list = nullptr;//编译时计算大小
list = (struct list*)malloc(sizeof(*list));
// *list为struct list本身
if (list == nullptr)
{
return nullptr;
}
list->head = list->tail = nullptr;
list->len = 0;
list->dup = nullptr;
list->free = nullptr;
list->match = nullptr;
return list;
}
2)销毁链表
void listRelease(list* list)
{
listNode* current;
while (list->free != nullptr)
{
current = list->head;
list->head = current->next;
if (list->free != nullptr)
{
list->free(current->value);
}
free(current);
}
free(list);
}
3)头部增加节点
list* listAddNodeHead(list* list, void* value)
{
listNode* node = (listNode*)malloc(sizeof(*list));
if (node == nullptr)return nullptr;
node->value = value;
if (list->len == 0)
{
list->head = list->tail = node;
node->next = node->prev = nullptr;
}
else
{
node->prev = nullptr;//头结点没有前驱
node->next = list->head;
list->head->prev = node;
list->head = node;
}
list->len += 1;
return list;
}
4)尾部增加节点
list* listAddNodeTail(list* list, void* value)
{
listNode* node = (listNode*)malloc(sizeof(*list));
if (node == nullptr)return nullptr;
node->value = value;
if (list->len == 0)
{
list->head = list->tail = node;
node->next = node->prev = nullptr;
}
else
{
node->next = NULL;
list->tail->next = node;
node->prev = list->tail;
list->tail = node;
}
list->len += 1;
return list;
}
5)在某个节点之后插
list* listInsertNode(list* list, listNode* cur_node, void* value, bool after)
//after等于0时,在当前节点的前面插入,当after等于1时,在当前节点的后边插入
{
listNode* node = (listNode*)malloc(sizeof(*node));
if (node == nullptr)return nullptr;
node->value = value;
if (after)
{
node->prev = cur_node;
node->next = cur_node->next;
if (list->tail == cur_node)
{
list->tail = node;
}
}
else
{
node->next = cur_node;
node->prev = cur_node->prev;
if (list->head == cur_node)
{
list->head = node;
}
}
if (node->prev != nullptr)
{
node->prev->next = node;
}
if (node->next != nullptr)
{
node->next->prev = node;
}
list->len += 1;
return list;
}
6)删除某个节点
void listDelNode(list* list, listNode* node)
{
if (node->prev!=nullptr)
{
node->prev->next = node->next;
}
else
{
list->head = node->next;
}
if (node->next!= nullptr)
{
node->next->prev = node->prev;
}
else
{
list->tail = node->prev;
}
//若插入的数值为new int(i),则value处存放的是i的地址。
//此时需要先判断new int开辟的空间是否还存在,若存在,则先释放该空间,再释放valude所指向的空间
if (list->free != nullptr)
{
list->free(node->value);
}
free(node);
list->len -= 1;
}
7)获取某个方向上的迭代器
listIter* listGetIterator(list* list, int direction)
{
listIter* iter = (listIter*)malloc(sizeof(*iter));
if (iter == NULL)return NULL;
if(direction==AL_START_HEAD)
{
iter->next = list->head;
}
else
{
iter->next = list->tail;
}
iter->direction = direction;
return iter;
}
8)迭代器的内存释放及迭代方向
// 释放迭代器内存
void listReleaseIterator(listIter *iter) {
zfree(iter);
}
// 关联迭代器和链表,从头部开始迭代
void listRewind(list *list, listIter *li) {
li->next = list->head;
li->direction = AL_START_HEAD;
}
// 关联迭代器和链表,从尾部开始迭代
void listRewindTail(list *list, listIter *li) {
li->next = list->tail;
li->direction = AL_START_TAIL;
}
9)获取迭代器的下一个元素
listNode* listNext(listIter* iter)
{
listNode* current = iter->next;
if (current != nullptr)
{
if (iter->direction == AL_START_HEAD)
{
iter->next = current->next;
}
else
{
iter->next = current->prev;
}
}
return current;
}
10)复制-释放-比较
void* mydup(void* ptr)//复制
{
int x = (int)ptr;
return new int(x);
}
void myfree(void* ptr)//释放
{
delete ptr;
}
int mymatch(void* ptr, void* key)//比较
{
return *(int*)ptr == *(int*)key;
//return (int)ptr == (int)key;
}
11)查找链表
listNode *listSearchKey(list *list, void *key)
{
listIter *iter;
listNode *node;
iter = listGetIterator(list, AL_START_HEAD);
while((node = listNext(iter)) != NULL) {
if (list->match) {// 值匹配函数
if (list->match(node->value, key)) {
listReleaseIterator(iter);
return node;
}
} else { // 直接使用==
if (key == node->value) {
listReleaseIterator(iter);
return node;
}
}
}
listReleaseIterator(iter);
return NULL;
}
12)获取索引上的链表节点
listNode *listIndex(list *list, long index) {
listNode *n;
if (index < 0) { // 索引为负数,从尾部开始查找
index = (-index)-1;
n = list->tail;
while(index-- && n) n = n->prev;
} else { // 索引非负,从头部开始查找
n = list->head;
while(index-- && n) n = n->next;
}
return n;
}
13)打印函数及测试用例
void PrintInt(void* p)
{
int* ip = (int*)p;
printf("%d ", *ip);
//int x = (int)p;
//printf("%d ", x);
}
void PrintList(list* plist, void(*pfun)(void*))
{
listIter* iter = listGetIterator(plist, AL_START_TAIL);
listNode* p = nullptr;
while ((p = listNext(iter)) != nullptr)
{
pfun(p->value);
}
}
void Printf(list* plist)
{
listNode* node = plist->head;
while (node != nullptr)
{// 系统不知此处为整形,因此先强转成整形指针,再进行解析
printf("%d ", *(int*)(node->value));
//printf("%d ",(int)(node->value));
node = node->next;
}
}
void* mydup(void* ptr)//复制
{
int x = (int)ptr;
return new int(x);
}
void myfree(void* ptr)//释放
{
delete ptr;
}
int mymatch(void* ptr, void* key)
{
return *(int*)ptr == *(int*)key;
//return (int)ptr == (int)key;
}
int main()
{
//plist指针指向函数的堆区空间
list* plist = listCreate();
plist->dup = mydup;
plist->free = myfree;
for (int i = 0; i < 10; i++)
{
plist = listAddNodeHead(plist, new int(i+10));
//plist = listAddNodeHead(plist, (void*)(i + 10));
// plist = listAddNodeHead(plist, plist->dup((void*)(i + 10)));
}
Printf(plist);
printf("\n");
PrintList(plist, PrintInt);
printf("\n");
listNode* p = plist->head;
listDelNode(plist, p);
Printf(plist);
listGetIterator(plist, 0);
printf("\n");
PrintList(plist, PrintInt);
//listRelease(plist);
return 0;
}