- 浏览: 87969 次
- 性别:
- 来自: 广州
-
文章分类
最新评论
-
songfantasy:
不错,学习了
java与c/c++之间的数据交互-----jni点滴 -
wen0301:
有时间,读读看,并且看看花多长时间。
为了练好口语,你敢不敢每天读一遍,坚持一个月? -
wen0301:
加一些 代码,效果会更好。。。
设计模式:简单工厂、工厂方法、抽象工厂之小结与区别 -
wen0301:
能加一些实际代码 效果会更好吧~~
设计模式:简单工厂、工厂方法、抽象工厂之小结与区别 -
lijiancool:
每天一遍,都不要做其他事情了,哦滴神呀。。。。
为了练好口语,你敢不敢每天读一遍,坚持一个月?
<div class="iteye-blog-content-contain" style="font-size: 14px"><p><div id="Article"> </p>
<p> <p><a href="https://www.2cto.com/ym/" target="_blank" class="keylink">源码</a>版本: redis-4.0.1</p> </p>
<p> <p>源码位置:</p> server.h :zskiplistNode和zskiplist的数据结构定义。 t_zset.c: 以zsl开头的函数是SkipList相关的操作函数。 </p>
<p> <h1 id="一跳跃表简介">一、跳跃表简介</h1> </p>
<p> <p>跳跃表(SkipList),其实也是解决查找问题的一种数据结构,但是它既不属于平衡树结构,也不属于Hash结构,它的特点是元素是有序的。有关于跳跃表的更多解释,大家可以参考 张铁蕾老师 - Redis内部数据结构详解(6)——skiplist 中有关跳跃表的描述部分,我接下来主要分析有关于Redis跳跃表本身的代码部分,Redis作者antirez提到Redis中的实现的跳跃表与一般跳跃表相比具有以下三个特点:</p> </p>
<p> <blockquote> </p>
<p> <p>a) this implementation allows for repeated scores. // 允许分值重复<br /> b) the comparison is not just by key (our ‘score’) but by satellite data. //对比的时候不仅比较分值还比较对象的值<br /> c) there is a back pointer, so it’s a doubly linked list with the back pointers being only at “level 1”. //有一个后退指针,即在第一层实现了一个双向链表,允许后退遍历</p> </p>
<p> </blockquote> </p>
<p> <p>接下来我们去看下SkipList的数据结构定义。</p> </p>
<p> <h1 id="二数据结构定义">二、数据结构定义</h1> </p>
<p> <p>有许多数据结构的定义其实是按照(结点+组织方式)来的,结点就是一个数据点,组织方式就是把结点组织起来形成数据结构,比如 双端链表 (ListNode+list)、字典(dictEntry+dictht+dict)等,今天所说的SkipList其实也一样,我们首先看下它的结点定义:</p> </p>
<p> <pre class="brush:sql;"></p>
<p>typedef struct zskiplistNode { </p>
<p> sds ele; //数据域</p>
<p> double score; //分值 </p>
<p> struct zskiplistNode *backward; //后向指针,使得跳表第一层组织为双向链表</p>
<p> struct zskiplistLevel { //每一个结点的层级</p>
<p> struct zskiplistNode *forward; //某一层的前向结点</p>
<p> unsigned int span; //某一层距离下一个结点的跨度</p>
<p> } level[]; //level本身是一个柔性数组,最大值为32,由 ZSKIPLIST_MAXLEVEL 定义</p>
<p>} zskiplistNode;</p>
<p></pre> </p>
<p> <p>接下来是组织方式,即使用上面的zskiplistNode组织起一个SkipList:</p> </p>
<p> <pre class="brush:sql;"></p>
<p>typedef struct zskiplist {</p>
<p> struct zskiplistNode *header; //头部</p>
<p> struct zskiplistNode *tail; //尾部</p>
<p> unsigned long length; //长度,即一共有多少个元素</p>
<p> int level; //最大层级,即跳表目前的最大层级</p>
<p>} zskiplist;</pre> </p>
<p> <p>核心的数据结构就是上面两个。</p> </p>
<p> <h1 id="三创建插入查找删除释放">三、创建、插入、查找、删除、释放</h1> </p>
<p> <p>我们以下面这个例子来跟踪SkipList的代码,其中会涉及到的操作有创建、插入、查找、删除、释放等。(ps:将Redis中main函数的代码替换成下面的代码就可以测试)</p> </p>
<p> <pre class="brush:sql;"></p>
<p>// 需要声明下 zslGetElementByRank() 函数,main函数中使用</p>
<p>zkiplistNode* zslGetElementByRank(zskiplist *zsl, unsigned long rank);</p>
<p> </p>
<p>int main(int argc, char **argv) {</p>
<p> </p>
<p> </p>
<p> unsigned long ret;</p>
<p> zskiplistNode *node;</p>
<p> zskiplist *zsl = zslCreate();</p>
<p> </p>
<p> zslInsert(zsl, 65.5, sdsnew(&quot;tom&quot;)); //level = 1</p>
<p> zslInsert(zsl, 87.5, sdsnew(&quot;jack&quot;)); //level = 4</p>
<p> zslInsert(zsl, 70.0, sdsnew(&quot;alice&quot;)); //level = 3</p>
<p> zslInsert(zsl, 95.0, sdsnew(&quot;tony&quot;)); //level = 2</p>
<p> </p>
<p> zrangespec spec = { //定义一个区间, 70.0 &lt;= x &lt;= 90.0</p>
<p> .min = 70.0,</p>
<p> .max = 90.0,</p>
<p> .minex = 0,</p>
<p> .maxex = 0};</p>
<p> </p>
<p> printf(&quot;zslFirstInRange 70.0 &lt;= x &lt;= 90.0, x is:&quot;); // 找到符合区间的最小值</p>
<p> node = zslFirstInRange(zsl, &amp;spec);</p>
<p> printf(&quot;%s-&gt;%f\n&quot;, node-&gt;ele, node-&gt;score);</p>
<p> </p>
<p> printf(&quot;zslLastInRange 70.0 &lt;= x &lt;= 90.0, x is:&quot;); // 找到符合区间的最大值</p>
<p> node = zslLastInRange(zsl, &amp;spec);</p>
<p> printf(&quot;%s-&gt;%f\n&quot;, node-&gt;ele, node-&gt;score);</p>
<p> </p>
<p> printf(&quot;tony's Ranking is :&quot;); // 根据分数获取排名</p>
<p> ret = zslGetRank(zsl, 95.0, sdsnew(&quot;tony&quot;));</p>
<p> printf(&quot;%lu\n&quot;, ret);</p>
<p> </p>
<p> printf(&quot;The Rank equal 4 is :&quot;); // 根据排名获取分数</p>
<p> node = zslGetElementByRank(zsl, 4);</p>
<p> printf(&quot;%s-&gt;%f\n&quot;, node-&gt;ele, node-&gt;score);</p>
<p> </p>
<p> ret = zslDelete(zsl, 70.0, sdsnew(&quot;alice&quot;), &amp;node); // 删除元素</p>
<p> if (ret == 1) {</p>
<p> printf(&quot;Delete node:%s-&gt;%f success!\n&quot;, node-&gt;ele, node-&gt;score);</p>
<p> }</p>
<p> </p>
<p> zslFree(zsl); // 释放zsl</p>
<p> </p>
<p> return 0;</p>
<p>}</p>
<p> </p>
<p>Out &gt; </p>
<p>zslFirstInRange 70.0 &lt;= x &lt;= 90.0, x is:alice-&gt;70.000000</p>
<p>zslLastInRange 70.0 &lt;= x &lt;= 90.0, x is:jack-&gt;87.500000</p>
<p>tony's Ranking is :4</p>
<p>The Rank equal 4 is :tony-&gt;95.000000</p>
<p>Delete node:alice-&gt;70.000000 success!</pre> 接下来我们逐行分析代码,首先zskiplist *zsl = zslCreate();创建了一个SkipList,需要关注的重点是会初始化zsl-&gt;header为最大层级32,因为 ZSKIPLIST_MAXLEVEL 定义为32,这个原因与SkipList中获取Level的随机函数有关,具体参考文章开头给的博客链接。我们看下zslCreate的代码: </p>
<p> <pre class="brush:sql;"></p>
<p>zskiplist *zslCreate(void) {</p>
<p> int j;</p>
<p> zskiplist *zsl;</p>
<p> </p>
<p> zsl = zmalloc(sizeof(*zsl)); // 申请空间</p>
<p> zsl-&gt;level = 1; // 初始层级定义为1</p>
<p> zsl-&gt;length = 0;</p>
<p> zsl-&gt;header = zslCreateNode(ZSKIPLIST_MAXLEVEL,0,NULL); // 初始化header为32层</p>
<p> for (j = 0; j &lt; ZSKIPLIST_MAXLEVEL; j++) {</p>
<p> zsl-&gt;header-&gt;level[j].forward = NULL;</p>
<p> zsl-&gt;header-&gt;level[j].span = 0;</p>
<p> }</p>
<p> zsl-&gt;header-&gt;backward = NULL; </p>
<p> zsl-&gt;tail = NULL; // tail目前为NULL</p>
<p> return zsl; </p>
<p>}</p>
<p> </p>
<p>// zslCreateNode根据传入的level和score以及ele创建一个level层的zskiplistNode</p>
<p>zskiplistNode *zslCreateNode(int level, double score, sds ele) { </p>
<p> zskiplistNode *zn =</p>
<p> zmalloc(sizeof(*zn)+level*sizeof(struct zskiplistLevel));</p>
<p> zn-&gt;score = score;</p>
<p> zn-&gt;ele = ele;</p>
<p> return zn;</p>
<p>}</pre> </p>
<p> <p>目前我们的zsl如下图所示:</p> </p>
<p> <p><img alt="这里写图片描述" src="/uploadfile/Collfiles/20171114/20171114090518123.jpg" /></p> 接下来我们开始向zsl中插入数据,zslInsert(zsl, 65.5, sdsnew(&quot;tom&quot;));zslInsert的代码如下所示: </p>
<p> <pre class="brush:sql;"></p>
<p>zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {</p>
<p> </p>
<p> /* 虽然整个代码较长,但是从整体逻辑上可以分为三部分:</p>
<p> * 1:根据目前传入的score找到插入位置x,这个过程会保存各层x的前一个位置节点 </p>
<p> * 就像我们对有序单链表插入节点的时候先要找到比目前数字小的节点保存下来。</p>
<p> * 2:根据随机函数获取level,生成新的节点</p>
<p> * 3:修改各个指针的指向,将创建的新节点插入。</p>
<p> */ </p>
<p> </p>
<p> zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;</p>
<p> unsigned int rank[ZSKIPLIST_MAXLEVEL];</p>
<p> int i, level;</p>
<p> </p>
<p> /* 第一步: 根据目前传入的score找到插入位置x,并且将各层的前置节点保存至rank[]中 */</p>
<p> serverAssert(!isnan(score));</p>
<p> x = zsl-&gt;header;</p>
<p> for (i = zsl-&gt;level-1; i &gt;= 0; i--) {</p>
<p> /* store rank that is crossed to reach the insert position */</p>
<p> rank[i] = i == (zsl-&gt;level-1) ? 0 : rank[i+1];</p>
<p> while (x-&gt;level[i].forward &amp;&amp;</p>
<p> (x-&gt;level[i].forward-&gt;score &lt; score ||</p>
<p> (x-&gt;level[i].forward-&gt;score == score &amp;&amp;</p>
<p> sdscmp(x-&gt;level[i].forward-&gt;ele,ele) &lt; 0)))</p>
<p> {</p>
<p> rank[i] += x-&gt;level[i].span;</p>
<p> x = x-&gt;level[i].forward;</p>
<p> }</p>
<p> update[i] = x;</p>
<p> }</p>
<p> /* we assume the element is not already inside, since we allow duplicated</p>
<p> * scores, reinserting the same element should never happen since the</p>
<p> * caller of zslInsert() should test in the hash table if the element is</p>
<p> * already inside or not. */</p>
<p> </p>
<p> /* 第二步:获取level,生成新的节点 */</p>
<p> level = zslRandomLevel(); </p>
<p> if (level &gt; zsl-&gt;level) {</p>
<p> for (i = zsl-&gt;level; i &lt; level; i++) {</p>
<p> rank[i] = 0;</p>
<p> update[i] = zsl-&gt;header;</p>
<p> update[i]-&gt;level[i].span = zsl-&gt;length;</p>
<p> }</p>
<p> zsl-&gt;level = level;</p>
<p> }</p>
<p> x = zslCreateNode(level,score,ele);</p>
<p> </p>
<p> /* 第三步:修改各个指针的指向,将创建的新节点插入 */</p>
<p> for (i = 0; i &lt; level; i++) {</p>
<p> x-&gt;level[i].forward = update[i]-&gt;level[i].forward;</p>
<p> update[i]-&gt;level[i].forward = x;</p>
<p> </p>
<p> /* update span covered by update[i] as x is inserted here */</p>
<p> x-&gt;level[i].span = update[i]-&gt;level[i].span - (rank[0] - rank[i]);</p>
<p> update[i]-&gt;level[i].span = (rank[0] - rank[i]) + 1;</p>
<p> }</p>
<p> </p>
<p> /* increment span for untouched levels */</p>
<p> for (i = level; i &lt; zsl-&gt;level; i++) {</p>
<p> update[i]-&gt;level[i].span++;</p>
<p> }</p>
<p> </p>
<p> /* 更新backword的指向 */</p>
<p> x-&gt;backward = (update[0] == zsl-&gt;header) ? NULL : update[0];</p>
<p> if (x-&gt;level[0].forward)</p>
<p> x-&gt;level[0].forward-&gt;backward = x;</p>
<p> else</p>
<p> zsl-&gt;tail = x;</p>
<p> zsl-&gt;length++;</p>
<p> return x;</p>
<p>}</pre> </p>
<p> <p>需要注意的是span的含义,它表示当前节点距离下一个节点的跨度,之所以可以根据rank排名获取元素,就是根据span确定的。update[i]保存的就是第 i 层应该插入节点的前一个节点,在第三步更新指针的时候使用。插入了一个元素的zsl如下图所示(level=1):</p> </p>
<p> <p><img alt="这里写图片描述" src="/uploadfile/Collfiles/20171114/20171114090518124.jpg" /></p> 接着我们继续插入后面的三条数据,他们的level分别为jack-&gt;4、alice-&gt;3、tony-&gt;2,此时的zsl如下图所示,注意span的更新: </p>
<p> <p><img alt="这里写图片描述" src="/uploadfile/Collfiles/20171114/20171114090519125.jpg" /></p> 好了,插入终于结束啦!接下来我们看下查找的相关操作,上面的代码中有关查找举了4个例子,分别是:</p>
<p> <br /> 1)查找指定范围内最小的元素</p>
<p> <br /> 2)查找指定范围内最大的元素</p>
<p> <br /> 3)根据名称获取排名</p>
<p> <br /> 4)根据排名获取名称 </p>
<p> <p>我们分析下(1)和(4),(2)、(3)同理。首先来看(1),用zrangespec结构体定义了一个范围为70.0 &lt;= x &lt;= 90.0,有关zrangespec结构体如下所示:</p> </p>
<p> <pre class="brush:sql;"></p>
<p>typedef struct {</p>
<p> double min, max; // 定义最小范围和最大范围</p>
<p> int minex, maxex; // 是否包含最小和最大本身,为 0 表示包含,1 表示不包含</p>
<p>} zrangespec;</p>
<p> </p>
<p>/* 定义范围的代码如下所示 */</p>
<p>zrangespec spec = { //定义spec, 70.0 &lt;= x &lt;= 90.0</p>
<p> .min = 70.0, </p>
<p> .max = 90.0,</p>
<p> .minex = 0,</p>
<p> .maxex = 0}; //为结构体元素赋值</pre> </p>
<p> <p>下面调用zslFirstInRange()函数遍历得到了满足70.0 &lt;= x &lt;= 90.0的最小节点,代码如下:</p> </p>
<p> <pre class="brush:sql;"></p>
<p>/* Find the first node that is contained in the specified range.</p>
<p> * Returns NULL when no element is contained in the range. */</p>
<p>zskiplistNode *zslFirstInRange(zskiplist *zsl, zrangespec *range) {</p>
<p> zskiplistNode *x;</p>
<p> int i;</p>
<p> </p>
<p> /* If everything is out of range, return early. */</p>
<p> if (!zslIsInRange(zsl,range)) return NULL; // 判断给定的范围是否合法</p>
<p> </p>
<p> x = zsl-&gt;header; </p>
<p> for (i = zsl-&gt;level-1; i &gt;= 0; i--) { // 从最高的Level开始 </p>
<p> /* Go forward while *OUT* of range. */ </p>
<p> while (x-&gt;level[i].forward &amp;&amp; //只要没结束 &amp;&amp; 目前结点的score小于目标score</p>
<p> !zslValueGteMin(x-&gt;level[i].forward-&gt;score,range))</p>
<p> // 将结点走到当前的节点</p>
<p> x = x-&gt;level[i].forward;</p>
<p> }</p>
<p> </p>
<p> /* This is an inner range, so the next node cannot be NULL. */</p>
<p> x = x-&gt;level[0].forward; // 找到了符合的点</p>
<p> serverAssert(x != NULL); </p>
<p> </p>
<p> /* Check if score &lt;= max. */</p>
<p> if (!zslValueLteMax(x-&gt;score,range)) return NULL; // 判断返回的值是否小于max值</p>
<p> return x;</p>
<p>}</pre> </p>
<p> <p>可以看到,遍历的核心思想是:<br /> (1) 高Level -&gt; 低Level<br /> (2) 小score -&gt; 大score<br /> 即在从高Level遍历比较过程中,如果此时的score小于了某个高level的值,就在这个节点前一个节点降低一层Level继续往前遍历,我们找70.0的路线如下图所示(图中红线):</p> </p>
<p> <p><img alt="这里写图片描述" src="/uploadfile/Collfiles/20171114/20171114090519126.jpg" /></p> 继续看下根据排名获取元素的函数zslGetElementByRank(),主要是根据span域来完成,代码如下所示: </p>
<p> <pre class="brush:sql;"></p>
<p>zskiplistNode* zslGetElementByRank(zskiplist *zsl, unsigned long rank) {</p>
<p> zskiplistNode *x;</p>
<p> unsigned long traversed = 0;</p>
<p> int i;</p>
<p> </p>
<p> x = zsl-&gt;header;</p>
<p> for (i = zsl-&gt;level-1; i &gt;= 0; i--) {</p>
<p> while (x-&gt;level[i].forward &amp;&amp; (traversed + x-&gt;level[i].span) &lt;= rank)</p>
<p> {</p>
<p> traversed += x-&gt;level[i].span;</p>
<p> x = x-&gt;level[i].forward;</p>
<p> }</p>
<p> if (traversed == rank) {</p>
<p> return x;</p>
<p> }</p>
<p> }</p>
<p> return NULL;</p>
<p>}</pre> </p>
<p> <p>遍历的思想和之前没有什么差别,本次遍历路线如下图所示:</p> </p>
<p> <p><img alt="这里写图片描述" src="/uploadfile/Collfiles/20171114/20171114090520128.jpg" /></p> 接着我们看下Delete()函数,ret = zslDelete(zsl, 70.0, sdsnew(&quot;alice&quot;), &amp;node); 表示删除zsl中score为70.0,数据为alice的元素,这也是Redis SkipList的第二个特征,比较一个元素不仅比较score,而且比较数据,下面看下zslDelete的代码: </p>
<p> <pre class="brush:sql;"></p>
<p>int zslDelete(zskiplist *zsl, double score, sds ele, zskiplistNode **node) {</p>
<p> zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;</p>
<p> int i;</p>
<p> </p>
<p> x = zsl-&gt;header;</p>
<p> for (i = zsl-&gt;level-1; i &gt;= 0; i--) {</p>
<p> while (x-&gt;level[i].forward &amp;&amp;</p>
<p> (x-&gt;level[i].forward-&gt;score &lt; score ||</p>
<p> (x-&gt;level[i].forward-&gt;score == score &amp;&amp;</p>
<p> sdscmp(x-&gt;level[i].forward-&gt;ele,ele) &lt; 0)))</p>
<p> {</p>
<p> x = x-&gt;level[i].forward;</p>
<p> }</p>
<p> update[i] = x;</p>
<p> }</p>
<p> /* We may have multiple elements with the same score, what we need</p>
<p> * is to find the element with both the right score and object. */</p>
<p> x = x-&gt;level[0].forward;</p>
<p> if (x &amp;&amp; score == x-&gt;score &amp;&amp; sdscmp(x-&gt;ele,ele) == 0) {</p>
<p> zslDeleteNode(zsl, x, update);</p>
<p> if (!node)</p>
<p> zslFreeNode(x);</p>
<p> else</p>
<p> *node = x;</p>
<p> return 1;</p>
<p> }</p>
<p> return 0; /* not found */</p>
<p>}</pre> 需要注意的是zslDelete()第四个参数,是一个zskipListNode **类型,它如果不为NULL,那么代码在遍历找到node之后不会将其直接释放,而是将地址交给它,后续这块空间的释放就必须由我们手动处理。 遍历比较的思想和之前还是一样, 在update[]中记录下各层删除节点之前的节点。 while循环比较条件,sdscmp(x-&gt;level[i].forward-&gt;ele,ele) &lt; 0是因为插入函数zslInsert()也是按照这个逻辑插入的。 最后需要再次比较if (x &amp;&amp; score == x-&gt;score &amp;&amp; sdscmp(x-&gt;ele,ele) == 0)是因为Redis SkipList允许相同score的元素存在。 </p>
<p> <p>最后看看释放函数zslFree(zsl),思想很简单,因为level[0]一定是连续的(并且是一个双向链表),所以从level[0]依次遍历释放就行了。</p> </p>
<p> <pre class="brush:sql;"></p>
<p>/* Free a whole skiplist. */</p>
<p>void zslFree(zskiplist *zsl) {</p>
<p> zskiplistNode *node = zsl-&gt;header-&gt;level[0].forward, *next;</p>
<p> </p>
<p> zfree(zsl-&gt;header);</p>
<p> while(node) {</p>
<p> next = node-&gt;level[0].forward;</p>
<p> zslFreeNode(node);</p>
<p> node = next;</p>
<p> }</p>
<p> zfree(zsl);</p>
<p>}</pre> </p>
<p> <p>通过上面的例子,我们分析了zskiplist的创建、插入、查找、删除、释放等操作,结合数据结构的定义,基本上分析清楚了zskiplist。其实zskiplist在Redis中的主要用处是和dict一起实现Sorted Set,这个我们后续看Sorted Set的时候再分析。</p> </p>
<p> <h1 id="四性能分析">四、性能分析</h1> </p>
<p> <table> </p>
<p> <thead> </p>
<p> <tr> </p>
<p> <th> 操作</th> </p>
<p> <th> 一般性能</th> </p>
<p> <th> 最坏性能</th> </p>
<p> </tr> </p>
<p> </thead> </p>
<p> <tbody> </p>
<p> <tr> </p>
<p> <td>插入</td> </p>
<p> <td>O(log n)</td> </p>
<p> <td>O(n)</td> </p>
<p> </tr> </p>
<p> <tr> </p>
<p> <td>删除</td> </p>
<p> <td>O(log n)</td> </p>
<p> <td>O(n)</td> </p>
<p> </tr> </p>
<p> <tr> </p>
<p> <td>搜索</td> </p>
<p> <td>O(log n)</td> </p>
<p> <td>O(n)</td> </p>
<p> </tr> </p>
<p> </tbody> </p>
<p> </table> </p>
<p> <p>如果想了解更多关于跳表本身,比如RandomLevel()的随机性等,一定不要错过 维基百科 上的内容。</p> </p>
<p></div></p></div>
<p> <p><a href="https://www.2cto.com/ym/" target="_blank" class="keylink">源码</a>版本: redis-4.0.1</p> </p>
<p> <p>源码位置:</p> server.h :zskiplistNode和zskiplist的数据结构定义。 t_zset.c: 以zsl开头的函数是SkipList相关的操作函数。 </p>
<p> <h1 id="一跳跃表简介">一、跳跃表简介</h1> </p>
<p> <p>跳跃表(SkipList),其实也是解决查找问题的一种数据结构,但是它既不属于平衡树结构,也不属于Hash结构,它的特点是元素是有序的。有关于跳跃表的更多解释,大家可以参考 张铁蕾老师 - Redis内部数据结构详解(6)——skiplist 中有关跳跃表的描述部分,我接下来主要分析有关于Redis跳跃表本身的代码部分,Redis作者antirez提到Redis中的实现的跳跃表与一般跳跃表相比具有以下三个特点:</p> </p>
<p> <blockquote> </p>
<p> <p>a) this implementation allows for repeated scores. // 允许分值重复<br /> b) the comparison is not just by key (our ‘score’) but by satellite data. //对比的时候不仅比较分值还比较对象的值<br /> c) there is a back pointer, so it’s a doubly linked list with the back pointers being only at “level 1”. //有一个后退指针,即在第一层实现了一个双向链表,允许后退遍历</p> </p>
<p> </blockquote> </p>
<p> <p>接下来我们去看下SkipList的数据结构定义。</p> </p>
<p> <h1 id="二数据结构定义">二、数据结构定义</h1> </p>
<p> <p>有许多数据结构的定义其实是按照(结点+组织方式)来的,结点就是一个数据点,组织方式就是把结点组织起来形成数据结构,比如 双端链表 (ListNode+list)、字典(dictEntry+dictht+dict)等,今天所说的SkipList其实也一样,我们首先看下它的结点定义:</p> </p>
<p> <pre class="brush:sql;"></p>
<p>typedef struct zskiplistNode { </p>
<p> sds ele; //数据域</p>
<p> double score; //分值 </p>
<p> struct zskiplistNode *backward; //后向指针,使得跳表第一层组织为双向链表</p>
<p> struct zskiplistLevel { //每一个结点的层级</p>
<p> struct zskiplistNode *forward; //某一层的前向结点</p>
<p> unsigned int span; //某一层距离下一个结点的跨度</p>
<p> } level[]; //level本身是一个柔性数组,最大值为32,由 ZSKIPLIST_MAXLEVEL 定义</p>
<p>} zskiplistNode;</p>
<p></pre> </p>
<p> <p>接下来是组织方式,即使用上面的zskiplistNode组织起一个SkipList:</p> </p>
<p> <pre class="brush:sql;"></p>
<p>typedef struct zskiplist {</p>
<p> struct zskiplistNode *header; //头部</p>
<p> struct zskiplistNode *tail; //尾部</p>
<p> unsigned long length; //长度,即一共有多少个元素</p>
<p> int level; //最大层级,即跳表目前的最大层级</p>
<p>} zskiplist;</pre> </p>
<p> <p>核心的数据结构就是上面两个。</p> </p>
<p> <h1 id="三创建插入查找删除释放">三、创建、插入、查找、删除、释放</h1> </p>
<p> <p>我们以下面这个例子来跟踪SkipList的代码,其中会涉及到的操作有创建、插入、查找、删除、释放等。(ps:将Redis中main函数的代码替换成下面的代码就可以测试)</p> </p>
<p> <pre class="brush:sql;"></p>
<p>// 需要声明下 zslGetElementByRank() 函数,main函数中使用</p>
<p>zkiplistNode* zslGetElementByRank(zskiplist *zsl, unsigned long rank);</p>
<p> </p>
<p>int main(int argc, char **argv) {</p>
<p> </p>
<p> </p>
<p> unsigned long ret;</p>
<p> zskiplistNode *node;</p>
<p> zskiplist *zsl = zslCreate();</p>
<p> </p>
<p> zslInsert(zsl, 65.5, sdsnew(&quot;tom&quot;)); //level = 1</p>
<p> zslInsert(zsl, 87.5, sdsnew(&quot;jack&quot;)); //level = 4</p>
<p> zslInsert(zsl, 70.0, sdsnew(&quot;alice&quot;)); //level = 3</p>
<p> zslInsert(zsl, 95.0, sdsnew(&quot;tony&quot;)); //level = 2</p>
<p> </p>
<p> zrangespec spec = { //定义一个区间, 70.0 &lt;= x &lt;= 90.0</p>
<p> .min = 70.0,</p>
<p> .max = 90.0,</p>
<p> .minex = 0,</p>
<p> .maxex = 0};</p>
<p> </p>
<p> printf(&quot;zslFirstInRange 70.0 &lt;= x &lt;= 90.0, x is:&quot;); // 找到符合区间的最小值</p>
<p> node = zslFirstInRange(zsl, &amp;spec);</p>
<p> printf(&quot;%s-&gt;%f\n&quot;, node-&gt;ele, node-&gt;score);</p>
<p> </p>
<p> printf(&quot;zslLastInRange 70.0 &lt;= x &lt;= 90.0, x is:&quot;); // 找到符合区间的最大值</p>
<p> node = zslLastInRange(zsl, &amp;spec);</p>
<p> printf(&quot;%s-&gt;%f\n&quot;, node-&gt;ele, node-&gt;score);</p>
<p> </p>
<p> printf(&quot;tony's Ranking is :&quot;); // 根据分数获取排名</p>
<p> ret = zslGetRank(zsl, 95.0, sdsnew(&quot;tony&quot;));</p>
<p> printf(&quot;%lu\n&quot;, ret);</p>
<p> </p>
<p> printf(&quot;The Rank equal 4 is :&quot;); // 根据排名获取分数</p>
<p> node = zslGetElementByRank(zsl, 4);</p>
<p> printf(&quot;%s-&gt;%f\n&quot;, node-&gt;ele, node-&gt;score);</p>
<p> </p>
<p> ret = zslDelete(zsl, 70.0, sdsnew(&quot;alice&quot;), &amp;node); // 删除元素</p>
<p> if (ret == 1) {</p>
<p> printf(&quot;Delete node:%s-&gt;%f success!\n&quot;, node-&gt;ele, node-&gt;score);</p>
<p> }</p>
<p> </p>
<p> zslFree(zsl); // 释放zsl</p>
<p> </p>
<p> return 0;</p>
<p>}</p>
<p> </p>
<p>Out &gt; </p>
<p>zslFirstInRange 70.0 &lt;= x &lt;= 90.0, x is:alice-&gt;70.000000</p>
<p>zslLastInRange 70.0 &lt;= x &lt;= 90.0, x is:jack-&gt;87.500000</p>
<p>tony's Ranking is :4</p>
<p>The Rank equal 4 is :tony-&gt;95.000000</p>
<p>Delete node:alice-&gt;70.000000 success!</pre> 接下来我们逐行分析代码,首先zskiplist *zsl = zslCreate();创建了一个SkipList,需要关注的重点是会初始化zsl-&gt;header为最大层级32,因为 ZSKIPLIST_MAXLEVEL 定义为32,这个原因与SkipList中获取Level的随机函数有关,具体参考文章开头给的博客链接。我们看下zslCreate的代码: </p>
<p> <pre class="brush:sql;"></p>
<p>zskiplist *zslCreate(void) {</p>
<p> int j;</p>
<p> zskiplist *zsl;</p>
<p> </p>
<p> zsl = zmalloc(sizeof(*zsl)); // 申请空间</p>
<p> zsl-&gt;level = 1; // 初始层级定义为1</p>
<p> zsl-&gt;length = 0;</p>
<p> zsl-&gt;header = zslCreateNode(ZSKIPLIST_MAXLEVEL,0,NULL); // 初始化header为32层</p>
<p> for (j = 0; j &lt; ZSKIPLIST_MAXLEVEL; j++) {</p>
<p> zsl-&gt;header-&gt;level[j].forward = NULL;</p>
<p> zsl-&gt;header-&gt;level[j].span = 0;</p>
<p> }</p>
<p> zsl-&gt;header-&gt;backward = NULL; </p>
<p> zsl-&gt;tail = NULL; // tail目前为NULL</p>
<p> return zsl; </p>
<p>}</p>
<p> </p>
<p>// zslCreateNode根据传入的level和score以及ele创建一个level层的zskiplistNode</p>
<p>zskiplistNode *zslCreateNode(int level, double score, sds ele) { </p>
<p> zskiplistNode *zn =</p>
<p> zmalloc(sizeof(*zn)+level*sizeof(struct zskiplistLevel));</p>
<p> zn-&gt;score = score;</p>
<p> zn-&gt;ele = ele;</p>
<p> return zn;</p>
<p>}</pre> </p>
<p> <p>目前我们的zsl如下图所示:</p> </p>
<p> <p><img alt="这里写图片描述" src="/uploadfile/Collfiles/20171114/20171114090518123.jpg" /></p> 接下来我们开始向zsl中插入数据,zslInsert(zsl, 65.5, sdsnew(&quot;tom&quot;));zslInsert的代码如下所示: </p>
<p> <pre class="brush:sql;"></p>
<p>zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {</p>
<p> </p>
<p> /* 虽然整个代码较长,但是从整体逻辑上可以分为三部分:</p>
<p> * 1:根据目前传入的score找到插入位置x,这个过程会保存各层x的前一个位置节点 </p>
<p> * 就像我们对有序单链表插入节点的时候先要找到比目前数字小的节点保存下来。</p>
<p> * 2:根据随机函数获取level,生成新的节点</p>
<p> * 3:修改各个指针的指向,将创建的新节点插入。</p>
<p> */ </p>
<p> </p>
<p> zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;</p>
<p> unsigned int rank[ZSKIPLIST_MAXLEVEL];</p>
<p> int i, level;</p>
<p> </p>
<p> /* 第一步: 根据目前传入的score找到插入位置x,并且将各层的前置节点保存至rank[]中 */</p>
<p> serverAssert(!isnan(score));</p>
<p> x = zsl-&gt;header;</p>
<p> for (i = zsl-&gt;level-1; i &gt;= 0; i--) {</p>
<p> /* store rank that is crossed to reach the insert position */</p>
<p> rank[i] = i == (zsl-&gt;level-1) ? 0 : rank[i+1];</p>
<p> while (x-&gt;level[i].forward &amp;&amp;</p>
<p> (x-&gt;level[i].forward-&gt;score &lt; score ||</p>
<p> (x-&gt;level[i].forward-&gt;score == score &amp;&amp;</p>
<p> sdscmp(x-&gt;level[i].forward-&gt;ele,ele) &lt; 0)))</p>
<p> {</p>
<p> rank[i] += x-&gt;level[i].span;</p>
<p> x = x-&gt;level[i].forward;</p>
<p> }</p>
<p> update[i] = x;</p>
<p> }</p>
<p> /* we assume the element is not already inside, since we allow duplicated</p>
<p> * scores, reinserting the same element should never happen since the</p>
<p> * caller of zslInsert() should test in the hash table if the element is</p>
<p> * already inside or not. */</p>
<p> </p>
<p> /* 第二步:获取level,生成新的节点 */</p>
<p> level = zslRandomLevel(); </p>
<p> if (level &gt; zsl-&gt;level) {</p>
<p> for (i = zsl-&gt;level; i &lt; level; i++) {</p>
<p> rank[i] = 0;</p>
<p> update[i] = zsl-&gt;header;</p>
<p> update[i]-&gt;level[i].span = zsl-&gt;length;</p>
<p> }</p>
<p> zsl-&gt;level = level;</p>
<p> }</p>
<p> x = zslCreateNode(level,score,ele);</p>
<p> </p>
<p> /* 第三步:修改各个指针的指向,将创建的新节点插入 */</p>
<p> for (i = 0; i &lt; level; i++) {</p>
<p> x-&gt;level[i].forward = update[i]-&gt;level[i].forward;</p>
<p> update[i]-&gt;level[i].forward = x;</p>
<p> </p>
<p> /* update span covered by update[i] as x is inserted here */</p>
<p> x-&gt;level[i].span = update[i]-&gt;level[i].span - (rank[0] - rank[i]);</p>
<p> update[i]-&gt;level[i].span = (rank[0] - rank[i]) + 1;</p>
<p> }</p>
<p> </p>
<p> /* increment span for untouched levels */</p>
<p> for (i = level; i &lt; zsl-&gt;level; i++) {</p>
<p> update[i]-&gt;level[i].span++;</p>
<p> }</p>
<p> </p>
<p> /* 更新backword的指向 */</p>
<p> x-&gt;backward = (update[0] == zsl-&gt;header) ? NULL : update[0];</p>
<p> if (x-&gt;level[0].forward)</p>
<p> x-&gt;level[0].forward-&gt;backward = x;</p>
<p> else</p>
<p> zsl-&gt;tail = x;</p>
<p> zsl-&gt;length++;</p>
<p> return x;</p>
<p>}</pre> </p>
<p> <p>需要注意的是span的含义,它表示当前节点距离下一个节点的跨度,之所以可以根据rank排名获取元素,就是根据span确定的。update[i]保存的就是第 i 层应该插入节点的前一个节点,在第三步更新指针的时候使用。插入了一个元素的zsl如下图所示(level=1):</p> </p>
<p> <p><img alt="这里写图片描述" src="/uploadfile/Collfiles/20171114/20171114090518124.jpg" /></p> 接着我们继续插入后面的三条数据,他们的level分别为jack-&gt;4、alice-&gt;3、tony-&gt;2,此时的zsl如下图所示,注意span的更新: </p>
<p> <p><img alt="这里写图片描述" src="/uploadfile/Collfiles/20171114/20171114090519125.jpg" /></p> 好了,插入终于结束啦!接下来我们看下查找的相关操作,上面的代码中有关查找举了4个例子,分别是:</p>
<p> <br /> 1)查找指定范围内最小的元素</p>
<p> <br /> 2)查找指定范围内最大的元素</p>
<p> <br /> 3)根据名称获取排名</p>
<p> <br /> 4)根据排名获取名称 </p>
<p> <p>我们分析下(1)和(4),(2)、(3)同理。首先来看(1),用zrangespec结构体定义了一个范围为70.0 &lt;= x &lt;= 90.0,有关zrangespec结构体如下所示:</p> </p>
<p> <pre class="brush:sql;"></p>
<p>typedef struct {</p>
<p> double min, max; // 定义最小范围和最大范围</p>
<p> int minex, maxex; // 是否包含最小和最大本身,为 0 表示包含,1 表示不包含</p>
<p>} zrangespec;</p>
<p> </p>
<p>/* 定义范围的代码如下所示 */</p>
<p>zrangespec spec = { //定义spec, 70.0 &lt;= x &lt;= 90.0</p>
<p> .min = 70.0, </p>
<p> .max = 90.0,</p>
<p> .minex = 0,</p>
<p> .maxex = 0}; //为结构体元素赋值</pre> </p>
<p> <p>下面调用zslFirstInRange()函数遍历得到了满足70.0 &lt;= x &lt;= 90.0的最小节点,代码如下:</p> </p>
<p> <pre class="brush:sql;"></p>
<p>/* Find the first node that is contained in the specified range.</p>
<p> * Returns NULL when no element is contained in the range. */</p>
<p>zskiplistNode *zslFirstInRange(zskiplist *zsl, zrangespec *range) {</p>
<p> zskiplistNode *x;</p>
<p> int i;</p>
<p> </p>
<p> /* If everything is out of range, return early. */</p>
<p> if (!zslIsInRange(zsl,range)) return NULL; // 判断给定的范围是否合法</p>
<p> </p>
<p> x = zsl-&gt;header; </p>
<p> for (i = zsl-&gt;level-1; i &gt;= 0; i--) { // 从最高的Level开始 </p>
<p> /* Go forward while *OUT* of range. */ </p>
<p> while (x-&gt;level[i].forward &amp;&amp; //只要没结束 &amp;&amp; 目前结点的score小于目标score</p>
<p> !zslValueGteMin(x-&gt;level[i].forward-&gt;score,range))</p>
<p> // 将结点走到当前的节点</p>
<p> x = x-&gt;level[i].forward;</p>
<p> }</p>
<p> </p>
<p> /* This is an inner range, so the next node cannot be NULL. */</p>
<p> x = x-&gt;level[0].forward; // 找到了符合的点</p>
<p> serverAssert(x != NULL); </p>
<p> </p>
<p> /* Check if score &lt;= max. */</p>
<p> if (!zslValueLteMax(x-&gt;score,range)) return NULL; // 判断返回的值是否小于max值</p>
<p> return x;</p>
<p>}</pre> </p>
<p> <p>可以看到,遍历的核心思想是:<br /> (1) 高Level -&gt; 低Level<br /> (2) 小score -&gt; 大score<br /> 即在从高Level遍历比较过程中,如果此时的score小于了某个高level的值,就在这个节点前一个节点降低一层Level继续往前遍历,我们找70.0的路线如下图所示(图中红线):</p> </p>
<p> <p><img alt="这里写图片描述" src="/uploadfile/Collfiles/20171114/20171114090519126.jpg" /></p> 继续看下根据排名获取元素的函数zslGetElementByRank(),主要是根据span域来完成,代码如下所示: </p>
<p> <pre class="brush:sql;"></p>
<p>zskiplistNode* zslGetElementByRank(zskiplist *zsl, unsigned long rank) {</p>
<p> zskiplistNode *x;</p>
<p> unsigned long traversed = 0;</p>
<p> int i;</p>
<p> </p>
<p> x = zsl-&gt;header;</p>
<p> for (i = zsl-&gt;level-1; i &gt;= 0; i--) {</p>
<p> while (x-&gt;level[i].forward &amp;&amp; (traversed + x-&gt;level[i].span) &lt;= rank)</p>
<p> {</p>
<p> traversed += x-&gt;level[i].span;</p>
<p> x = x-&gt;level[i].forward;</p>
<p> }</p>
<p> if (traversed == rank) {</p>
<p> return x;</p>
<p> }</p>
<p> }</p>
<p> return NULL;</p>
<p>}</pre> </p>
<p> <p>遍历的思想和之前没有什么差别,本次遍历路线如下图所示:</p> </p>
<p> <p><img alt="这里写图片描述" src="/uploadfile/Collfiles/20171114/20171114090520128.jpg" /></p> 接着我们看下Delete()函数,ret = zslDelete(zsl, 70.0, sdsnew(&quot;alice&quot;), &amp;node); 表示删除zsl中score为70.0,数据为alice的元素,这也是Redis SkipList的第二个特征,比较一个元素不仅比较score,而且比较数据,下面看下zslDelete的代码: </p>
<p> <pre class="brush:sql;"></p>
<p>int zslDelete(zskiplist *zsl, double score, sds ele, zskiplistNode **node) {</p>
<p> zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;</p>
<p> int i;</p>
<p> </p>
<p> x = zsl-&gt;header;</p>
<p> for (i = zsl-&gt;level-1; i &gt;= 0; i--) {</p>
<p> while (x-&gt;level[i].forward &amp;&amp;</p>
<p> (x-&gt;level[i].forward-&gt;score &lt; score ||</p>
<p> (x-&gt;level[i].forward-&gt;score == score &amp;&amp;</p>
<p> sdscmp(x-&gt;level[i].forward-&gt;ele,ele) &lt; 0)))</p>
<p> {</p>
<p> x = x-&gt;level[i].forward;</p>
<p> }</p>
<p> update[i] = x;</p>
<p> }</p>
<p> /* We may have multiple elements with the same score, what we need</p>
<p> * is to find the element with both the right score and object. */</p>
<p> x = x-&gt;level[0].forward;</p>
<p> if (x &amp;&amp; score == x-&gt;score &amp;&amp; sdscmp(x-&gt;ele,ele) == 0) {</p>
<p> zslDeleteNode(zsl, x, update);</p>
<p> if (!node)</p>
<p> zslFreeNode(x);</p>
<p> else</p>
<p> *node = x;</p>
<p> return 1;</p>
<p> }</p>
<p> return 0; /* not found */</p>
<p>}</pre> 需要注意的是zslDelete()第四个参数,是一个zskipListNode **类型,它如果不为NULL,那么代码在遍历找到node之后不会将其直接释放,而是将地址交给它,后续这块空间的释放就必须由我们手动处理。 遍历比较的思想和之前还是一样, 在update[]中记录下各层删除节点之前的节点。 while循环比较条件,sdscmp(x-&gt;level[i].forward-&gt;ele,ele) &lt; 0是因为插入函数zslInsert()也是按照这个逻辑插入的。 最后需要再次比较if (x &amp;&amp; score == x-&gt;score &amp;&amp; sdscmp(x-&gt;ele,ele) == 0)是因为Redis SkipList允许相同score的元素存在。 </p>
<p> <p>最后看看释放函数zslFree(zsl),思想很简单,因为level[0]一定是连续的(并且是一个双向链表),所以从level[0]依次遍历释放就行了。</p> </p>
<p> <pre class="brush:sql;"></p>
<p>/* Free a whole skiplist. */</p>
<p>void zslFree(zskiplist *zsl) {</p>
<p> zskiplistNode *node = zsl-&gt;header-&gt;level[0].forward, *next;</p>
<p> </p>
<p> zfree(zsl-&gt;header);</p>
<p> while(node) {</p>
<p> next = node-&gt;level[0].forward;</p>
<p> zslFreeNode(node);</p>
<p> node = next;</p>
<p> }</p>
<p> zfree(zsl);</p>
<p>}</pre> </p>
<p> <p>通过上面的例子,我们分析了zskiplist的创建、插入、查找、删除、释放等操作,结合数据结构的定义,基本上分析清楚了zskiplist。其实zskiplist在Redis中的主要用处是和dict一起实现Sorted Set,这个我们后续看Sorted Set的时候再分析。</p> </p>
<p> <h1 id="四性能分析">四、性能分析</h1> </p>
<p> <table> </p>
<p> <thead> </p>
<p> <tr> </p>
<p> <th> 操作</th> </p>
<p> <th> 一般性能</th> </p>
<p> <th> 最坏性能</th> </p>
<p> </tr> </p>
<p> </thead> </p>
<p> <tbody> </p>
<p> <tr> </p>
<p> <td>插入</td> </p>
<p> <td>O(log n)</td> </p>
<p> <td>O(n)</td> </p>
<p> </tr> </p>
<p> <tr> </p>
<p> <td>删除</td> </p>
<p> <td>O(log n)</td> </p>
<p> <td>O(n)</td> </p>
<p> </tr> </p>
<p> <tr> </p>
<p> <td>搜索</td> </p>
<p> <td>O(log n)</td> </p>
<p> <td>O(n)</td> </p>
<p> </tr> </p>
<p> </tbody> </p>
<p> </table> </p>
<p> <p>如果想了解更多关于跳表本身,比如RandomLevel()的随机性等,一定不要错过 维基百科 上的内容。</p> </p>
<p></div></p></div>
发表评论
-
Oracle的where条件in/not in中包含NULL时的处理
2018-01-15 13:15 1614创建一个测试表t_inlinuxidc@TEST> ... -
在sae中设置django,让sae的工作环境跟本地python环境一致
2018-01-15 13:15 444sae中安装有python环境,想让sae导入自己下载的d ... -
win10下Django工程的创建
2018-01-11 13:39 382一、配置环境 win ... -
MySQL 5.7.18 zip文件安装教程
2018-01-11 13:47 535MySQL 5.7.18 zip 文件安装教程 安装 ... -
win10下Django工程的创建
2018-01-11 13:34 599一、配置环境 win10、python3.6、p ... -
MySql数据库逻辑架构讲解
2018-01-11 13:44 638与其他数据库相比,My ... -
Python使用虚拟环境
2018-01-11 13:44 451这里想象一下需 ... -
Kivy 中文教程 实例入门 简易画板 (Simple Paint App):1. 自定义窗口部件 (widget)
2018-01-05 10:01 9111. 框架代码 用 PyCharm 新建一个名为 S ... -
Python 零基础 快速入门 趣味教程 (咪博士 海龟绘图 turtle) 2. 变量
2018-01-05 10:01 1153大家在中学就已经学过变量的概念了。例如:我们令 x = 1 ... -
面向对象1
2018-01-05 09:49 468面向对象概念 面向对象是利用类和对象来创建各种模型对 ... -
Python列表及元组操作
2018-01-05 09:49 581#列表(一组有序 ... -
Python的hasattr() getattr() setattr() 函数使用方法详解
2018-01-05 09:57 732hasattr(object, name)判断一个对象里面 ... -
利用DBUTILS获得刚插入自增id记录的id信息的方法及代码
2018-01-02 12:36 2099我在做两个需要关联的表的时候,第二张表需要知道第一张表的i ... -
win10如何打开sqlserver配置管理器
2018-01-02 12:24 1081win10如何打开sqlserver配置管理器,windo ... -
数据库查询语言DQL使用介绍
2018-01-02 12:24 524(1)C++ppentry.C++om/list.php? ... -
Redis Basic在CentOS下安装指令
2018-01-02 12:23 518安装 C++entOS下安装指令: wge ... -
SpringBoot整合PageHelper实现数据库分页的代码教程
2018-01-02 12:23 778最近学习了SpringBoot 由于需要数据库分页功能 再 ... -
Day01_计算机硬件及启动流程
2017-12-26 17:23 568一.计算机硬件介绍 概念:由一条总线把CPU、内存和 ... -
python+Eclipse+pydev环境搭建
2017-12-26 17:12 422本文重点介绍使用Eclipse+pydev插件来写Pyth ... -
Python学习一:序列基础详解
2017-12-26 17:20 451作者:NiceCui 本文谢绝转载,如需转载需征得作 ...
相关推荐
根据提供的信息,我们可以深入探讨 Redis 源代码分析的相关知识点,特别是从给定的标题“Redis 源码分析 2. 分析起步”以及描述“Redis 的源码分析第二部分 By SinSay”中提取出的关键信息。 ### 1. Redis 源码概述...
Redis支持多种数据结构,如字符串(String)、哈希表(Hash)、列表(List)、集合(Set)和有序集合(Sorted Set)。这些数据结构在源码中由不同的C结构体实现,例如`sds`用于表示字符串,`dict`表示哈希表,`list`...
- 对于**有序集合类型**,可以使用`REDISENCODING_ZIPLIST`或`REDISENCODING_SKIPLIST`编码。 这些编码方式的选择取决于数据的大小、类型以及预期的操作类型,以达到最佳的性能和内存利用率。 通过以上内容我们...
Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。 附件里面包括redis源码,phpredis源码,redis指令及文档
redis源码阅读中文分析注释
数据结构丰富:Redis支持多种数据结构,包括字符串(string)、哈希(hash)、列表(list)、集合(set)、有序集合(sorted set)等。这些数据结构都支持丰富的操作,如push/pop、add/remove以及取交集、并集和差集...
"redis源码日志(源码分析)"是针对Redis源码进行深入剖析的学习资料,旨在帮助开发者理解如何通过源码实现Redis的高并发处理能力和海量数据存储功能。 在Redis中,高并发主要依赖于以下几点: 1. **单线程模型**...
在Windows环境下,Redis的源码分析和部署对于开发者来说具有重要意义,尤其是在Windows服务端开发中。以下是对"Redis Windows源码"的详细解析: 1. Redis核心架构: Redis基于单线程模型,通过事件驱动机制处理...
**一、Redis源码安装** 1. **下载源码** 首先,我们需要从Redis官方网站或者GitHub仓库下载源码。在这个例子中,我们使用的是`redis-2.8.9.tar.gz`。你可以通过命令行工具如`wget`或`curl`来下载,或者直接在网页...
墨墨导读:本文节选自《Redis 5设计与源码分析》,主要为读者分析Redis高性能内幕,重点从源码层次讲解了Redis事件模型,网络IO事件重在使用IO复用模型,时间事件重在限制最大执行CPU时间。最后简单介绍了Redis的...
例如,哈希表用于存储键值对,跳跃列表实现有序集合,链表处理列表数据结构等。 2. **事件驱动模型** Redis 使用单线程的事件驱动模型,通过 I/O 多路复用技术如epoll或kqueue来处理多个客户端连接。这种模型使得 ...
- **第9章:redis数据结构skiplist** - **概述**: 跳表的基本概念。 - **实现**: 描述跳表的插入、删除等操作细节。 - **场景**: Redis中跳表的应用实例。 - **第10章:redis数据结构intset** - **结构**: int...
spring和redis集成有很多方式,看到网上很多都是使用redistemplate自己去做redis 的一些操作,但是对于我们开发来说,肯定是使用越方便越好,于是乎就有了spring的对redis或者memcahe这些换成框架的封装,只需要引入...
2. **丰富的 API**:提供了大量的方法来操作 Redis 数据类型,如字符串(String)、哈希(Hash)、列表(List)、集合(Set)和有序集合(Sorted Set)。 3. **序列化支持**:ServiceStack.Redis 支持多种序列化方式...
在本文中,我们将深入探讨如何在Spring Boot应用中集成Redis,并理解其源码背后的机制。Spring Boot是一款由Pivotal团队开发的Java框架,旨在简化Spring应用的初始搭建以及开发过程。而Redis则是一款高性能的键值...
在本项目中,"ssm框架整合redis源码"意味着开发者将SSM框架与Redis缓存系统进行了集成,主要目的是实现session共享。Session共享是Web应用中解决用户会话跨服务器问题的关键技术,尤其是在分布式环境下,确保用户...
redis源码学习。redis作为重要的缓存中间件,在大型的网站架构中基本都有身影,想要详细的了解redis,从源码开始学习。
### Redis源码学习知识点解析 #### 一、Redis服务初始化与配置 Redis通过定义一个`struct redisServer`类型的全局变量`server`来保存服务器的相关信息,包括配置信息、统计信息、服务器状态等。启动时,根据配置...
Redis不仅可以作为数据库使用,还可以作为缓存、消息中间件等,支持数据的高速读写和实时分析。 在讨论windows环境下的Redis源码包之前,需要明确,由于Redis的开发主要集中在Linux环境下,其官方并不提供Windows...
跳跃表(Skiplist)是一种在Redis中广泛使用的数据结构,它作为一种概率性的平衡树替代方案,通过概率性平衡而非严格平衡,实现了更简单、更快的插入和删除算法。本篇将深入探讨跳跃表的基本概念、工作原理以及其在...