1.构建双向链表基架
redis中双向链表的节点都是由如下3个元素构成:
- 指向前驱节点的指针prev。
- 指向后继节点的指针next。
- 指向当前节点值的指针value。
所以笔者对于双向链表节点的结构体的定义也按照这套定义复刻:
因为是双向链表,这意味着链表可以从前或者从后进行链表操作,所以双向链表就必须具备如下3个构成部分:
- 指向链表第一个节点的head指针。
- 指向链表最后一个节点的tail指针。
- 维护链表长度的字段len。
于是我们基于这个思路,再次给出链表的结构体定义:
了解了基础的结构定义,我们就可以编写双向链表初始化的函数listCreate,和redis初始化步骤基本一致,笔者同样是按照:结构体内存空间分配、头尾指针初始化、长度设置为0,然后返回这个双向链表结构体指针的步骤进行操作:
实现节点头插和尾后追加
此时,我们就可以实现mini-redis中双向链表的第一个操作——头插法,该操作就是将新插入的节点作为链表的头节点,该操作的步骤比较明确:
- 新节点指向原有头节点。
- 原有头节点的前驱指针指向新节点。
- 将head指针指向新节点,完成节点头插。
完成这些操作之后,维护一下链表长度信息:
基于上述思路笔者给出对应的实现,和原生redis的函数和入参基本一致,传入需要操作的链表和value值之后,将value封装为节点,结合上述的思路将其设置为链表头节点:
与之同理的还有尾插法,无论入参和操作步骤基本一致,唯一区别就是将节点追加到链表末端作为尾节点,读者可以参考笔者的的实现和注释了解操作细节:
基于索引定位节点
双向链表支持基于索引的方式查询,例如我们希望查询索引2节点的值,传入index为2,双向链表就会基于索引2这个值跳越两次来到目标节点并返回:
假如我们传入负数,例如负数2,按照语义就是返回倒数第2个节点,双向链表会按照公式(-index)-1得到值1,然后从尾节点跳1步找到目标节点并返回:
对此我们给出相应的源码实现,整体思路和上述说明一致,读者可参考源码和注释了解细节:
指定位置插入
双向链表支持在指定元素的前面或者后面插入元素,我们以元素后插入为例,双向链表会将新节点追加到原有节点后面并维护前驱后继指针的信息,插入到指定节点的前方也是同理:
唯一需要注意的就是如果新节点追加到尾节点后面,我们需要将tail指向新节点。追加到头节点同理,我们需要将head指针指向新节点:
对此我们给出listInsertNode的源码实现,读者可参阅思路并结合注释了解实现细节:
双向链表节点删除
节点删除则比较简单,传入要被删除的节点指针,让被删除节点d的前驱节点指向d的后继节点,同时让d的后继指向d的前驱:
唯一需要注意的就是以下两种情况:
- 删除的是头节点,则让head指向头节点的后面一个节点。
- 删除的是尾节点,则让tail指向尾节点的前一个节点。
最后我们断掉被删除节点的前后继指针指向,让go语言垃圾回收自动帮我们完成节点删除即可,这里我们也给出相应的源码实现: