redis为什么使用跳跃表而不是树

Redis中支持五种数据类型中有序集合Sorted Set的底层数据结构使用的跳跃表,为何不使用其他的如平衡二叉树、b+树等数据结构呢?

1,redis的设计目标、性能需求:

redis是高性能的非关系型(NoSQL)内存键值数据库,它以其快速的操作速度而闻名。
读取速度:Redis能实现极高的读取速度,据官方测试报告,可以达到每秒约110,000次读取操作。
写入速度:与读取相比,写入速度略低,但仍然相当可观,官方数据显示,Redis的写入速度大约是每秒81,000次操作。

类似产品如Memcached等,无法达到如此性能。

2,有序集合都可以借助什么数据结构及其基本原理

有序集合需求:自然有序,查找高速,支持范围查找

2.1,传统数组/链表+排序

数组或链表可以存储数据,可以新增或修改数据后重新排序,

而在集合排序方面,最快的归并排序也需要O(NlongN)的时间复杂度。
时间不够快,但实现、使用方面简单
在这里插入图片描述

2.2,跳跃表(链表的优化–链表+多级索引)

跳跃表最早是由William Pugh在1990年提出的,被用来代替平衡树(如AVL树和红黑树)来实现有序集合。跳跃表的查询复杂度为O(log n),与平衡树相当,但由于其实现较为简单,所以在实际使用中比平衡树更加高效。

例:查找90
在这里插入图片描述

增加指针,让指针指向远处个节点,
如上图,共四层,最底层(原链表L1)节点是10 - 20 - 30 -… - 120,中间层L2增加节点10 - 30 - 40 - 60 - 80 - 100 - 120,L2上层L3增加节点10 - 40 - 80 - 120 最高层10 - 120

这样形成三个新的链表,但它包含的节点个数只有原来的一部分
当我们查找数据之时,先沿着这个最高层链表进行查找。当碰到比待查数据大的节点时,再到中间层,最后回到原来的链表中进行查找。

如查找90,共经历6步。

过程类似二分查找,时间复杂度支持平均O(logN),最坏O(N)的节点查找,还可以顺序性操作来批量处理节点。

2.3,平衡二叉树/红黑树

在这里插入图片描述
平衡二叉树的查询复杂度为O(logN),但新增、删除需要调整保持平衡,实现相对复杂;
范围查询方面,平衡二叉树支持范围查询,但是这这种数据结构要范围查找要往回找,即回溯到父结点,而B+树的叶子结点的指针的效率则更高

2.4,B+树

B+ Tree是一种经典的多路平衡查找树,它通常被用来实现磁盘上的数据结构。在B+ Tree中,每个节点可以包含多个键值对,而且所有叶子节点都被连接成一个链表。B+ Tree的查询复杂度也是O(log n),但由于其实现较为复杂,所以在实际使用中通常用于数据库系统等需要高效读写的场景中。
在这里插入图片描述

3,跳跃表与平衡树的实现

在这里插入图片描述

//redis源码中跳跃表结构的实现:
/* ZSETs use a specialized version of Skiplists */
typedef struct zskiplistNode {sds ele;double score;//分值struct zskiplistNode *backward;//后退指针//层struct zskiplistLevel {struct zskiplistNode *forward;//前进指针unsigned long span;//跨度} level[];
} zskiplistNode;

如图,一个跳表节点,有level数组,每个元素都有一个指向其他节点的指针,可以通过这些层来加快访问速度

3.1使用java实现跳跃表:

