zoukankan      html  css  js  c++  java
  • 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;
    
  • 相关阅读:
    【Services】【Web】【tomcat】配置tomcat支持https传输
    【Services】【Web】【apr】安装apr
    【Services】【Web】【Nginx】静态下载页面的安装与配置
    【Linux】【Problems】在fedora 9上解决依赖问题
    【Java】【设计模式】单例设计模式
    【Linux】【Shell】【text】awk
    【Linux】【Shell】【Basic】字符串操作
    【Linux】【Shell】【Basic】数组
    Linux上常用插件的一些命令(十)
    常见HTTP状态码
  • 原文地址:https://www.cnblogs.com/yinyunmoyi/p/11521860.html
Copyright © 2011-2022 走看看