转:Hashmap在JDK8中的提升

HashMap使用key的hashCode()和equals()方法来将值划分到不同的桶里。桶的数量通常要比map中的记录的数量要稍大,这样 每个桶包括的值会比较少(最好是一个)。当通过key进行查找时,我们可以在常数时间内迅速定位到某个桶(使用hashCode()对桶的数量进行取模) 以及要找的对象。

这些东西你应该都已经知道了。你可能还知道哈希碰撞会对hashMap的性能带来灾难性的影响。如果多个hashCode()的值落到同一个桶内的 时候,这些值是存储到一个链表中的。最坏的情况下,所有的key都映射到同一个桶中,这样hashmap就退化成了一个链表——查找时间从O(1)到 O(n)。

当然这是在jdk8以前,JDK1.6中HashMap采用的是位桶+链表的方式,即我们常说的散列链表的方式,而JDK1.8中采用的是位桶+链表/红黑树的方式,也是非线程安全的。当某个位桶的链表的长度达到某个阀值的时候,这个链表就将转换成红黑树。

看下面的代码

[java]view plaincopy

//链表节点

staticclassNodeimplementsMap.Entry {

finalinthash;

finalK key;

V value;

Node next;

//省略

}

//红黑树节点

staticfinalclassTreeNodeextendsLinkedHashMap.Entry {

TreeNode parent;// red-black tree links

TreeNode left;

TreeNode right;

TreeNode prev;// needed to unlink next upon deletion

booleanred;

TreeNode(inthash, K key, V val, Node next) {

super(hash, key, val, next);

}

//省略

}

// HashMap的主要属性

publicclassHashMapextendsAbstractMap

implementsMap, Cloneable, Serializable {

// 槽数组,Node类型,TreeNode extends LinkedHashMap.Entry,所以可以存放TreeNode来实现Tree bins

transientNode[] table;

transientSet> entrySet;

transientintsize;

// 去掉了volatile的修饰符

transientintmodCount;

intthreshold;

finalfloatloadFactor;

...

}

[java]view plaincopy

//计算key的hash

staticfinalinthash(Object key) {

inth;

return(key ==null) ?0: (h = key.hashCode()) ^ (h >>>16);

}

饭后我们在看看具体的put和get方法

[java]view plaincopy

publicV get(Object key) {

Node e;

return(e = getNode(hash(key), key)) ==null?null: e.value;

}

finalNode getNode(inthash, Object key) {

Node[] tab;

Node first, e;

intn; K k;

//hash & length-1 定位数组下标

if((tab = table) !=null&& (n = tab.length) >0&&

(first = tab[(n -1) & hash]) !=null)

{

if(first.hash == hash &&// always check first node

((k = first.key) == key || (key !=null&& key.equals(k))))

returnfirst;

if((e = first.next) !=null) {

/*第一个节点是TreeNode,则采用位桶+红黑树结构,

* 调用TreeNode.getTreeNode(hash,key),

*遍历红黑树,得到节点的value

*/

if(firstinstanceofTreeNode)

return((TreeNode)first).getTreeNode(hash, key);

do{

if(e.hash == hash &&

((k = e.key) == key || (key !=null&& key.equals(k))))

returne;

}while((e = e.next) !=null);

}

}

returnnull;

}

finalTreeNode getTreeNode(inth, Object k) {

//找到红黑树的根节点并遍历红黑树

return((parent !=null) ? root() :this).find(h, k,null);

}

/*

*通过hash值的比较,递归的去遍历红黑树,这里要提的是compareableClassFor(Class k)这个函数的作用,在某些时候

*如果红黑树节点的元素are of the same "class C implements Comparable" type

*利用他们的compareTo()方法来比较大小,这里需要通过反射机制来check他们到底是不是属于同一个类,是不是具有可比较性.

*/

finalTreeNode find(inth, Object k, Class kc) {

TreeNode p =this;

do{

intph, dir; K pk;

TreeNode pl = p.left, pr = p.right, q;

if((ph = p.hash) > h)

p = pl;

elseif(ph < h)

p = pr;

elseif((pk = p.key) == k || (k !=null&& k.equals(pk)))

returnp;

elseif(pl ==null)

p = pr;

elseif(pr ==null)

p = pl;

elseif((kc !=null||

(kc = comparableClassFor(k)) !=null) &&

(dir = compareComparables(kc, k, pk)) !=0)

p = (dir <0) ? pl : pr;

elseif((q = pr.find(h, k, kc)) !=null)

returnq;

else

p = pl;

}while(p !=null);

returnnull;

}

[java]view plaincopy

//put(K key,V value)函数

publicV put(K key, V value) {

returnputVal(hash(key), key, value,false,true);

}

finalV putVal(inthash, K key, V value,booleanonlyIfAbsent,

booleanevict) {

Node[] tab;

Node p;

intn, i;

//如果table为空或者长度为0,则resize()

if((tab = table) ==null|| (n = tab.length) ==0)

n = (tab = resize()).length;

//找到key值对应的槽并且是第一个,直接加入

if((p = tab[i = (n -1) & hash]) ==null)

tab[i] = newNode(hash, key, value,null);

else{

Node e;

K k;

//第一个node的hash值即为要加入元素的hash

if(p.hash == hash &&

((k = p.key) == key || (key !=null&& key.equals(k)))){

e = p;

}elseif(pinstanceofTreeNode)//第一个节点是TreeNode,即tree-bin

/*Tree version of putVal.

*final TreeNode putTreeVal(HashMap map, Node[] tab,int h, K k, V v)

*/

e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);

else{

//不是TreeNode,即为链表,遍历链表

for(intbinCount =0; ; ++binCount) {

/*到达链表的尾端也没有找到key值相同的节点,

*则生成一个新的Node,并且判断链表的节点个数是不是到达转换成红黑树的上界

*达到,则转换成红黑树

*/

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);

//返回旧的value值

returnoldValue;

}

}

++modCount;

if(++size > threshold)

resize();

afterNodeInsertion(evict);

returnnull;

}

HashMap会动态的使用一个专门的treemap实现来替换掉它。这样做的结果会更好,是O(logn),而不是糟糕的O(n)。它是如何工作 的?前面产生冲突的那些KEY对应的记录只是简单的追加到一个链表后面,这些记录只能通过遍历来进行查找。但是超过这个阈值后HashMap开始将列表升 级成一个二叉树,使用哈希值作为树的分支变量,如果两个哈希值不等,但指向同一个桶的话,较大的那个会插入到右子树里

个性能提升有什么用处?比方说恶意的程序,如果它知道我们用的是哈希算法,它可能会发送大量的请求,导致产生严重的哈希碰撞。然后不停的访问这些 key就能显著的影响服务器的性能,这样就形成了一次拒绝服务攻击(DoS)。JDK 8中从O(n)到O(logn)的飞跃,可以有效地防止类似的攻击,同时也让HashMap性能的可预测性稍微增强了一些。

转载请注明出处http://blog.csdn.NET/a837199685

你可能感兴趣的:(转:Hashmap在JDK8中的提升)