Redis跳跃表

redis的5种数据类型:
○string(字符串):一个key对应一个value,可以包含任何数据,比如jpg或者序列化对象,一个key最大存储512MB的value。
命令: 在这里插入图片描述

•hash(哈希):是string类型的field和value的映射表,适合存储对象。
设置两个key-value
在这里插入图片描述
在这里插入图片描述

  每个hash可以存放2^32-1个key-value对。

○list(列表):Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。最多可存放2^32-1个元素。
在这里插入图片描述

•set(集合):
set是redis 的string类型的无序集合,集合中最大的成员数为 2^32 - 1。
在这里插入图片描述

•zset(sorted set:有序集合):按照score对元素排序。
在这里插入图片描述

–HyperLogLog:快速统计基数(不重复元素个数)
在这里插入图片描述

发布和订阅:开启两个客户端
在这里插入图片描述
在这里插入图片描述

3.redis事务
事务在提交之前,事务中的操作命令都被放进缓存队列中,且其他cli的命令请求不会被执行,在某个命令运行失败时,其他命令正常执行。
开始事务:MULTI
在这里插入图片描述

redis的主要功能
redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
a.redis -GEO

b.HyperLogLog

c.bitmap

d.Bloom Filter

e.发布订阅

4.redis的底层数据结构

a.跳跃表
i.什么是跳跃表
对一个单链表来说,即使链表中存储的数据是有序的,随机查找的时间复杂度也是O(n)
2.如果我们想要提高其查找效率, 可以考虑在链表上建索引的方式。每2个节点提取一个节点到上一级,我们把抽出来的那一级叫作索引。
在这里插入图片描述

这个时候,假设 我们要查找节点8 ,我们可以先在索引层遍历, 当遍历到索引层中7节点时,发现下一个节点是9, 那么要查询的节点8 一定在 7和9节点之间, 我们下降到 原始链表层,继续遍历就找到了8这个节点, 原来我们在单链表中要找到8这个节点要遍历8个节点,而现在有了一层索引后只需要遍历5个节点即可。
c.加了一层索引以后, 查找一个节点需要遍历的节点数少了, 也就是说查找效率提升了,同理可以再加一层索引。

像这种链表加多级索引的结构,就是跳跃表。
在这里插入图片描述

   header:指向跳跃表的表头节点,通过这个指针程序定位表头节点的时间复杂度就为O(1)

•tail:指向跳跃表的表尾节点,通过这个指针程序定位表尾节点的时间复杂度就为O(1)
•level:记录目前跳跃表内,层数最大的那个节点的层数(表头节点的层数不计算在内),通过这个属性可以再O(1)的时间复杂度内获取层高最好的节点的层数。
•length:记录跳跃表的长度,也即是,跳跃表目前包含节点的数量(表头节点不计算在内),通过这个属性,程序可以再O(1)的时间复杂度内返回跳跃表的长度。

import java.util.Random;

/***
 @ClassName: SkipNode
 @Author: zry
 @Date: 2022/6/21 17:29
 ***/
class SkipNode<T> {
    // 数据区
    public Integer score;
    public T value;

    // 当前节点的 上下左右层级节点
    public SkipNode up;
    public SkipNode down;
    public SkipNode left;
    public SkipNode right;

    public SkipNode(Integer score, T value) {
        this.score = score;
        this.value = value;
    }
}
public class SkipList {
    // 节点数量
    public int num;

    // 最高层数
    public int max_hight;

    public SkipNode head;
    public SkipNode tail;

    // 概率随机
    private Random random;

    public SkipList() {
        this.max_hight = 6; // 设置最高层数为6层
        this.num = 0; // 初始化时没有节点 这里设置 0
        this.head = new SkipNode(Integer.MIN_VALUE, null);
        this.tail = new SkipNode(Integer.MAX_VALUE, null);
        this.random = new Random();
        this.head.right = tail; // 将链表 头节点与尾节点关联
        this.tail.left = head;
    }

