索引数据结构之哈希、红黑树(Red Black Tree)、B树(B-Tree)、B+树详解

引言

1、索引的本质:索引是帮助MySQL高效获取数据的排好序的数据结构
2、可以做索引结构:
(1)数组,依赖下标。
(2)链表
(3)哈希
(4)二叉树
(5)B树
(6)B+树
在这里插入图片描述
3、MySQL的索引为啥是B+Tree?
 答题线路:全部遍历→Hash→二叉树→平衡二叉树→B-TreeB+Tree

一、全部遍历(没有索引)

从头到尾扫描,根据实践经验,如果数据规模在2千条以下,可以直接查找,时间也是很快的,毕竟数据规模小。但是随着规模的扩大,是一定要建立索引,提高查找效率。比如没有目录的牛津字典,那查找起来,不得…

二、哈希(Hash)

1、Hash函数:JDKhash函数非常经典,建议熟记:

int hash(Object key){
	int h = key.hashCode();
	return (h ^ (h>>>16)) & (capitity-1); // capicity表示散列表的大小。
}

2、HashMap的底层数据结构:分JDK1.7JDK1.8两个版本。
(1)JDK1.7:底层数据结构是数组+单链表
(2)JDK1.8:对 HashMap 做了进一步优化,底层数据结构是数组+单链表(红黑树),当链表过长(默认当长度超过8)时,链表就转换成了红黑树,利用红黑树快速增删改查的特点,进一步提高HashMap的性能。当红黑树的结点个数少于6时,又会将红黑树转化成链表。因此在数据量较小的情况下,红黑树因为要维护平衡,性能上的优势并不明显。

3、HashMap默认的初始大小是16,如果事先值到大概的数据量有多大,可以通过修改默认值,减少动态扩容的次数,提高HashMap性能。

4、当 HashMap 中元素个数超过0.75*capacity的时候,就会启动自动扩容,每次扩容都会扩为原来的两倍大小。(当前容量大小*2)

5、扩容的时候,会将数据全部重新计算一次,线程不安全。

6、既然线程不安全,如何改进?
(1)new ConcurrentHashMap<>();

7、HashMap & ConcurrentHashMap 的区别?
  除了加锁,原理上无太大区别。另外,HashMap 的键值对允许有null,但是ConCurrentHashMap 都不允许。
  JDK 1.7 中使用分段锁(ReentrantLock + Segment + HashEntry),相当于把一个 HashMap 分成多个段,每段分配一把锁,这样支持多线程访问。锁粒度:基于 Segment,包含多个 HashEntry
  JDK 1.8 中使用 CAS + synchronized + Node + 红黑树。锁粒度:Node(首结点)(实现 Map.Entry)。锁粒度降低了。

8、JDK1.8中对HashMap做了哪些改变?
(1)在 java 1.8中,如果链表的长度超过了8,那么链表将转换为红黑树。(桶的数量必须大于64,小于64的时候只会扩容)
(2)发生hash碰撞时,java 1.7 会在链表的头部插入,而 java 1.8会在链表的尾部插入
(3)在java 1.8中,EntryNode 替代(换了一个马甲)。

9、思考:
(1)HashMap的查询/插入/修改/删除的平均时间复杂度是 O ( 1 ) O(1) O(1),效率极高,但是为什么没有用在MySQL中做索引?
 答:因为哈希索引不支持部分索引查询以及范围查询。仅当查询条件不会变,无部分查询,无范围查询,可以使用哈希。[要么 O ( 1 ) O(1) O(1), 要么 O ( n ) O(n) O(n)] [InnoDB并不支持哈希索引]

三、二叉排序树(Binary Sort Tree),又称二叉查找树(Binary Search Tree),亦称二叉搜索树

