美文网首页
JDK HashMap详解

JDK HashMap详解

作者: 丑男李狗蛋 | 来源:发表于2017-12-19 18:41 被阅读0次

    HashMap的继承关系如图:


    HashMap的继承关系图

    初始空间大小 1<<4(16)
    最大空间大小1 << 30 (1073741824)
    哈希桶元素超过1,链接方式默认为链表形式,数量超过TREEIFY_THRESHOLD的阈值(默认8)将被转换成红黑树
    哈希桶的元素如果降低到UNTREEIFY_THRESHOLD的阈值(默认6)会被重新转换成链表形式
    最小的哈希表容量为64(4倍TREEIFY_THRESHOLD值)
    哈希表节点的类型为Node<K,V>(实现Map.Entry<K,V> 接口)

    结构为:
    hash:哈希值
    key:key
    value:值
    next:持有下一个节点的索引

    put过程(以下是伪代码,贴有部分jdk8的代码实现):

    1. 计算key的哈希值
      采用哈希算法

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

      这里也是哈希能兼容key为null的原因

      哈希算法为什么这样写?
      右位移16位,正好是32bit的一半,自己的高半区和低半区做异或,就是为了混合原始哈希码的高位和低位,以此来加大低位的随机性。而且混合后的低位掺杂了高位的部分特征,这样高位的信息也被变相保留下来。

    2. 将hash,key,value移交给putVal方法处理

    3. 处理节点数据

      1. 哈希表是空的:
        初始化哈希表,在初始化哈希表的过程中会进行扩容操作(resize()方法)会创建一个数组

        注意:实质上初始化数组和数组容量达到当前数组上限都会调用resize()方法并返回一个新的数组,达到容量上限的时候,会将原有数组的值copy(遍历到新的数组中)并返回新数组的引用,在扩容的时候会将原有哈希表中的元素分散到新的哈希表中,因为无论如何计算哈西元素的时候当前元素要么在当前位置(j)要么在(j+oldCap)位置,形成新的哈希桶,这样可以快速确定元素位置,而不用去重新确定元素对于所有桶的位置,这点很重要

      2. 根据当前key的哈希和当前的数组长度计算数值将被赋予的哈希桶位置,并赋值给如果当前哈希桶首元素为null,则创建一个新的node并赋予当前哈希表位置

      3. 如果不是null,则分为三种情况

        • map中已经有当前元素的key了.
          判定条件为:
          p.hash == hash &&
          ((k = p.key) == key || (key != null && key.equals(k)))
        • 当前节点是一个TreeNode节点(超过了树化阈值)
        • 没有当前元素并且不达到树化条件,则添加元素到链表中并将引用存入node节点的next中,如果添加元素之后达到树化条件,则将链表实现更新为红黑树的实现

        以下摘自网络
        但是超过这个阈值后HashMap开始将列表升级成一个红黑树,使用哈希值作为树的分支变量,如果两个哈希值不等,但指向同一个桶的话,较大的那个会插入到右子树里。如果哈希值相等,HashMap希望key值最好是实现了Comparable接口的,这样它可以按照顺序来进行插入。这对HashMap的key来说并不是必须的,不过如果实现了当然最好。如果没有实现这个接口,在出现严重的哈希碰撞的时候,你就并别指望能获得性能提升了。

      4. 如果map中存在次元素则替换元素的值并提供拓展点afterNodeAccess(Node<K,V> p)来处理这类数据

      5. 判定当前容量已经达到最大设置容量,如果达到给数组扩容

      6. 提供拓展点给子类提供添加之后的一些处理afterNodeInsertion(boolean evict)

    get过程(以下是伪代码,贴有部分jdk8的代码实现):

    1. 链表查找:计算key的哈希值,并获取当前哈希桶,判定当前哈希桶中的首元素是不是需要的数据(进行equals)比较,如果不是,则依次遍历节点后驱直至找到key对应的node

    2. 遍历红黑树:判定首节点的类型是否是TreeNode,如果是从红黑树中获取元素并返回

    3. 如果没有查找到元素则返回null

       需要注意的是:
       containsKey()和containsValue()方法,其实质还是遍历一遍哈希桶,所以,如果只是判定在map中含有这个数据,用这个方法当然更好,但是如果需要这个数据做下一步运算就用get判空效率更高,因为containsKey()之后再次get相当于调用了两次遍历,效率相对下降许多
      

    keySet()和values()方法:

    keySet()和values()是因为hashMap继承自AbstractMap,它提供了超类实现将node中的key和value拆分到两个set中(keySet和values()),在第一次调用的时候(new KeySet的时候和values()的时候会创建一个对象,这个对象引用了key序列和value序列,这里并不需要去维护这个数组,只需要实现了set的迭代器name这个对象就会被索引到set里面(很神奇,我也惊讶了好久 但是实验过后的确是这样的)),之后就能取得set里面的数据,但是这个set的数值发生改变,map本身的数据也会发生改变,里面存放的只是对象的key/value引用。

    ps:没有人比我写的更详细了吧,就问还有谁还有谁!!!

    相关文章

      网友评论

          本文标题:JDK HashMap详解

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