61. 跳表(Skip List)和 B+树(B+ Tree)的区别
跳表(Skip List)
一、是什么?
跳表是一种基于多层有序链表的随机化数据结构,通过建立多级索引加速查找。每个节点包含多个指向不同层级后继节点的指针,形成“跳跃”式查询路径。
二、解决什么问题
解决有序链表查询效率低(O(n))的问题,提供接近平衡树的查询性能(平均 O(log n)),同时实现更简单。
三、核心方法
search()
:从顶层索引开始逐层向下查找insert()
:插入节点并随机生成层数(抛硬币算法)delete()
:删除节点并更新索引指针
四、应用场景
- Java 的
ConcurrentSkipListMap
和ConcurrentSkipListSet
(JDK6+) - 内存数据库索引(如 Redis ZSET)
- 需要高并发读写的场景(跳表天然支持无锁并发)
五、Java 示例
java
ConcurrentSkipListMap<Integer, String> skipList = new ConcurrentSkipListMap<>();
skipList.put(3, "Apple");
skipList.put(1, "Banana");
skipList.put(2, "Cherry");
System.out.println(skipList.get(2)); // 输出 Cherry
System.out.println(skipList.ceilingKey(1)); // 输出 1(范围查询)
六、重要注意事项
- 空间开销较大(索引层占用额外空间)
- 性能依赖随机层数生成,最坏复杂度 O(n)
- JDK 的实现是线程安全的
B+树(B+ Tree)
一、是什么?
B+树是一种多路平衡查找树,所有数据存储在叶子节点,内部节点仅存键值(索引)。叶子节点通过指针形成有序链表。
二、解决什么问题
解决磁盘 I/O 效率问题:通过增加节点分支因子降低树高,减少磁盘访问次数(典型应用在数据库索引)。
三、核心特征
- 所有数据在叶子节点,内部节点仅存索引
- 叶子节点形成双向链表(高效范围查询)
- 节点填充因子通常 >50%(如 MySQL InnoDB 的 15/16)
四、应用场景
- 数据库索引(MySQL InnoDB、Oracle)
- 文件系统(NTFS、ReiserFS)
- 大数据存储(HBase、LevelDB)
五、重要注意事项
- 节点分裂/合并成本高
- 内存不足时性能急剧下降
- Java 标准库无内置实现,需第三方库(如 Apache Commons Collections)
关键区别(对比表)
特性 | 跳表 (Skip List) | B+树 (B+ Tree) |
---|---|---|
数据结构 | 多层链表 | 多叉平衡树 |
查询复杂度 | 平均 O(log n),最坏 O(n) | 稳定 O(log n) |
插入/删除成本 | 低(只需更新局部指针) | 高(可能触发节点分裂/合并) |
空间占用 | 高(额外索引层) | 低(填充因子高) |
范围查询效率 | 中等(需遍历链表) | 极高(叶子节点直接链表访问) |
磁盘友好性 | 差(内存数据结构) | 极优(节点大小=磁盘页) |
并发控制 | 天然支持无锁并发(如 CAS) | 需额外锁机制 |
Java 实现 | ConcurrentSkipListMap (JDK6+) | 无标准库实现 |
适用场景 | 内存数据库、高并发缓存 | 磁盘数据库、文件系统 |
总结
内存 vs 磁盘:
- 跳表适合内存操作(Java 并发集合)
- B+树专为磁盘存储优化(数据库索引)
复杂度稳定性:
- B+树提供稳定 O(log n) 操作
- 跳表最坏情况可能退化(但概率极低)
工程选择建议:
- 需要高并发内存索引 → 选跳表(
ConcurrentSkipListMap
) - 需持久化存储/范围扫描 → 选 B+树(如数据库场景)
- 需要高并发内存索引 → 选跳表(
💡 JDK 提示:Java 的跳表实现自 JDK6 引入,在并发场景下比
TreeMap
(红黑树)性能更优,尤其适合现代多核处理器环境。