【Redis】对通用双向链表实现的理解
Redis实现的双向链表还是比较容易看得懂的,其实现的原理很经典, 代码很整洁清晰。 以下是对其源码注释的翻译及本人见解的部分说明,如有偏颇欢迎指正: /* adlist.h - 通用双向链表的实现*/#ifndef __ADLIST_H__#define __ADLIST_H__/* 目前的数据结构只使用
Redis实现的双向链表还是比较容易看得懂的,其实现的原理很经典, 代码很整洁清晰。
以下是对其源码注释的翻译及本人见解的部分说明,如有偏颇欢迎指正:
/* adlist.h - 通用双向链表的实现*/ #ifndef __ADLIST_H__ #define __ADLIST_H__ /* 目前的数据结构只使用了Node, List, and Iterator. */ /* list节点*/ typedef struct listNode { struct listNode *prev; // 前向指针 struct listNode *next; // 后向指针 void *value; // 当前节点值 } listNode; /* list迭代器*/ typedef struct listIter { listNode *next; // 节点指针 int direction; // 迭代方向 } listIter; /*链表结构*/ typedef struct list { listNode *head; // 头结点 listNode *tail; // 尾节点 void *(*dup)(void *ptr); // 复制函数 void (*free)(void *ptr); // 释放函数 int (*match)(void *ptr, void *key); // 匹对函数 unsigned long len; // 节点数量 } list; /* 函数宏定义 */ #define listLength(l) ((l)->len) // 链表长度 #define listFirst(l) ((l)->head) // 链表头节点 #define listLast(l) ((l)->tail) // 链表末节点 #define listPrevNode(n) ((n)->prev) // 指定节点的前驱节点 #define listNextNode(n) ((n)->next) // 指定节点的后继节点 #define listNodeValue(n) ((n)->value) // 指定节点的值 /* 函数指针, 设置外部调用模块的自定义的方法 */ #define listSetDupMethod(l,m) ((l)->dup = (m)) // 复制链表 #define listSetFreeMethod(l,m) ((l)->free = (m)) // 释放链表 #define listSetMatchMethod(l,m) ((l)->match = (m)) // 匹配 /* 函数指针, 获取外部调用模块的自定义的方法 */ #define listGetDupMethod(l) ((l)->dup) // 获取复制的自定义方法 #define listGetFree(l) ((l)->free) // 获取释放的自定义方法 #define listGetMatchMethod(l) ((l)->match) // 获取匹配的自定义方法 /* 函数原型 */ list *listCreate(void); // 创建链表 void listRelease(list *list); // 释放链表 list *listAddNodeHead(list *list, void *value); // 在表头添加节点 list *listAddNodeTail(list *list, void *value); // 在表尾添加节点 list *listInsertNode(list *list, listNode *old_node, void *value, int after); // 在指定位置之后添加节点 void listDelNode(list *list, listNode *node); // 删除节点 listIter *listGetIterator(list *list, int direction); // 获取列表迭代器 listNode *listNext(listIter *iter); // 获取下一个节点 void listReleaseIterator(listIter *iter); // 释放列表迭代器 list *listDup(list *orig); // 复制链表 listNode *listSearchKey(list *list, void *key); // 给定key查找节点 listNode *listIndex(list *list, long index); // 给定index查找节点 void listRewind(list *list, listIter *li); // 迭代器指针重新指向头节点 void listRewindTail(list *list, listIter *li); // 迭代器指针重新指向末节点 void listRotate(list *list); // 链表翻转, 末节点移动成为头节点 /* 迭代器的迭代方向 */ #define AL_START_HEAD 0 #define AL_START_TAIL 1 #endif /* __ADLIST_H__ */
/* adlist.c - 通用双向链表的实现 */ #include <stdlib.h> #include "adlist.h" #include "zmalloc.h" /* 创建新链表. 新建的链表可以用函数 * AlFreeList()来释放, 但调用此函数之前需要要应用手动释放对每个节点的私有值空间 * * 出现错误则返回NULL,否则返回指向该list的指针*/ list *listCreate(void) { struct list *list; if ((list = zmalloc(sizeof(*list))) == NULL) // 用了在malloc之上封装的zmalloc来申请内存 return NULL; list->head = list->tail = NULL; list->len = 0; list->dup = NULL; list->free = NULL; list->match = NULL; return list; } /* 释放链表,该方法不能失败. * */ void listRelease(list *list) { unsigned long len; listNode *current, *next; current = list->head; len = list->len; while(len--) { next = current->next; if (list->free) list->free(current->value); // 每个节点指向的空间都会被释放 zfree(current); // zfree基于系统函数free上的封装 current = next; } zfree(list); } /* 把包含指针指向的值的节点插入链表头部 * * 如发生错误,将返回NULL并且不对链表进行任何操作, * * 如成功则返回该链表指针.*/ list *listAddNodeHead(list *list, void *value) { listNode *node; if ((node = zmalloc(sizeof(*node))) == NULL) return NULL; node->value = value; if (list->len == 0) { list->head = list->tail = node; node->prev = node->next = NULL; } else { node->prev = NULL; node->next = list->head; list->head->prev = node; list->head = node; } list->len++; return list; } /* 把包含指针指向的值的节点插入链表尾部, * 如发生错误,将返回NULL并且不对链表进行任何操作, * * 如成功则返回该链表指针.*/ list *listAddNodeTail(list *list, void *value) { listNode *node; if ((node = zmalloc(sizeof(*node))) == NULL) return NULL; node->value = value; if (list->len == 0) { list->head = list->tail = node; node->prev = node->next = NULL; } else { node->prev = list->tail; node->next = NULL; list->tail->next = node; list->tail = node; } list->len++; return list; } /* 把新节点插入链表某个节点的前或后, * 如发生错误,将返回NULL并且不对链表进行任何操作, * * 如成功则返回该链表指针.*/ list *listInsertNode(list *list, listNode *old_node, void *value, int after) { listNode *node; if ((node = zmalloc(sizeof(*node))) == NULL) return NULL; node->value = value; if (after) { // after!=0则表示节点插入的位置在旧节点之后,否则在其之前 node->prev = old_node; node->next = old_node->next; if (list->tail == old_node) { list->tail = node; } } else { node->next = old_node; node->prev = old_node->prev; if (list->head == old_node) { list->head = node; } } if (node->prev != NULL) { node->prev->next = node; } if (node->next != NULL) { node->next->prev = node; } list->len++; return list; } /* 从链表中删除某个节点. * 会调用底层函数把节点的空间释放. * * 该方法不能失败. */ void listDelNode(list *list, listNode *node) { if (node->prev) node->prev->next = node->next; else list->head = node->next; if (node->next) node->next->prev = node->prev; else list->tail = node->prev; if (list->free) list->free(node->value); zfree(node); list->len--; } /* 获取迭代器对象'iter'. 在初始化之后 *每次调用listNext()都会返回链表的下一个元素. * * 该方法不能失败. */ listIter *listGetIterator(list *list, int direction) { listIter *iter; if ((iter = zmalloc(sizeof(*iter))) == NULL) return NULL; if (direction == AL_START_HEAD) iter->next = list->head; else iter->next = list->tail; iter->direction = direction; return iter; } /* 释放迭代器对象的空间 */ 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; } /* 获取迭代器的下一个元素. * 可以通过listDelNode()方法来删除当前返回的节点,但不能删除其他的节点。 * * 如果成功则返回迭代器的下一个元素,否则返回NULL; * 因此推荐以下这样使用: * * iter = listGetIterator(list,<direction>); * while ((node = listNext(iter)) != NULL) { * doSomethingWith(listNodeValue(node)); * } * * */ listNode *listNext(listIter *iter) { listNode *current = iter->next; if (current != NULL) { if (iter->direction == AL_START_HEAD) iter->next = current->next; else iter->next = current->prev; } return current; } /* 复制整个链表. * 成功则返回复制的链表指针,否则返回NULL. * * 复制的方法通过listSetDupMethod()来指定, * 如果没有指定dup方法则会完整拷贝原始节点的值. * * 原始链表不会给更改. */ list *listDup(list *orig) // 有个疑问: 既然需要保持原始链表的不被修改,为什么不加const修饰? { list *copy; listIter *iter; listNode *node; if ((copy = listCreate()) == NULL) return NULL; copy->dup = orig->dup; copy->free = orig->free; copy->match = orig->match; iter = listGetIterator(orig, AL_START_HEAD); while((node = listNext(iter)) != NULL) { void *value; if (copy->dup) { value = copy->dup(node->value); if (value == NULL) { listRelease(copy); listReleaseIterator(iter); return NULL; } } else value = node->value; if (listAddNodeTail(copy, value) == NULL) { listRelease(copy); listReleaseIterator(iter); return NULL; } } listReleaseIterator(iter); return copy; } /* 通过指定key来查找节点. * 查找节点的匹配方法可以通过listSetMatchMethod()来指定. * 如果外部调用模块没有指定匹配方法, 则直接比较key值和链表中节点指针指向的值. * * 如果正常将返回第一个匹配的节点指针,如果找不到匹配元素则返回NULL. */ listNode *listSearchKey(list *list, void *key) { listIter *iter; listNode *node; iter = listGetIterator(list, AL_START_HEAD); while((node = listNext(iter)) != NULL) { if (list->match) { // 使用自定义的match方法 if (list->match(node->value, key)) { listReleaseIterator(iter); return node; } } else { // 直接比较值 if (key == node->value) { listReleaseIterator(iter); return node; } } } listReleaseIterator(iter); // 释放iter对象 return NULL; } /* 根据index来获取元素。 * 如果传入index为非负值,说明为正向迭代: 0为头节点,1为下一个节点,以此类推. * 如果为负值,则说明为反向迭代: -1为尾节点, -2为倒数第二个节点, 以此类推 * 如果index越界则返回NULL. */ 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; } /* 翻转链表, 将尾节点插入到链表头. */ void listRotate(list *list) { listNode *tail = list->tail; if (listLength(list) <= 1) return; /* 将当前末节点从链表中摘除 */ list->tail = tail->prev; list->tail->next = NULL; /* 将末节点插入链表头 */ list->head->prev = tail; tail->prev = NULL; tail->next = list->head; list->head = tail; }
1)既然源码中list空间的创建及销毁是通过zmalloc模块的zmalloc和zfree来完成, zmalloc又是怎么实现的呢?
2)很好奇这么多对象指针都没有const作为限制, 是什么原因可以省略了它呢?

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