1、二叉排序树的性质:
(1)如果它的左子树不为空,则左子树上的结点的值都小于根结点;
(2)如果它的右子树不为空,则右子树上的结点的值都大于根结点;
(3)子树同样也要遵循以上两点。
在这里插入图片描述
采用该数据结构做索引的话,能够有效的解决范围查找的需求,而且查找的效率也很不错。例如,需要查找 13,只需要 4 次比较即可。时间复杂度为: O ( l o g 2 n ) , n O(log_2n),n O(log2n)n 是结点的总数。但是,在一些情况会退化成链表形式,如上图的右边所示,那么时间复杂度: O ( n ) O(n) O(n)

四、平衡二叉搜索树(Self-balancing binary search tree),又被称为AVL树

1、它的本质是一棵二叉搜索树,只不过是添加一些约束防止该二叉搜索树退化成链表。具有以下性质:
(1)左右两个子树的高度差的绝对值不超过1;
(2)并且左右两个子树都是一棵平衡二叉树。

【注】:尽管AVL树解决了二叉搜索树退化成链表的这一问题,但是由于其里面太过于理想,实际中使用得很少,更多的是采用了不追求 “完全平衡” 的红黑树。红黑树的插入删除操作效率略高于AVL树,AVL树用于自平衡的计算牺牲了插入删除性能,但是因为最多只有一层的高度差,查询效率会比红黑树高一些。实际应用中,若搜索的次数远远大于插入和删除,那么选择AVL树,如果搜索和插入删除次数几乎差不多,应该选择红黑树。

五、红黑树(Red Black Tree)t5

1、红黑树的底层数据结构是:二叉树
2、红黑树的性质:
(1)每个结点不是红色就是黑色;
(2)不可能有两个红色的结点连在一起(两个黑色可以连接在一起);
(3)树的跟结点都是黑色(root);
(4)每个红色结点的两个子节点都是黑色;
(5)叶子结点都是黑色;(隐藏了 nil 叶子结点)
3、红黑树的变换:
(1)变颜色
(2)旋转(分左旋和右旋)

在这里插入图片描述
4、案例:
给定一个红黑树,现在需要插入一个结点,描述其调整步骤。
在这里插入图片描述

调整过程详细步骤如下:
【分析】当遇到不满足条件时,我们需要变色和旋转来调整该树,使之成为一个红黑树。【第一步】判断变色的规则是:当前结点的父亲是红色,且它的祖父结点的另一个子结点也是红色(叔叔结点)。上述情况刚好满足变色的要求,故对其进行变色,变色三部曲:
(1)把父节点设为黑色;
(2)把叔叔结点也设为黑色;
(3)把祖父(爷爷)结点也就是父亲的父亲设为红色;

在这里插入图片描述
【第二步】经过变色后(当前指针是指向12这个结点)仍然存在两个红色结点相连,故需要继续调整(变色或旋转)。因为当前结点的叔叔结点是黑色,不满足变色规则,则是通过旋转。旋转又分左旋和右旋,此部分是符合左旋。判断左旋的规则是
(1)父结点是红色
(2)叔叔结点是黑色
(3)当前结点是右子树
(4)以父结点作为左旋

左旋步骤:当前结点(12)晋升为父结点,之前的父结点(5)变成当前父结点(12)的左孩子,此前结点(12)的左孩子全部变成结点(5)的右孩子。
在这里插入图片描述
【第三步】左旋后(当前结点指向5)仍然存在两个红色结点相连,故需要继续调整(变色或旋转)。因为当前结点的叔叔结点是黑色,不满足变色规则,则是通过旋转。当前结点(5)是左子树,故也不是左旋,只能是右旋了。判断右旋的规则是
(1)父结点是红色
(2)叔叔结点是黑色
(3)当前结点是左子树