import java.util.Random;class Node {int key;int value;Node[] next;public Node(int level, int key, int value) {this.key = key;this.value = value;this.next = new Node[level + 1];}
}public class SkipList {private static final int MAX_LEVEL = 16; // 最大层数private int level; // 当前层数private Node head; // 头节点private Random random; // 用于生成随机层数public SkipList() {this.level = 0;this.head = new Node(MAX_LEVEL, 0, 0);this.random = new Random();}// 生成随机层数private int randomLevel() {int level = 0;while (level < MAX_LEVEL && random.nextDouble() < 0.5) {level++;}return level;}// 插入节点public void insert(int key, int value) {Node[] update = new Node[MAX_LEVEL + 1];Node current = head;for (int i = level; i >= 0; i--) {while (current.next[i] != null && current.next[i].key < key) {current = current.next[i];}update[i] = current;}int newLevel = randomLevel();if (newLevel > level) {for (int i = level + 1; i <= newLevel; i++) {update[i] = head;}level = newLevel;}Node newNode = new Node(newLevel, key, value);for (int i = 0; i <= newLevel; i++) {newNode.next[i] = update[i].next[i];update[i].next[i] = newNode;}}// 查找节点public Node search(int key) {Node current = head;for (int i = level; i >= 0; i--) {while (current.next[i] != null && current.next[i].key < key) {current = current.next[i];}}if (current.next[0] != null && current.next[0].key == key) {return current.next[0];}return null;}// 删除节点public void delete(int key) {Node[] update = new Node[MAX_LEVEL + 1];Node current = head;for (int i = level; i >= 0; i--) {while (current.next[i] != null && current.next[i].key < key) {current = current.next[i];}update[i] = current;}if (current.next[0] != null && current.next[0].key == key) {for (int i = 0; i <= level; i++) {if (update[i].next[i] != current.next[i]) {break;}update[i].next[i] = current.next[i];}while (level > 0 && head.next[level] == null) {level--;}}}// 打印跳跃表public void printSkipList() {for (int i = level; i >= 0; i--) {Node current = head;System.out.print("Level " + i + ": ");while (current.next[i] != null) {System.out.print(current.next[i].key + " ");current = current.next[i];}System.out.println();}}public static void main(String[] args) {SkipList skipList = new SkipList();skipList.insert(3, 30);skipList.insert(1, 10);skipList.insert(2, 20);skipList.insert(4, 40);System.out.println("Skip List after insertion:");skipList.printSkipList();int searchKey = 2;Node searchResult = skipList.search(searchKey);if (searchResult != null) {System.out.println("Node with key " + searchKey + " found. Value: " + searchResult.value);} else {System.out.println("Node with key " + searchKey + " not found.");}int deleteKey = 2;skipList.delete(deleteKey);System.out.println("Skip List after deletion of key " + deleteKey + ":");skipList.printSkipList();}
}

3.2使用java实现平衡二叉树(AVLTree):

class Node {int key, height;Node left, right;public Node(int key) {this.key = key;this.height = 1;}
}public class AVLTree {private Node root;// 获取节点的高度private int height(Node node) {return (node == null) ? 0 : node.height;}// 获取树的平衡因子private int getBalance(Node node) {return (node == null) ? 0 : height(node.left) - height(node.right);}// 更新节点的高度private void updateHeight(Node node) {node.height = 1 + Math.max(height(node.left), height(node.right));}// 执行右旋转private Node rightRotate(Node y) {Node x = y.left;Node T2 = x.right;// 执行旋转x.right = y;y.left = T2;// 更新高度updateHeight(y);updateHeight(x);return x;}// 执行左旋转private Node leftRotate(Node x) {Node y = x.right;Node T2 = y.left;// 执行旋转y.left = x;x.right = T2;// 更新高度updateHeight(x);updateHeight(y);return y;}// 插入节点public Node insert(Node node, int key) {if (node == null) {return new Node(key);}// 执行标准的BST插入if (key < node.key) {node.left = insert(node.left, key);} else if (key > node.key) {node.right = insert(node.right, key);} else {// 相等的键不允许插入return node;}// 更新节点的高度updateHeight(node);// 获取平衡因子int balance = getBalance(node);// 进行平衡操作// 左重,需要右旋转if (balance > 1 && key < node.left.key) {return rightRotate(node);}// 右重,需要左旋转if (balance < -1 && key > node.right.key) {return leftRotate(node);}// 左右,先左旋转后右旋转if (balance > 1 && key > node.left.key) {node.left = leftRotate(node.left);return rightRotate(node);}// 右左,先右旋转后左旋转if (balance < -1 && key < node.right.key) {node.right = rightRotate(node.right);return leftRotate(node);}// 不需要平衡操作,直接返回节点return node;}// 插入节点的公共接口public void insert(int key) {root = insert(root, key);}// 打印中序遍历结果private void inOrderTraversal(Node node) {if (node != null) {inOrderTraversal(node.left);System.out.print(node.key + " ");inOrderTraversal(node.right);}}// 打印中序遍历结果的公共接口public void inOrderTraversal() {inOrderTraversal(root);System.out.println();}public static void main(String[] args) {AVLTree avlTree = new AVLTree();// 插入节点avlTree.insert(10);avlTree.insert(20);avlTree.insert(30);avlTree.insert(15);avlTree.insert(5);// 打印中序遍历结果System.out.println("Inorder traversal of the AVL tree:");avlTree.inOrderTraversal();}
}

3.3java实现B+树:

