学习目标:
HasgMap链表在什么状态下变为红黑树?(查看此文章建议大家把代码copy在idea,通过断点调试去理解学习)
Demo:
我们创建一个类,类中重写HashCode方法和equals方法。并且让Person对象获取的Hash值相等。目的是添加元素时,我们保证数据插入到同一个桶内。
class Person{
String name;
public Person(String name){
super();
this.name = name;
}
@Override
public int hashCode() {
return 100;
}
@Override
public boolean equals(Object obj) {
if(this==obj)
return true;
if(obj==null)
return false;
if(getClass()!=obj.getClass())
return false;
Person other = (Person) obj;
if(name==null){
if(other.name!=null){
return false;
}else if(!name.equals(other.name))
return false;
}return true;
}
}
添加main方法,实例如下:第一次循环添加8个Person对象,里面的name值不相等,但获取到的hash值是一样的。
public static void main(String [] args){
Map map = new HashMap();
int i=0;
for(i=1; i<=8;i++){
map.put(new Person("john"+i),i);
}
for(; i<=13;i++){
map.put(new Person("john"+i),i);
}
}
我们这里以添加第二个元素为例说一下,第一个元素添加详情请看HashMap Put方法源码分析*------上篇分析。第二次添加元素首先走putVal方法中的第一个if去判断当前的table值是不是null,我们二次添加元素显然不是null.
if ((tab = table) == null || (n = tab.length) == 0)
继续走下一个if,我们获取到的hash值与当前数组长度-1进行求余运算之后得带得数组索引值为i.判断tab数组在该索引处是否有值,因为我们获取到得HashCode值相等,所以数组索引值也相等,在我们第二次添加元素时,说明第一次已经在该数组的索引下有值。则走else{}。else方法中p为前一次数组对象,它去判断前一次元素hash值与当前hash值是否相等,它们的key值是否相等。如果相等则值覆盖。我们这里判断key值不一样,因为它添加Person对象,循环是key+i。不一样之后去判断p为树状结构还是列表结构。
if ((p = tab[i = (n - 1) & hash]) == null)
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
如果为列表结构,则把值拼接到下一个节点。核心代码。
p.next = newNode(hash, key, value, null);
如果binCount值超过8时,相当于桶容量超过8时,则走treeifyBin方法。
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
treeifyBin方法中不是立马变为树结构,它是通过先判断当前table数组值有没有超过64。如果没超过,再次扩容,并打乱原本存储数据位置,这时该桶下元素不一定有8个了。然后再次添加元素,table继续扩容,直到数组超过64之后,则把列表元素转化为TreeNode数据结构。
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
总结:当一个桶中的列表节点数>=8时,桶的总个数>=64时,会将列表结构变成红黑树结构
jdk7和jdk8的区别:
jdk7:创建hashmap对象,初始table容量值为16。
jdk8:创建hashmap没有初始table容量,只初始化加载因子,只有在第一次添加元素时,才把table容量初始化为16.
jdk7:table的数据类型为Entry,
jdk8:table的数据类型为Node
jdk7:不管列表的总结点数是多少都不会变为树结构。
jdk8:一个桶中的列表节点数>=8时,桶的总个数>=64时,会将列表结构变成红黑树结构
底层结构 线程安全 允许null键null值
hashtable 哈希表 不安全 允许
hashmap 哈希表 安全 不允许
您提的问题,就是对小编一次知识的回顾。