• HashMap底层实现原理


      之前在csdn上面看到了这篇博客关于讲HashMap得,感觉讲的还挺细致的,就拿出来分享下,顺便总结下。

      HashMap连接:https://blog.csdn.net/QXJQQQ/article/details/78317385

      Hash参考连接:https://blog.csdn.net/u011109881/article/details/80379505?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522159962100819724839831976%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=159962100819724839831976&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v3~pc_rank_v2-1-80379505.first_rank_ecpm_v3_pc_rank_v2&utm_term=%E5%93%88%E5%B8%8C%E8%A1%A8&spm=1018.2118.3001.4187

      在说HashMap之前,先简单说下Hash函数,毕竟HashMap1.7得底层对key得下标位置计算也是运用了Hash函数。index=H(key)。Hash函数得构造方法也是有很多种:(1)直接定制法 (2)数字分析法 (3)平方取中法 (4)折叠法 (5)除留余数法。    除留余数法是最常用得,这里我就不一一说明了,具体得可以看上面得Hash参考链接。不过折叠法还是蛮有意思得。比如key=123 456 789,我们可以存储在6 15 24,取末三位,存在524的位置。

      Hash冲突: 两个元素,经过Hash算法后,得到了同一个位置,这就是冲突。所以Hash算法得涉及也是蛮重要得,涉及得好与坏,决定了你冲突得概率得高与低。既然存在Hash冲突,那也相对应存在解决方法,如:开放定址法(发生冲突,继续寻找下一块未被占用的存储地址),再散列函数法,链地址法,而HashMap即是采用了链地址法,也就是数组+链表的方式。

       接下来就总结下HashMap得put原理

    上面我说到了Hash函数得涉及得好与坏,决定了冲突概率,而HashMap里面得Hash设计采用了异或,移位等运算,对key的hashcode进一步进行计算以及二进制位的调整等来保证最终获取的存储位置尽量分布均匀

    final int hash(Object k) {
    	int h = hashSeed;
    	if (0 != h && k instanceof String) {
    		return sun.misc.Hashing.stringHash32((String) k);
    	}
    
    	h ^= k.hashCode();
    
    	h ^= (h >>> 20) ^ (h >>> 12);
    	return h ^ (h >>> 7) ^ (h >>> 4);
    }
    

    得到hash值之后,进一步确认key得下标位置

    /**
      * 返回数组下标
      */
    static int indexFor(int h, int length) {
         return h & (length-1);
    } 
    h&(length-1)保证获取的index一定在数组范围内,举个例子,默认容量16,length-1=15,h=18,转换成二进制计算为
       1 0 0 1 0
    &      0 1 1 1 1
           0 0 0 1 0 = 2
     
    最终计算出的index=2。有些版本的对于此处的计算会使用 取模运算,也能保证index一定在数组范围内
     
    接下来看看addEntry得实现方式

     通过以上的代码能够得知,当发生哈希冲突,并且size大于阙值的时候,需要进行数组扩容,扩容时需要新建一个长度为之前数据2倍的新的数组,然后将当前的Entry数组中的元素全部传输过去,扩容后的数组的长度是之前的2倍,所以扩容相对来说是个耗资源的操作。

    下面再说下get原理

    可以看出,get方法的流程,key(hashcode)-->hash-->indexFor-->最终索引位置,找到对应位置table[i],再查看是否有链表,遍历链表,通过key的equals方法比对查找对应的记录。

    总结:(1)对于存取元素,都是会根据key得到hash值,然后结合indexFor方法,找到对应得具体下标位置。

       (2)put:遍历链表,如果位置上存在相同得key,则将value替换成新得,并且返回oldValue。  get:遍历链表,寻找key,找到则返回。

    jdk1.8HashMap

    我就不细说了,推荐一篇解析jdk1.8HashMap原理不错得连接:https://www.cnblogs.com/xiaoxi/p/7233201.html

    jdk1.7和1.8中hashMap的一些变化

    (1)jdk1.7底层是数组+链表; jdk1.8里面底层就是数据+链表+红黑树
    (2)jdk1.7变为链表的头插法以及jdk1.8的尾插法区别
    (3)resize的逻辑修改(jdk7会出现死循环,jdk8不会)
    (4)hash算法优化:在 JDK1.8 的实现中,还优化了高位运算的算法,将 hashCode 的高 16 位与 hashCode 进行异或运算。

  • 相关阅读:
    DEVMODE 结构体
    VS2019如何将主菜单从标题栏移到单独一行
    最近学到的东西
    线上问题处理相关思考
    mybatis+spring
    jenkins
    自动化case校验点
    Sqlserver大数据迁移,导出-》导入(BULK INSERT)
    阿里P7大佬带你解密Sentinel
    《高可用系列》-限流神器Sentinel,不了解一下吗?
  • 原文地址:https://www.cnblogs.com/wei-cy/p/13645213.html
Copyright © 2020-2023  润新知