右旋步骤:旋转之前,需要右旋步骤:要先把父结点变成黑色,祖父结点变成红色。而且,不同于左旋,右旋是要以祖父结点(19)作为右旋。当前结点(5)的父结点(12)晋升为祖父结点,曾经的祖父结点(19)变成结点(12)的右孩子,此前结点(12)的右孩子全部变成结点(19)的右孩子。
在这里插入图片描述
5、思考:
(1)红黑树效率很高,为什么没有用在MySQL中做索引?
数据库是将记录保存在磁盘中,将数据库的内容读取到内存中,是读一页大小(4K)的内容,而红黑树中的一个结点仅仅保存了一个数据,所以被查找到的概率很低,需要多次I/O操作,存在I/O浪费以及读取资源浪费红黑树适合在内存中进行查找,不适合数据量大或磁盘存储的情况中查找。

六、B树(B-Tree)

1、B树是二叉搜索树的一种拓展(为了更好理解,我个人称其B-Tree为n叉排序树)。

2、案例:创建一个 5 阶的 B-Tree
数据:[3,  14,  7,  1,  8,  5,  11,  17,  13,  6,  23,  12,  20,  26,  4,  16,  18,  24,  25,  19] (共20个)
提示:根据B-Tree特性,5 阶级的B-Tree,则磁盘空间最多有5个指针(存的查找路径),4个关键字(MySQL存的数据)。

创建过程详细步骤如下:

  1. 插入3,此前B-Tree 结构中为空,直接插入即可。
    在这里插入图片描述

  2. 插入14,因为 14 > 3 14>3 14>3,故直接将14放在3的后面即可。
    在这里插入图片描述

  3. 插入7,因为 3 > 7 > 14 3>7>14 3>7>14,故直接将7放在3与14的中间即可。
    在这里插入图片描述

  4. 插入1,因为 3 > 1 3>1 3>1,故直接将1放在3的前面即可。在这里插入图片描述

  5. 插入8,以上四个数据都很好处理,如果第五个数据也直接按照大小直接插入的话,就出现了矛盾(仅仅可以存放4个关键字),为了解决这一问题,需要分裂分裂规则是将中间元素移动到根结点
    在这里插入图片描述

  6. 插入5,
    在这里插入图片描述

  7. 插入11
    在这里插入图片描述

  8. 插入17
    在这里插入图片描述

  9. 插入13,此时又需要分裂了,将中间元素13移动到根结点,13比根结点的7大,故直接在7后面即可。
    在这里插入图片描述

  10. 插入6
    在这里插入图片描述

  11. 插入23
    在这里插入图片描述

  12. 插入12
    在这里插入图片描述

  13. 插入20
    在这里插入图片描述

  14. 插入26,又需要分裂,将中间结点20移动到根结点即可。
    在这里插入图片描述

  15. 插入4,又需要分裂,将中间结点4移动到根结点即可。
    在这里插入图片描述

  16. 插入16
    在这里插入图片描述

  17. 插入18
    在这里插入图片描述

  18. 插入24
    在这里插入图片描述

  19. 插入25
    在这里插入图片描述

  20. 插入19,又需要分裂,将中间结点17移动到根结点;但是发现根结点已经不满足条件了,故对当前的根结点进行分裂。
    在这里插入图片描述
    在这里插入图片描述

3、思考:
(1)B-Ttree,为什么没有用在MySQL中做索引?
 从B树结构图中可以看到每个节点中不仅包含数据的key值,还有data值。而每一个页的存储空间是有限的,如果data数据较大时将会导致每个节点(即一个页)能存储的key的数量很小,当存储的数据量很大时同样会导致B树的深度较大,增大查询时的磁盘I/O次数进而影响查询效率。但在B+树中,所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储key值信息,这样可以大大加大每个节点存储的key值数量,降低B+树的高度。

七、B+树(B+Tree)

B+树 是对 B树 的一种变形树,它与 B树 的差异在于:
(1)B+树非叶子结点不存数据,仅存key
(2)数据都记录在B+树的叶子结点中;
(3)B+树的叶子结点有双向指针(双向链表);
(4)这样的两个改进,能够提高区间访问的性能。(定位完后,要么从后往前找,要么从前往后找)
在这里插入图片描述
【注】:InnoDB存储引擎就是用B+树实现其索引结构。

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值