Redis06:底层:跳跃链表skiplist

底层:跳跃链表skiplist

跳表

每个node的结构:含键和值,还有一个由多个node组成的list,就是本node在不同层指向下一个node的指针。

每次初始化时初始化最初的节点,也就是最左边的节点,它的高度就是跳表最大高度,初始每个位置指针指向null。

插入元素时首先判断该元素在集合中是否存在,从最初节点的最高位置开始寻找,向前的指针键值比要找的值大就向下移动,小就向前移动。如果不存在就插入,首先用随机函数每次随机一个数0或1,出现一次1就停止,出现0的个数就是层数。然后从初始节点的对应层开始,如果前一个节点值大,就生成一个node,令初始节点层指针指向node,node向前指向本来前面的值,如果前一个节点值小就向下移动然后继续判断,直到走完第一层,对应节点的所有向前指针都已经创建完毕了。

删除时只需要找到该节点该层谁指向它,把指针指向node前面的node即可。

java实现的跳表

跳表的node:

class SkipListNode{
		public Integer value;
    	//指向下一个node,如果本node是5层,那么这个list大小为5,分别指向1-5层的下一个node
		public ArrayList<SkipListNode> nextNodes;
		public SkipListNode(Integer value) {
			this.value = value;
			nextNodes = new ArrayList<MySkipList.SkipListNode>();
		}
}

跳表list的字段和构造方法:

class SkipList{
		//最小值,跳表的起始节点,它的高度和跳表中node的最大高度一致
		private SkipListNode head;
		
		//跳表达到的最大高度
		private int maxLevel;
		
		//跳表中元素的个数
		private int size;
		
		//跳表决定高度的概率,以0.5的概率产生0,如果是1就晋升一层
		private static final double PROBABILITY = 0.5;
		
		public SkipList() {
			size = 0;
			maxLevel = 0;
			head = new SkipListNode(null);
			head.nextNodes.add(null);
		}
}

跳表的add方法:

//添加元素
public void add(Integer newValue) {

    //如果不包含这个元素,就插入跳表
    if(!contains(newValue)) {
        //随机确定新的层高
        size++;
        int level = 0;
        while(Math.random() < PROBABILITY) {
            level++;
        }
        while(level > maxLevel) {
            head.nextNodes.add(null);
            maxLevel++;
        }
        //建立node,确定head为查找时的起始node
        SkipListNode newNode = new SkipListNode(newValue);
        SkipListNode current = head;
        //遍历的时候每次要么向前走,要么向下走,按照层数遍历
        //每一层都要建立新node,然后连接前后指针
        do {
            //获得这一层里最后一个大于newValue的node
            current = findNext(newValue, current, level);

            //生成newNode其中一个向前的指针(指向更大的node),每次都从0添加
            //这样最后如果一共有5层,相当于先添加第五层然后第四层。。
            //每个指针指向的就是current的前一个节点
            //连接从node到前
            newNode.nextNodes.add(0, current.nextNodes.get(level));

            //使newNode和它后面的节点产生联系,将后面节点的向前指针连接到newNode
            //连接从node到后(指向更小的node)
            current.nextNodes.set(level, newNode);
        }while(level-- > 0);
    }
}

查找方法findNext:

//找到要找的目标值e在这一层level里刚刚大于e的node,current是寻找的起始位置
private SkipListNode findNext(Integer e, SkipListNode current, int level) {
    //找到current节点在当前层的前一个节点
    SkipListNode next = current.nextNodes.get(level);
    while(next != null) {
        Integer value = next.value;
        //如果本节点是8,前一个节点是10,要找的值是9就会出现这种情况
        //此时直接返回,进入下一层
        if(e < value) {
            break;
        }
        //如果本节点是8,前一个节点是10,要找的值是12就会继续向前找
        //更新current和next

        //每次如果前一个节点比要找的值小就前进,否则找下一层
        current = next;
        next = current.nextNodes.get(level);
    }
    return current;
}

contains方法:

public boolean contains(Integer value) {
    SkipListNode node = find(value);
    return node != null && node.value != null && node.value == value;
}

//找到e对应的节点
private SkipListNode find(Integer e) {
    return find(e, head, maxLevel);
}