AI Hentai Generator
AI Hentai를 무료로 생성하십시오.

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











Redis Cluster Mode는 Sharding을 통해 Redis 인스턴스를 여러 서버에 배포하여 확장 성 및 가용성을 향상시킵니다. 시공 단계는 다음과 같습니다. 포트가 다른 홀수 redis 인스턴스를 만듭니다. 3 개의 센티넬 인스턴스를 만들고, Redis 인스턴스 및 장애 조치를 모니터링합니다. Sentinel 구성 파일 구성, Redis 인스턴스 정보 및 장애 조치 설정 모니터링 추가; Redis 인스턴스 구성 파일 구성, 클러스터 모드 활성화 및 클러스터 정보 파일 경로를 지정합니다. 각 redis 인스턴스의 정보를 포함하는 Nodes.conf 파일을 작성합니다. 클러스터를 시작하고 Create 명령을 실행하여 클러스터를 작성하고 복제본 수를 지정하십시오. 클러스터에 로그인하여 클러스터 정보 명령을 실행하여 클러스터 상태를 확인하십시오. 만들다

Redis는 해시 테이블을 사용하여 데이터를 저장하고 문자열, 목록, 해시 테이블, 컬렉션 및 주문한 컬렉션과 같은 데이터 구조를 지원합니다. Redis는 Snapshots (RDB)를 통해 데이터를 유지하고 WRITE 전용 (AOF) 메커니즘을 추가합니다. Redis는 마스터 슬레이브 복제를 사용하여 데이터 가용성을 향상시킵니다. Redis는 단일 스레드 이벤트 루프를 사용하여 연결 및 명령을 처리하여 데이터 원자력과 일관성을 보장합니다. Redis는 키의 만료 시간을 설정하고 게으른 삭제 메커니즘을 사용하여 만료 키를 삭제합니다.

Redis-Server가 찾을 수없는 문제를 해결하기위한 단계 : Redis가 올바르게 설치되어 있는지 확인하십시오. 환경 변수를 설정 redis_host 및 redis_port; Redis Server Redis-Server를 시작하십시오. 서버가 Redis-Cli Ping을 실행 중인지 확인하십시오.

Redis에서 모든 키를 보려면 세 가지 방법이 있습니다. 키 명령을 사용하여 지정된 패턴과 일치하는 모든 키를 반환하십시오. 스캔 명령을 사용하여 키를 반복하고 키 세트를 반환하십시오. 정보 명령을 사용하여 총 키 수를 얻으십시오.

Redis 버전 번호를 보려면 다음 세 가지 방법을 사용할 수 있습니다. (1) info 명령을 입력하고 (2) -version 옵션으로 서버를 시작하고 (3) 구성 파일을 봅니다.

Redis 소스 코드를 이해하는 가장 좋은 방법은 단계별로 이동하는 것입니다. Redis의 기본 사항에 익숙해집니다. 특정 모듈을 선택하거나 시작점으로 기능합니다. 모듈 또는 함수의 진입 점으로 시작하여 코드를 한 줄씩 봅니다. 함수 호출 체인을 통해 코드를 봅니다. Redis가 사용하는 기본 데이터 구조에 익숙해 지십시오. Redis가 사용하는 알고리즘을 식별하십시오.

Redis 지시 사항을 사용하려면 다음 단계가 필요합니다. Redis 클라이언트를 엽니 다. 명령 (동사 키 값)을 입력하십시오. 필요한 매개 변수를 제공합니다 (명령어마다 다름). 명령을 실행하려면 Enter를 누르십시오. Redis는 작업 결과를 나타내는 응답을 반환합니다 (일반적으로 OK 또는 -err).

Redis 비밀번호를 설정하려면 구성 파일의 요구 사항을 필요한 비밀번호로 수정하고 서비스를 다시 시작하십시오. 비밀번호로 보호 된 인스턴스에 연결할 때는 redis-cli 명령을 사용하고 호스트 이름/IP, 포트 및 비밀번호를 제공하십시오. 비밀번호의 보안에주의를 기울이고 보안을 향상시키기 위해 정기적으로 변경하십시오.
