Redis 跳表(skip list)MySQL Btree 与 Java Map 红黑树

跳表(skip list)

跳表的特点:

  • 基于单向链表和多级索引结构实现了等同于“二分查找”时间复杂度的查询性能

跳表时间和空间复杂度:

  • 查询数据的时间复杂度是 O(logN)
  • 插入操作的时间复杂度是 O(logN)
  • 删除操作的时间复杂度是 O(logN)
  • 空间复杂度是 O(N)

跳表时间复杂度分析:

  • 设原始链表有 N 个节点,每两个节点抽取一个节点作为上一级索引节点,这样第 k 层索引有 N/(2^k)个节点
  • 设共有 h 级索引,最高一级索引有 2 个节点,结合上面的分析知道 N/2^h = 2,所以 h + 1 = log2(N)
  • 加上原始链表这一层,整个跳表的高度是 log2(N)
  • 查找某个数据的时候,如果每层对比 m 个节点,那么总的时间复杂度是 log2(m*N)(m 是个常数),最终的时间复杂度 O(logN)

跳表索引动态更新:

  • 往跳表中插入数据时,选择性的将这个数据同步插入部分索引层中,由一个随机函数来确定需要插入哪些索引层级,这样在两个索引节点间插入大量数据后可以避免跳表查询性能的退化

Redis 有序集合(Sorted Set)

Reids 有序集合支持的核心操作有:插入数据、查找数据、删除数据、根据 score 按照区间查找数据

Redis 有序集合的底层编码有两种实现,分别是 ziplist 和 skiplist
当有序集合的元素个数小于 zset-max-ziplist-entries 配置(默认128个),并且每个元素的值都小于 zset-max-ziplist-value 配置(默认64字节)时,Redis 会用 ziplist 来作为有序集合的内部实现,上述两个条件之一不满足时,Redis 启用 skiplist 作为有序集合的内部实现。

下图演示了 Redis 存储 zset 内部实现转换过程,刚开始使用 ziplist,后面变为 skiplist

B- 树(B-Tree)

二叉查找树(binary search tree):

  • 每个节点其左子树上所有节点值要小于该节点值,右子树上所有节点的值要大于该节点值

平衡二叉树查找树:

  • 二叉树查找树中任意节点的左子树和右子树的高度差不大于一

B-Tree 遵循如下规则:

  • B-Tree 是一种自平衡的 M 叉查找树
  • 根节点至少存在两个子节点,至多存在 M 个子节点
  • 除了根节点和叶子节点,其他节点包含 k-1 个关键字和 k 个子节点(k 的取值范围[M/2,M])
  • 叶子节点包含 k-1 个关键字(k 的取值范围 [M/2,M] )
  • 所有叶子节点在树的同一层

B+ 树(B+Tree)

B+ 树遵循如下规则:

  • 每个节点最多有 M 个子节点(下文 MySQL 索引部分说明 M 取值)
  • 除根节点外,每个节点至少有 M/2 个子节点,根节点至少有两个子节点
  • M 叉树非叶子节点中只存储索引,不存储数据
  • 通过双向链表将叶子节点串联起来,这样可以方便按区间查找

B+ 树时间和空间复杂度:

  • 查询数据的时间复杂度是 O(logN)
  • 插入操作的时间复杂度是 O(logN)
  • 删除操作的时间复杂度是 O(logN)
  • 空间复杂度是 O(N)

B+ 树动态更新索引节点:

  • 写入数据时,如某节点的子节点个数大于 M,这时将对应节点分裂为两个节点,父节点有需要会级联分裂父节点
  • 删除数据后,如某节点的子节点个数小于 M/2,将相邻的兄弟节点合并

B+Tree 与 B-Tree 不同点:

  • 每个节点有 k 个关键字就有 k 个子节点(B-Tree 有 k 个关键字时有 k+1 个子节点)
  • 非叶子节点的关键字也存在于子节点中,并且是子节点中的最小/最大关键字
  • 非叶子节点只用于索引,不保存数据记录(B-Tree 中非叶子节点既保存索引也保存数据记录)
  • 关键字只出现在叶子节点,并且构成有序链表(按关键字从小到大排列)

MongoDB 索引

为什么 MongoDB 使用 B-Tree 来实现索引:

  • B+Tree 的非叶子节点只储存索引不保存数据,这样通过叶子节点查找数据的时间复杂度是固定的 O(N)
  • B-Teee 的非叶子节点也保存数据,所以查找数据的时间复杂度不固定,最快速的时候时间复杂度是 O(1)
  • MongoDB 是一种文件型的 NoSQL 数据库,主要追求高性能、高可用和可扩展性,基于对高性能的要求 MongoDB 选择了时间复杂度比 MySQL B+Tree 更高的 B-Tree 结构来实现索引

MySQL 索引

文件系统和数据库中索引通常使用 B+Tree 来实现,MySQL InnoDB 和 MYISAM 存储引擎也是使用 B+Tree 来实现索引

MySQL 不同存储引擎支持的索引实现结构如下官方文档:

为什么 MySQL 选择用 B+Tree 实现索引:

  • 操作系统是按页(默认大小 4K)读取磁盘中数据,一次读取一页数据
  • 如果读取的数据量超过一页大小,会触发多次 I/O 操作
  • 构建 M 叉树时,M 越大树的高度越小,M 的取值应让每个节点大小等于一个页大小,这样读取一个节点只需要一次磁盘 I/O 操作

红黑树(Red-Black Tree)

红黑树是一颗自平衡的二叉查找树(self-balancing Binary Search Tree),遵循如下规则:

  • 每个节点要么是红色要么是黑色
  • 根节点始终是黑色的
  • 没有相邻的两个红色节点(每个红色节点的两个子节点都是黑色)
  • 从任意节点到任意叶子节点的路径,包含相同数量的黑色节点

Java HashMap

Java 7 以及之前版本的 HashMap 同一个桶(Bucket)里面的节点(Entry)使用链表(Linked list)串联起来,当同一个桶里面存在过多节点时(对不同 key 的 hashcode 函数取值相等),查询时间的复杂度会从哈希 O(1) 退化到链表 O(N),为了避免上述问题, Java 8 的 HashMap 同一个桶中的节点个数在满足一定条件时会使用红黑树结构代替链表结构

红黑树和链表相互转换规则:

  • 当单个桶中的节点个数大于 TREEIFY_THRESHOLD( 默认 8),并且桶的个数大于 MIN_TREEIFY_CAPACITY( 默认 64),对应的桶会使用红黑树替代链表结构
  • 当移除元素后单个桶中的节点个数小于 UNTREEIFY_THRESHOLD( 默认 6),对应的桶会从红黑树恢复到链表结构
    /**
     * The bin count threshold for using a tree rather than list for a
     * bin.  Bins are converted to trees when adding an element to a
     * bin with at least this many nodes. The value must be greater
     * than 2 and should be at least 8 to mesh with assumptions in
     * tree removal about conversion back to plain bins upon shrinkage.
     */
    static final int TREEIFY_THRESHOLD = 8;

    /**
     * The bin count threshold for untreeifying a (split) bin during a
     * resize operation. Should be less than TREEIFY_THRESHOLD, and at
     * most 6 to mesh with shrinkage detection under removal.
     */
    static final int UNTREEIFY_THRESHOLD = 6;

    /**
     * The smallest table capacity for which bins may be treeified.
     * (Otherwise the table is resized if too many nodes in a bin.)
     * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
     * between resizing and treeification thresholds.
     */
    static final int MIN_TREEIFY_CAPACITY = 64;

你可能感兴趣的:(b-tree,mysql,redis,红黑树,skiplist)