Radis

一.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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值