什么是红黑树?为什么Java的HashMap结构里要引入红黑树?
在jdk1.7到jdk1.8的更新中,HashMap引入了红黑树的数据结构,为啥呢,有什么好处呢,这就要把jdk1.7和jdk1.8的HashMap的结构拿来对比一番。
jdk1.7中的HashMap数据结构
在jdk1.7中,HashMap是使用一个Entry(一个实体类)来存储数据的,但是这个Entry是数组加链表结构,啥?数组还能和链表一起玩?请看图:
这个图是一个数组,描述的是一个添加元素的过程,大家可以按照存入第n个数的顺序来看。存一入一个元素时,会附带地存入一个hashcode值和下一项的地址,这个先不深究,先往下看。
细心的你发现了,这个数组的长度只有8(HashMap定义的数组长度不一定为8)。那HashMap可以一直add()添加元素,这数组长度只有8,能存那么多东西吗?
等等,添加第四个值的时候,好像有些不对劲,它跟在了第二个数的后面。这里其实就是链表了,可以看到,第二个数的下一项地址指向了第四个数,这里就可以解释,为什么长度为8的数组可以存无穷尽个元素。好了,新问题来了,第四个数为什么跟在第二个数后面,而不是其他数后面?先来了解一个小知识。
哈希计算 HashMap在添加数据时,会有一个哈希计算。计算过程如下:
添加的第一个元素哈希值为2300(Java创建对象时赋予的)
2300(哈希值)➗8(数组长度)=287(这个不重要)---------4(余数)
得到余数为4,那么这个对象就会存在Entry[4]这个位置上。第二元素的hashcode为1354,除以8后,余数为2,放在Entry[2]的位置。哦~原来HashMap在添加元素时候,不是乱添加的。下一个问题也随之而出,如果取余数结果相同,要怎么添加? 看图:
实体类代码如下
Node{
Node Parent;
Node Son;
Object object;
Node(Object object){//这个一般是泛型做到传啥是啥,不多说,懂得都懂
this.object = object;//上一条注释不负责,不懂的可以去看看Java泛型
}
}
现在很清晰了
存第四个对象只要两行代码就搞定:
//node是第二个数,将第四个数存在第二个数后面的过程
node.Son = new Node(Object);//创建个新节点,赋给Entry[2]的子节点
node.Son.Parent = node;//父子节点互相指向
现在对jdk1.7的HashMap应该有了一定了解,有没有想到有什么不妥,或者还可以优化的地方?
如果一堆对象,他们的哈希值除以8,余数全是2呢
那数组的某一个位置岂不是拖着很长的链表,并且想要查询某一个元素时候,要一个一个往后查,查询的速度就慢了。红黑树就能解决这个问题。
jdk1.8中的HashMap数据结构
通过上一小节,jdk1.7的HashMap由数组+链表组成。那么,jdk1.8的HashMap呢,是由数组+链表+红黑树组成。
我们先从数组加链表的结构谈到红黑树。首先链表和树的区别,一条直链在查找元素时,只能一条一条遍历。而对于树来说,可以通过特殊的遍历方法,如先序、中序、后序等遍历方法来加快查找速度。其中,二叉树的表现更优。红黑树就是二叉树的一种。先来看个图。
提问:这两棵树谁先能查找到H?
查找方法为,H比A小,查询A的左节点,比B小,查询B的左节点...
我们来复习一下数据结构知识
Node{
Node Parent;
Node Left;
Node Right;
Object object;
Node(Object object){
this.object = object;
}
}
查询代码如下:
public Node find(Node node, hashCode){//该方法传入
//根节点和要查数据的哈希值即可开始遍历查找
if(hashCode == node.hashCode){
return node;
}
else if(hashCode < node.hashCode){
return find(node.Left);//当被查找的哈希值小于当前节点的哈希值
//再调用自己,并传入左节点
//我调我自己----递归
}else{
return find(node.Right);
}
}
那么得到的路径为:
第一棵树的查找路径:A->B->D->F->H
第二棵树的查找路径:A->F->H
显然第二棵树更快一些。这是因为右边的二叉树比较平衡,就是左边和右边的节点差不多多
红黑树就是一颗,你插入值,它能根据某种规则,更改树各个节点的位置,来使得这棵树,更加平衡,这样找某些数的时候就递归的次数就会减少。
红黑树到底是啥,我们下一篇文章见