import java.util.ArrayList;
import java.util.List;class BPlusTree {private BPlusNode root;private int order;public BPlusTree(int order) {this.root = new BPlusLeafNode();this.order = order;}public void insert(int key, String value) {root = root.insert(key, value);}public String search(int key) {return root.search(key);}public void printTree() {root.print();}// B+树节点抽象类abstract static class BPlusNode {List<Integer> keys;BPlusNode() {this.keys = new ArrayList<>();}abstract BPlusNode insert(int key, String value);abstract String search(int key);abstract void print();}// B+树叶子节点类static class BPlusLeafNode extends BPlusNode {List<String> values;BPlusLeafNode next; // 用于连接叶子节点形成链表BPlusLeafNode() {this.values = new ArrayList<>();this.next = null;}@OverrideBPlusNode insert(int key, String value) {int index = 0;while (index < keys.size() && keys.get(index) < key) {index++;}keys.add(index, key);values.add(index, value);// 检查是否需要分裂if (keys.size() > order) {int splitIndex = keys.size() / 2;BPlusLeafNode newLeaf = new BPlusLeafNode();// 将一半的键和值移至新节点newLeaf.keys.addAll(keys.subList(splitIndex, keys.size()));newLeaf.values.addAll(values.subList(splitIndex, values.size()));keys.subList(splitIndex, keys.size()).clear();values.subList(splitIndex, values.size()).clear();// 调整叶子节点链表newLeaf.next = next;next = newLeaf;return newLeaf;}return this;}@OverrideString search(int key) {int index = 0;while (index < keys.size() && keys.get(index) <= key) {if (keys.get(index) == key) {return values.get(index);}index++;}return null;}@Overridevoid print() {System.out.print("Leaf Node: ");for (int i = 0; i < keys.size(); i++) {System.out.print("(" + keys.get(i) + ", " + values.get(i) + ") ");}System.out.println();}}// B+树内部节点类static class BPlusInternalNode extends BPlusNode {List<BPlusNode> children;BPlusInternalNode() {this.children = new ArrayList<>();}@OverrideBPlusNode insert(int key, String value) {int index = 0;while (index < keys.size() && keys.get(index) < key) {index++;}BPlusNode child = children.get(index);BPlusNode newChild = child.insert(key, value);if (newChild != child) {keys.add(index, newChild.keys.get(0));children.add(index + 1, newChild);if (keys.size() > order) {int splitIndex = keys.size() / 2;BPlusInternalNode newInternal = new BPlusInternalNode();// 将一半的键和子节点移至新节点newInternal.keys.addAll(keys.subList(splitIndex, keys.size()));newInternal.children.addAll(children.subList(splitIndex + 1, children.size()));keys.subList(splitIndex, keys.size()).clear();children.subList(splitIndex + 1, children.size()).clear();return newInternal;}}return this;}@OverrideString search(int key) {int index = 0;while (index < keys.size() && keys.get(index) <= key) {index++;}return children.get(index).search(key);}@Overridevoid print() {System.out.print("Internal Node: ");for (int i = 0; i < keys.size(); i++) {System.out.print(keys.get(i) + " ");}System.out.println();for (BPlusNode child : children) {child.print();}}}public static void main(String[] args) {BPlusTree bPlusTree = new BPlusTree(3);bPlusTree.insert(10, "Value10");bPlusTree.insert(20, "Value20");bPlusTree.insert(5, "Value5");bPlusTree.insert(6, "Value6");bPlusTree.insert(12, "Value12");bPlusTree.insert(30, "Value30");System.out.println("B+ Tree after insertion:");bPlusTree.printTree();int searchKey = 12;String searchResult = bPlusTree.search(searchKey);if (searchResult != null) {System.out.println("Value for key " + searchKey + ": " + searchResult);} else {System.out.println("Key " + searchKey + " not found.");}}
}

4,Redis的作者 @antirez 的原话与总结