    /**
     * 基本操作   增加, 删除, 查找
     * 但是 这些操作都离不开 查询, 首先要定位到数据 然后再进行 其他操作
     */

    // 查找 , 根据指定的分数值查找数据
    private SkipNode find(Integer score) {
        SkipNode start = this.head;
        while (true) {
            // 如果当前 节点的右节点 score 比要查询的小则 往右移动, 直到遇见比它大的
            // 这里进来会一直往右去找
            while (start.right.score <= score) {
                start = start.right;
            }

            // 如果已经定位到 当前这一层 右节点比要查询的大了 , 那么将向下定位 ,这里判断下一层是否为null 不为null 则继续定位
            if (start.down != null) {
                start = start.down;
            } else {
                break;
            }
        }
        return start;  // 注意 这里 start.score 分值是小雨等于 传入的 score 值
        // 注意 这里 是小于等于 score值 ,右2种情况
        // 1 如果查找的分值 存在,则返回该对象的底层节点;
        // 2 如果查找的分值 不存在 ,则返回 最接近该对象的底层节点
    }

    // get操作 这里就可以获取 传入分数对应的节点, 如果完全匹配 那么返回该节点对应的value , 不匹配 则 return null;
    public Object get(Integer score) {
        if (num < 1) {
            return null;
        }
        SkipNode mark = find(score);
        if (mark.score.equals(score)) {
            return mark.value;
        } else {
            return null;
        }
    }

    public void put(Integer score, Object value) {
        // 寻找对应的 分值,如果不存在 则返回最近位置的分值 ,等于已经找到合适的插入位置
        SkipNode mark = find(score);
        // 如果找的节点分值 与要穿入的 相等 那么表示已存在,则进行更新
        if (mark.score.equals(score)) {
            mark.value = value;
            return;
        }
        // 否则 进行插入
        SkipNode brand_new = new SkipNode<>(score, value);
        // 要在 查询到的 mark 右边进行插入,
        // 即 新增的 节点 左边为 查询到的mark。  右边为 原来mark 的右边节点
        brand_new.left = mark;
        brand_new.right = mark.right;
        // 原来 mark 右边节点的左节点更新为 新创建的节点。  将mark 的右节点 更新为 新创建的节点
        mark.right.left = brand_new;
        mark.right = brand_new;

        // 使用变量标识 层级
        int i = 0;
        while (random.nextDouble() < 0.5) {
            if (i > max_hight) {
                break;
            }
            // 这里 mark 在查询到的时候 已经是最下层 包含所有节点的 那一层的数据了, 然后这里 获取它的上层,看是否存在更高层
            // 如果不存在,那么就切换到左边一个节点继续便利, 一直到获取到存在最高层的 节点
            while (mark.up == null) {
                mark = mark.left;
            }
            // 找到左侧存在的高层节点
            mark = mark.up;
            // 设置为 新创建的节点的上层节点的 左节点
            // 只有最底层的节点 保存 value ,其他节点只保存 分值即可
            SkipNode up_new = new SkipNode<>(score, null);
            up_new.left = mark;
            up_new.right = mark.right;
            mark.right.left = up_new;
            mark.right = up_new;

            up_new.down = brand_new;
            brand_new.up = up_new;

            // 将当前 全新引用 指向 新创建的 上层的 引用, 如果 while 继续循环,那么 mark 也 已经是 up, 上层的了
            brand_new = up_new;
            ++i;
        }
        ++num;
    }

    public boolean remove(Integer score) {
        SkipNode mark = find(score);
        // 如果 查询到的mark 的 score 与传入的 不同, 表示 根本不存在对应的分值对象, 只是返回了最接近的一个
        if (!mark.score.equals(score)) {
            return true;
        }

        // 查询到的mark 一定是 最底层的链表, 那么就是从最底层开始向上解除关联即 链表删除
        SkipNode del;
        while (mark != null) {
            del = mark.up;
            mark.left.right = mark.right;
            mark.right.left = mark.left;
            // 升一层 如果不为nul 继续处理
            mark = del;
        }
        return true;
    }
}```
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值