美文网首页
转:Hashmap在JDK8中的提升

转:Hashmap在JDK8中的提升

作者: Maggie编程去 | 来源:发表于2017-08-02 10:46 被阅读0次

    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中的提升

          本文链接:https://www.haomeiwen.com/subject/mxeelxtx.html