delete方法:

public void delete(Integer deleteValue) {
    if(contains(deleteValue)) {
        SkipListNode deleteNode = find(deleteValue);
        size--;
        int level = maxLevel;
        SkipListNode current = head;
        do {
            //找到刚刚比deleteNode稍大一点的node,然后将该node的各向前指针修正为
            //原来deleteNode向前指针的指向,相当于删除了deleteNode
            current = findNext(deleteNode.value, current, level);
            if(deleteNode.nextNodes.size() > level) {
                current.nextNodes.set(level, deleteNode.nextNodes.get(level));
            }
        }while(level-- > 0);
    }
}

跳表的迭代器(遍历结果一定是从小到大的):

class SkipListIterator implements Iterator<Integer>{

		SkipList list;
		SkipListNode current;
		
		public SkipListIterator(SkipList list) {
			this.list = list;
			this.current = list.getHead();
		}
		
		@Override
		public boolean hasNext() {
			// TODO Auto-generated method stub
			return current.nextNodes.get(0) != null;
		}

		@Override
		public Integer next() {
			// TODO Auto-generated method stub
			current = current.nextNodes.get(0);
			return current.value;
		}
		
}

redis中的跳表

在组织score时redis采用跳表,而从member到score的映射redis使用字典来存储。

在redis中每一层的晋升概率是25%,它是一种更扁平化的跳表,在单个层上遍历的节点个数就会稍多一些。

在调整元素权重时,redis采用对该节点先删后加的方式来进行。

如果权重都相同redis还会比较value值,使redis中的跳表有序。

redis计算元素排名rank时,是对跳表功能的一种加强,对于每个元素都有它的字段rank值,跳表在变化时会更新这个值。

在redis中跳跃表是有序集合sorted set的底层实现之一。跳跃表通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。支持平均OlogN、最坏ON复杂度的节点查找,还可以通过顺序性操作来批量处理节点。

redis的链表由两种结构组成,一个是结点zskiplistNode,一个是表zskiplist。下图为一个跳表例子:

最左边就是一个zskiplist,它包括header(指向跳跃表的表头节点)、tail(指向跳跃表的表尾节点)、level(当前跳跃表的最大层数)、length(跳跃表的长度,也就是数据节点的数量)。

右侧是4个zskiplistNode,它有几个属性:

1、层level,也就是L1/L2/../L4等,每个层都有两个属性,分别是指针(指向表尾方向的其他节点)、跨度(记录跨度方向两点的距离),当从表头向表尾遍历跳表时,就会借助前进指针。

2、后退指针BW,它用来指向当前节点的前一个节点,在从表尾向表头遍历跳表时,就会借助后退指针。

3、分值:每个节点中的1.0、2.0、3.0。节点按照分值从小到大排列。

4、成员对象obj:o1、o2和o3,它是节点保存的值。

zskiplistNode的定义如下:

typedef struct zskiplistNode{
	//层
	struct zskiplistLevel{
        //前进指针
		struct zskiplistNode *forward;
        //跨度
		unsigned int span;
	} level[];
    //后退指针
	struct zskiplistNode *backward;
    //分值
	double score;
    //成员对象
	robj *obj;
} zskiplistNode;

每次生成一个新跳跃表节点的时候,程序都会自动生成一个介于1到32之间的值作为level数组的大小,这个大小就是层的高度,每一层的晋升概率是25%,这是一个更偏于扁平的跳表。

span跨度这个字段是为了计算某个元素的排位rank使用的,在查找某个节点的过程中,将沿途访问的所有层的跨度都累加起来,得到的结果就是目标节点在跳跃表中的排位。

obj是一个指向SDS的指针,在同一个跳表中,各节点保存的对象obj必须是唯一的,而分值score可以是相同的,分值相同的节点会按照对象obj的字典序大小来进行排序。

zskiplist的定义如下:

typedef struct zskiplist{
    //表头节点和表尾节点
	structz zskiplistNode *header, *tail;
    //表中节点的数量
	unsigned long length;
    //表中层数最大的节点的层数
	int level;
} zskiplist;
posted @ 2019-09-15 13:03  勇闯8  阅读(419)  评论(0编辑  收藏  举报