HashMap 源码分析

HashMap 源码分析

  1. HashMap 的内部数据结构是怎样的?

    答:数组+链表 具体结构如下图:
    在这里插入图片描述
    验证:数组、链表(单向)

    数组:

     transient Node<K,V>[] table;
    

    Node 结构如下(链表):

     static class Node<K,V> implements Map.Entry<K,V> {
            final int hash;
            final K key;
            V value;
            Node<K,V> next;  指向下一个节点(单向链表)
            Node(int hash, K key, V value, Node<K,V> next) {
                this.hash = hash;
                this.key = key;
                this.value = value;
                this.next = next;
            }
    

2.hash 的作用?

map.put(1,“javck”)

-----Node(key=1,value="jack",....) 组装成功后。它该落到那边?数组上or 链表上?

答:1>.一定是要经过数组。数组的默认大小为16. 具体源码中是通过

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
使用位移算法的好处?位移运算效率高。

2>.一定要得到一个数组的下标位置,

​ 为什么不使用 Random.next(16)? 随机得到一个值呢?

​ 答:因为这样重复几率高,会出现链表节点上Node 节点分布不均匀

​ 因此引进一个算法 hash这个算法是用于得到数组下标的前戏。

​ (1)根据hash 算法得到一个整型数

​ (2)控制在 0-15之间

​ hash 算法 :用 key.hashCode()高16位 和低16位 进行一个异或运算

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
  1. 请你描述下 put 的过程,流程是怎么的,在源码中是如何体现的

    a.先根据hash 算法得到一个 结果 :1484515

    b.先判断当前 Node 节点的组是否为空,如果为空,先初始化这个数组,源码如下:

    判断是否为空

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ASw2BjtR-1584172650103)(C:\Users\86176\AppData\Roaming\Typora\typora-user-images\1582883075766.png)]

    对变量进行初始化赋值:

      else {               // zero initial threshold signifies using defaults
          newCap = DEFAULT_INITIAL_CAPACITY;  
          newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
       }
    

    初始化数组大小:

      @SuppressWarnings({"rawtypes","unchecked"})
      Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    

    c.根据key,value 值组装成 Node 节点 然后计算初下标的位置 (根据hash 算法的值 “&”n-1 得到下标的位置 ),判断该位置是被占用

     if ((p = tab[i = (n - 1) & hash]) == null)  //计算下标i的位置 说明:n-1=15的二进制是01111表示 & 之前算的hash 值 得到的结果必然在 0~15之间,为什么不用前面计算出的hash来%16算出下标的位置?因为 二进制& 运算比% 运算效率高的多。
      tab[i] = newNode(hash, key, value, null);
    

    d.如果!=null 说明此时数组下标tab[i ]有值,则需要将该Node节点存储到链表上。

     else {
          Node<K,V> e; K k;   //e临时变量
          if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k)))) //说明key相等
                    e = p; //将p 赋值到临时 e
                else if (p instanceof TreeNode)//如果当前节点属于红黑树Node 则用红黑树的存储方式
                    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                else {
                    
                    for (int binCount = 0; ; ++binCount) {
                        if ((e = p.next) == null) {
                            //使用普通的Node链表进行存储。
                            p.next = newNode(hash, key, value, null);
                            //如果链表的长度大于8,则转成红黑树的存储方式。
                            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;
                    }
                }
                //如果临时变量有值 证明 key相等,接下来需要将之前的value进行覆盖
                if (e != null) { // existing mapping for key
                    V oldValue = e.value;
                    if (!onlyIfAbsent || oldValue == null)
                        e.value = value;
                    afterNodeAccess(e);
                    return oldValue;
                }
            }
    

    e.判断数组的一个容量大小的改变,扩容机制

    if (++size > threshold)
                resize();  //另外一个功能就是进行扩容
     afterNodeInsertion(evict);
    

    扩容 :扩容的前提, 当16*n <16进行扩容 比如说12

    扩容的标准:数组的大小*0.75=某个值,当大于这个值的时候进行扩容。

    2倍扩容,为什么是2的整数倍进行扩容呢?源码描述如下:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CBARII7X-1584172650103)(C:\Users\86176\AppData\Roaming\Typora\typora-user-images\1582888083180.png)]
    至于为什么是2的整数倍呢?
    在这里插入图片描述
    16->32

    对新数组进行使用,将原来的数组移动到新数组上

    (1)数组有元素,下面为null的时候:
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KRCxHCSi-1584172650104)(C:\Users\86176\AppData\Roaming\Typora\typora-user-images\1582888763404.png)]

    (2)数组位置有元素,下面不为null,链表,

    else { // preserve order
                            Node<K,V> loHead = null, loTail = null;
                            Node<K,V> hiHead = null, hiTail = null;
                            Node<K,V> next;
                            do {
                                next = e.next;
                                //正常情况下 是计算在数组上的位置
                                if ((e.hash & oldCap) == 0) {
                                    // 判断001010101010
                                     //		    01111  &
                                     //		    10000  &
                                  //  ------------------------
                                             // 00000 判断这个结果是否为0 ,如果到数第五位是0的话则满足该条件
                                    if (loTail == null)
                                        loHead = e;
                                    else
                                        loTail.next = e;
                                    loTail = e;
                                }
                                else {
                                    if (hiTail == null)
                                        hiHead = e;
                                    else
                                        hiTail.next = e;
                                    hiTail = e;
                                }
                            } while ((e = next) != null);
                            if (loTail != null) {
                                loTail.next = null;
                                newTab[j] = loHead;  //要么在原来的位置
                            }
                            if (hiTail != null) {
                                hiTail.next = null;
                                newTab[j + oldCap] = hiHead;  //要么原来的位置+原来总容量大小。
                            }
                        }
    

    (3)数组位置有元素,下面不是null,红黑树

在这里插入图片描述

问题

  1. 为什么HashMap的长度是2的整数次幂?

    1.计算 Noed节点下落数组中的位置方式是: hash(KEY) & (length - 1)

    假设现在数组的长度 length 可能是偶数也可能是奇数

    length 为偶数时,length-1 为奇数,奇数的二进制最后一位是 1,这样便保证了 hash &(length-1) 的最后一位可能为 0,也可能为 1(这取决于 hash的值)即 & 运算后的结果可能为偶数,也可能为奇数,这样便可以保证散列的均匀性

    例如 length = 4,length - 1 = 3, 3 的 二进制是 11
    若此时的 hash 是 2,也就是 10,那么 10 & 11 = 10(偶数位置)
    hash = 3,即 11 & 11 = 11 (奇数位置)
    

    而如果 length 为奇数的话,很明显 length-1 为偶数,它的最后一位是 0,这样 hash & (length-1) 的最后一位肯定为 0,即只能为偶数,这样任何 hash 值都只会被散列到数组的偶数下标位置上,这便浪费了近一半的空间

    因此,length 取 2 的整数次幂,是为了使不同 hash 值发生碰撞的概率较小,这样就能使元素在哈希表中均匀地散列

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值