There are a few reasons:
1、They are not very memory intensive. It’s up to you basically. Changing parameters about the probability of a node to have a given number of levels will make then less memory intensive than btrees.
2、A sorted set is often target of many ZRANGE or ZREVRANGE operations, that is, traversing the skip list as a linked list. With this operation the cache locality of skip lists is at least as good as with other kind of balanced trees.
3、They are simpler to implement, debug, and so forth. For instance thanks to the skip list simplicity I received a patch (already in Redis master) with augmented skip lists implementing ZRANK in O(log(N)). It required little changes to the code.
主要是从内存占用、对范围查找的支持、实现难易程度这三方面总结的原因,
简单翻译一下:
1、它们不是非常内存密集型的。基本上由你决定。改变关于节点具有给定级别数的概率的参数将使其比 btree 占用更少的内存。
2、Zset 经常需要执行 ZRANGE 或 ZREVRANGE 的命令,即作为链表遍历跳表。通过此操作,跳表的缓存局部性至少与其他类型的平衡树一样好。
3、它们更易于实现、调试等。例如,由于跳表的简单性,我收到了一个补丁(已经在Redis master中),其中扩展了跳表,在 O(log(N) 中实现了 ZRANK。它只需要对代码进行少量修改。

跳跃表的优势:

1,时间复杂度方面:大部分情况下,跳跃表的效率和平衡树媲美;
2,算法实现方面:跳跃表的实现比平衡树、b+树更为简单;
3,范围查找方面,跳表比平衡树操作要简单,平衡树需要回溯到父结点,条表可以做到 O(logn) 的时间复杂度定位区间的起点,然后在原始链表中顺序往后遍历;
4,对于小数据集的性能: 在某些场景下,跳表在小数据集上的性能可能优于B+树。跳表的查询操作在某些情况下可能更迅速。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/686634.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【51单片机实验笔记】开关篇(二) 矩阵按键

目录 前言原理图分析矩阵按键扫描算法 软件实现1. 矩阵键盘检测2. 简易计算器实现 总结 前言 本节内容&#xff0c;我们学习一下矩阵按键&#xff0c;它是独立按键的阵列形式&#xff0c;常见的应用即键盘。 本节涉及到的封装源文件可在《模块功能封装汇总》中找到。 本节完…

websocket数据帧格式

客户端、服务端数据的交换&#xff0c;离不开数据帧格式的定义。因此&#xff0c;在实际讲解数据交换之前&#xff0c;我们先来看下WebSocket的数据帧格式。 WebSocket客户端、服务端通信的最小单位是帧&#xff08;frame&#xff09;&#xff0c;由1个或多个帧组成一条完整的消…

基于协同过滤的时尚穿搭推荐系统

项目&#xff1a;基于协同过滤的时尚穿搭推荐系统 摘 要 基于协同过滤的时尚穿搭推荐系统是一种能自动从网络上收集信息的工具&#xff0c;可根据用户的需求定向采集特定数据信息的工具&#xff0c;本项目通过研究服饰流行的分析和预测的分析和预测信息可视化时尚穿搭推荐系统…

C++中的volatile:穿越编译器的屏障

C中的volatile&#xff1a;穿越编译器的屏障 在C编程中&#xff0c;我们经常会遇到需要与硬件交互或多线程环境下访问共享数据的情况。为了确保程序的正确性和可预测性&#xff0c;C提供了关键字volatile来修饰变量。本文将深入解析C中的volatile关键字&#xff0c;介绍其作用、…

浅谈电商场景中的扣除库存问题

库存 一、场景二、扣减时机1.下单时扣库存2.支付完成扣库存3.预扣除 三、库存存储方案1.数据库存储2.数据库缓存混合存储 四、整体方案1.单数据库方案2.主从数据库方案3.主从数据库缓存方案4.数据库缓存混合存储 五、其他情况1.秒杀QPS过高2.Redis QPS过高3.Master DB QPS过高4…

使用ShardingJDBC实现分库分表

一、测试环境 JDK&#xff1a;1.8SpringBoot&#xff1a;2.7.17MySQL驱动&#xff1a;5.1.49MyBatis&#xff1a;2.3.1shardingJDBC&#xff1a;5.1.0 二、核心依赖 <!-- mysql 驱动 --> <dependency><groupId>mysql</groupId><artifactId>mysq…

Manifest merger failed with multiple errors, see logs

问题 Manifest merger failed with multiple errors, see logs详细问题 笔者进行Android 项目开发&#xff0c;修改AndroidManifest.xml代码后&#xff0c;控制台报错 AndroidManifest.xml报错核心代码 <manifest><uses-permission android:name"android.perm…

【C语言】长篇详解,字符系列篇1-----“混杂”的各种字符类型字符转换和strlen的模拟实现【图文详解】

欢迎来CILMY23的博客喔&#xff0c;本期系列为【C语言】长篇详解&#xff0c;字符系列篇1-----“混杂”的各种字符函数……&#xff0c;图文讲解各种字符函数&#xff0c;带大家更深刻理解C语言中各种字符函数的应用&#xff0c;感谢观看&#xff0c;支持的可以给个赞哇。 前言…

内存块与内存池

&#xff08;1&#xff09;在运行过程中&#xff0c;MemoryPool内存池可能会有多个用来满足内存申请请求的内存块&#xff0c;这些内存块是从进程堆中开辟的一个较大的连续内存区域&#xff0c;它由一个MemoryBlock结构体和多个可供分配的内存单元组成&#xff0c;所有内存块组…

Java学习笔记------static

static 创建Javabean类 public class student {private int age;private String name;private String gender;public student() {}public student(int age, String name, String gender) {this.age age;this.name name;this.gender gender;}/*** 获取* return age*/public…

使用Python编写脚本-根据端口号杀掉进程

我的GitHub&#xff1a;Powerveil - GitHub 我的Gitee&#xff1a;Powercs12 - Gitee 皮卡丘每天学Java 从前段开始遇到一个问题&#xff0c;服务在启动的时候总是端口被占用&#xff0c;发现还是Java程序&#xff0c;但是当时并没有启动Java程序&#xff0c;电脑出问题了。 一…

【Linux】Framebuffer 应用

# 前置知识 LCD 操作原理 在 Linux 系统中通过 Framebuffer 驱动程序来控制 LCD。 Frame 是帧的意思&#xff0c; buffer 是缓冲的意思&#xff0c;这意味着 Framebuffer 就是一块内存&#xff0c;里面保存着一帧图像。 Framebuffer 中保存着一帧图像的每一个像素颜色值&…

Tomcat要点总结

一、Tomcat 服务中部署 WEB 应用 1.什么是Web应用 &#xff08;1&#xff09; WEB 应用是多个 web 资源的集合。简单的说&#xff0c;可以把 web 应用理解为硬盘上的一个目录&#xff0c; 这个目录用于管理多个 web 资源。 &#xff08;2&#xff09;Web 应用通常也称之为…

七、ActiveMQ的传输协议

ActiveMQ的传输协议 一、是什么二、协议1.TCP(默认)2.NIO3.AMQP4.STOMP5.SSL6.MQTT7 WS 三、NIO配置案例1.修改activemq.xml2.重启3.生产者/消费者4.性能提升4.1 配置4.2 生产者/消费者 一、是什么 官网地址&#xff1a;http://activemq.apache.org/configuring-version-5-tra…

Mysql知识点汇总

Mysql知识点汇总 1. Mysql基本场景的简单语句。2. Mysql的增删改查&#xff0c;统计表中的成绩最好的两个同学的名字&#xff0c;年级等。3&#xff1a;请使用多种方法查询每个学生的每门课分数>80的学生姓名4、order by&#xff0c;group by&#xff0c;子查询4.1、having和…

Apache Httpd 常见漏洞解析(全)

一、Apache HTTPD 换行解析漏洞 漏洞编号&#xff1a;CVE-2017-15715 Apache HTTPD是一款HTTP服务器&#xff0c;它可以通过mod_php来运行PHP网页。 其2.4.0~2.4.29版本中存在一个解析漏洞。 在解析PHP时&#xff0c;1.php\x0A将被按照PHP后缀进行解析&#xff0c;导致绕过…

XSS数据接收平台

一.使用xss数据接收平台的好处&#xff1a; 正常执行反射型xss和存储型xss&#xff0c;反射型xss在执行poc时&#xff0c;会直接在页面弹出执行注入的poc代码&#xff1b;存储型则是&#xff0c;在将poc代码注入用户的系统中后&#xff0c;用户访问有存储型xss的地方&#xff…

SpringCloud-Nacos集群搭建

本文详细介绍了如何在SpringCloud环境中搭建Nacos集群&#xff0c;为读者提供了一份清晰而详尽的指南。通过逐步演示每个关键步骤&#xff0c;包括安装、配置以及Nginx的负载均衡设置&#xff0c;读者能够轻松理解并操作整个搭建过程。 一、Nacos集群示意图 Nacos&#xff0…

机器学习西瓜书之决策树

目录 算法原理剪枝处理连续值处理缺失值处理多变量决策树 算法原理 从逻辑角度&#xff1a;通过一系列if-else语句进行多重判断&#xff0c;比如白富美的判断条件&#xff08;“白”“富”“美”&#xff09;。 从几何角度&#xff1a;根据定义的标准进行样本空间的划分。 以二…

MySQL数据库基础(六):DDL数据库操作

文章目录 DDL数据库操作 一、MySQL的组成结构 二、数据库的基本操作 1、创建数据库 2、查询数据库 3、删除数据库 4、选择数据库 三、总结 DDL数据库操作 一、MySQL的组成结构 注&#xff1a;我们平常说的MySQL&#xff0c;其实主要指的是MySQL数据库管理软件。 一个M…