在高性能后端开发和分布式存储中跳表Skip List和B 树是高频出现的两个核心数据结构。Redis 的 ZSet、Java 的ConcurrentSkipListMap选择了跳表而 MySQL 的 InnoDB 存储引擎则选择了 B 树。本文将为你彻底拆解跳表的底层逻辑、核心机制并深度对比它与 B 树的异同。一、 什么是跳表Skip List跳表是一种可以用来替代平衡树如红黑树、AVL树的概率型数据结构。它在有序链表的基础上增加了多级索引通过“空间换时间”的策略实现了高效的查找、插入和删除操作其平均时间复杂度均为O(logn)O(\log n)O(logn)。核心结构特点基础层Level 0最底层的单链表包含所有的元素并且这些元素是严格递增排序的。索引层Level 1 ~ Level N上层的链表是下层链表的“导流索引”。每一层的节点都是从下一层中按一定概率ppp通常为1/21/21/2或1/41/41/4随机抽取出来的。概率平衡跳表不需要像平衡树那样在插入时进行复杂的旋转或重平衡而是为每个新插入的节点随机生成一个高度层数。这种概率上的平衡同样能保证整体操作的高效性。二、 核心精髓多级索引Multi-level Index单链表最大的痛点在于无法进行二分查找只能从头到尾一个个往后拉时间复杂度O(n)O(n)O(n)。为了让链表也能飞起来跳表引入了多级索引。1. 结构具象化多级索引的核心思想是“给索引再做索引通过层层提炼、减少搜索范围实现大数据的快速定位”。PlaintextLevel 2 (高跨度索引) : [1] -------------------------- [5] -------------------------- [9] | | | Level 1 (中跨度索引) : [1] ----------- [3] ----------- [5] ----------- [7] ----------- [9] | | | | | Level 0 (基础数据层) : [1] - [2] - [3] - [4] - [5] - [6] - [7] - [8] - [9]2. 查找过程示例假设我们要查找节点7从顶层Level 2出发看到1向右看是5。因为7 5继续向右看是9。由于7 9说明目标值必然在5到9之间。下沉到 Level 1从刚才锁定的5开始往右看下一个节点直接就是7。目标命中直接下沉到 Level 0 即可获取真实数据。生活映射这就像我们查字典。先根据声母第一级索引找到T再根据音节第二级索引找到tiao最后翻到具体页码基础层顺序找到“跳”字。多级索引将长距离的查找切分成**“大步跳跃→\rightarrow→小步微调”**的过程。三、 为什么跳表能完美支持范围查询跳表能够高效支持范围查询Range Query如查找区间[low,high][low, high][low,high]内的所有元素主要得益于它的双重特性上层的快速定位能力底层的顺序遍历能力。第一步快速定位起点利用多级索引从顶层向下、向右查找如同二分查找一般快速跳过无关元素在O(logn)O(\log n)O(logn)的时间内定位到范围的左边界第一个≥low\ge low≥low的节点。第二步底层顺序横扫定位到起点后直接下沉到最底层的Level 0。由于 Level 0 是一个完整的、紧凑的有序单向或双向链表接下来只需沿着底层链表一路向右顺序指针遍历直到遇到第一个high highhigh的节点为止。复杂度分析总时间复杂度为O(lognk)O(\log n k)O(lognk)其中O(logn)O(\log n)O(logn)为定位起点的时间kkk为区间内元素的数量。四、 终极对决跳表 VS B 树跳表和 B 树都能完美支持范围查询但它们的底层设计哲学和应用场景截然不同。1. 存储介质与内存布局核心区别B 树专为磁盘外存设计。它的分支因子非常大通常上百树的高度极低一般 3~4 层每个节点对应一个固定大小的磁盘页Page。这样可以最大限度地减少磁盘 I/O 次数。跳表专为纯内存设计。跳表充斥着大量的指针在内存中离散分布。如果放到磁盘上指针跳转会导致极其致命的随机 I/O。但在纯内存环境下指针跳转的代价微乎其微。2. 并发锁粒度为什么高并发多线程喜欢跳表B 树在多线程高并发插入时如果引发节点的分裂或合并可能会触发级联反应导致从叶子节点一直向上锁到根节点锁升级并发性能受限。跳表插入和删除操作极其局部化。由于节点的层数是随机决定的插入一个节点只需要修改它前后相邻节点的指针不需要做全局平衡调整。因此跳表可以非常容易地使用CASCompare And Swap保证线程安全实现无锁或细粒度锁的并发结构如 Java 的ConcurrentSkipListMap。3. 特性对比一览表对比维度跳表 (Skip List)B 树 (B Tree)主要存储介质纯内存 (In-Memory)磁盘 / 外存 (Disk-Based)平衡机制概率型平衡依靠随机数无锁化友好确定型平衡节点分裂/合并易触发级联锁平均时间复杂度O(logn)O(\log n)O(logn)O(logn)O(\log n)O(logn)(由于分支大常数项更小)空间开销较大每个节点需要维护多个前向指针较小紧凑的页结构指针占比低并发性能极高局部指针修改适合 CAS 无锁化一般树平衡时需要锁大范围节点缓存友好度一般指针悬空容易 CPU Cache Miss极高页内数据连续存储充分利用预读机制实现复杂度简单代码优雅指针操作易于维护极高分裂、合并、红黑平衡逻辑复杂典型应用经典Redis (ZSet)、Lucene、Java 并发包MySQL (InnoDB)、文件系统 (XFS, NTFS)五、 总结如果你的场景是大数据量、强依赖磁盘 I/O、需要极致压榨单次查询性能如数据库引擎B 树是无可替代的选择。如果你的场景是纯内存操作、面临超高并发的读写交织、且希望代码易于实现和扩展如缓存中间件、并发工具包那么跳表凭借其随性的概率平衡和极其优秀的无锁化潜力则是绝对的明星选手。
【数据结构】核心数据结构解析:跳表(Skip List)从底层原理到经典对比
发布时间:2026/6/25 17:49:26
在高性能后端开发和分布式存储中跳表Skip List和B 树是高频出现的两个核心数据结构。Redis 的 ZSet、Java 的ConcurrentSkipListMap选择了跳表而 MySQL 的 InnoDB 存储引擎则选择了 B 树。本文将为你彻底拆解跳表的底层逻辑、核心机制并深度对比它与 B 树的异同。一、 什么是跳表Skip List跳表是一种可以用来替代平衡树如红黑树、AVL树的概率型数据结构。它在有序链表的基础上增加了多级索引通过“空间换时间”的策略实现了高效的查找、插入和删除操作其平均时间复杂度均为O(logn)O(\log n)O(logn)。核心结构特点基础层Level 0最底层的单链表包含所有的元素并且这些元素是严格递增排序的。索引层Level 1 ~ Level N上层的链表是下层链表的“导流索引”。每一层的节点都是从下一层中按一定概率ppp通常为1/21/21/2或1/41/41/4随机抽取出来的。概率平衡跳表不需要像平衡树那样在插入时进行复杂的旋转或重平衡而是为每个新插入的节点随机生成一个高度层数。这种概率上的平衡同样能保证整体操作的高效性。二、 核心精髓多级索引Multi-level Index单链表最大的痛点在于无法进行二分查找只能从头到尾一个个往后拉时间复杂度O(n)O(n)O(n)。为了让链表也能飞起来跳表引入了多级索引。1. 结构具象化多级索引的核心思想是“给索引再做索引通过层层提炼、减少搜索范围实现大数据的快速定位”。PlaintextLevel 2 (高跨度索引) : [1] -------------------------- [5] -------------------------- [9] | | | Level 1 (中跨度索引) : [1] ----------- [3] ----------- [5] ----------- [7] ----------- [9] | | | | | Level 0 (基础数据层) : [1] - [2] - [3] - [4] - [5] - [6] - [7] - [8] - [9]2. 查找过程示例假设我们要查找节点7从顶层Level 2出发看到1向右看是5。因为7 5继续向右看是9。由于7 9说明目标值必然在5到9之间。下沉到 Level 1从刚才锁定的5开始往右看下一个节点直接就是7。目标命中直接下沉到 Level 0 即可获取真实数据。生活映射这就像我们查字典。先根据声母第一级索引找到T再根据音节第二级索引找到tiao最后翻到具体页码基础层顺序找到“跳”字。多级索引将长距离的查找切分成**“大步跳跃→\rightarrow→小步微调”**的过程。三、 为什么跳表能完美支持范围查询跳表能够高效支持范围查询Range Query如查找区间[low,high][low, high][low,high]内的所有元素主要得益于它的双重特性上层的快速定位能力底层的顺序遍历能力。第一步快速定位起点利用多级索引从顶层向下、向右查找如同二分查找一般快速跳过无关元素在O(logn)O(\log n)O(logn)的时间内定位到范围的左边界第一个≥low\ge low≥low的节点。第二步底层顺序横扫定位到起点后直接下沉到最底层的Level 0。由于 Level 0 是一个完整的、紧凑的有序单向或双向链表接下来只需沿着底层链表一路向右顺序指针遍历直到遇到第一个high highhigh的节点为止。复杂度分析总时间复杂度为O(lognk)O(\log n k)O(lognk)其中O(logn)O(\log n)O(logn)为定位起点的时间kkk为区间内元素的数量。四、 终极对决跳表 VS B 树跳表和 B 树都能完美支持范围查询但它们的底层设计哲学和应用场景截然不同。1. 存储介质与内存布局核心区别B 树专为磁盘外存设计。它的分支因子非常大通常上百树的高度极低一般 3~4 层每个节点对应一个固定大小的磁盘页Page。这样可以最大限度地减少磁盘 I/O 次数。跳表专为纯内存设计。跳表充斥着大量的指针在内存中离散分布。如果放到磁盘上指针跳转会导致极其致命的随机 I/O。但在纯内存环境下指针跳转的代价微乎其微。2. 并发锁粒度为什么高并发多线程喜欢跳表B 树在多线程高并发插入时如果引发节点的分裂或合并可能会触发级联反应导致从叶子节点一直向上锁到根节点锁升级并发性能受限。跳表插入和删除操作极其局部化。由于节点的层数是随机决定的插入一个节点只需要修改它前后相邻节点的指针不需要做全局平衡调整。因此跳表可以非常容易地使用CASCompare And Swap保证线程安全实现无锁或细粒度锁的并发结构如 Java 的ConcurrentSkipListMap。3. 特性对比一览表对比维度跳表 (Skip List)B 树 (B Tree)主要存储介质纯内存 (In-Memory)磁盘 / 外存 (Disk-Based)平衡机制概率型平衡依靠随机数无锁化友好确定型平衡节点分裂/合并易触发级联锁平均时间复杂度O(logn)O(\log n)O(logn)O(logn)O(\log n)O(logn)(由于分支大常数项更小)空间开销较大每个节点需要维护多个前向指针较小紧凑的页结构指针占比低并发性能极高局部指针修改适合 CAS 无锁化一般树平衡时需要锁大范围节点缓存友好度一般指针悬空容易 CPU Cache Miss极高页内数据连续存储充分利用预读机制实现复杂度简单代码优雅指针操作易于维护极高分裂、合并、红黑平衡逻辑复杂典型应用经典Redis (ZSet)、Lucene、Java 并发包MySQL (InnoDB)、文件系统 (XFS, NTFS)五、 总结如果你的场景是大数据量、强依赖磁盘 I/O、需要极致压榨单次查询性能如数据库引擎B 树是无可替代的选择。如果你的场景是纯内存操作、面临超高并发的读写交织、且希望代码易于实现和扩展如缓存中间件、并发工具包那么跳表凭借其随性的概率平衡和极其优秀的无锁化潜力则是绝对的明星选手。