1、HashMap原理

HashMap就像它的名字一样,它的Key是通过HashCode来存储的,它的底层结构在JDK1.8以前是数组+链表,JDK1.8以后为了提高查询的性能加入了红黑树,首先HashMap维护了一个数组,数组中存储的元素类型为Entry,Entry就是一个键值对,当每一个Entry要被添加到数组中时,就要对这个Entry进行Hash算法来计算出它的HashCode,计算的时候是用Key来计算它的hashCode,得到它的HashCode以后,对它的HashCode进行取模运算,比如说EntryA取模的结果是1,EntryB取模的结果是2,entryC取模的结果是3,那么就根据这个值将Entry添加到对应的数组中的位置,在这个添加的过程中Hash算法是很重要的,假如一个Hash算法比较优秀的话,他计算出来的HashCode是比较分散、比较均匀的, 这样的话每一个元素才能均匀的分布在数组中,这样的话就认为Hash算法是比较优秀的,这样的元素分布可以让HashMap的一个查找的时间复杂度达到O(1),如果这个Hash算法比较差,它计算出来的HashCode取模的结果都一样的,这样的话数组就会退化成一个链表,查询的时间复杂度就变成了O(n),这样的话查找效率的差异是非常大的,所以在JDK8中对这个做了优化,就是如果一个数组中的节点数值超过8的时候,就将这个链表转换为红黑树,红黑树它的查询时间复杂度是O(logn),这样的话就起到了一定的优化作用。
节点数大于8时转换为红黑树是泊松分布,综合考虑到时间和空间的一个开销。

  • 结构中数组总存储的元素叫Node, Node是一个对Entry的实现,Entry是一个接口,node是它的一个实现类。
  static class Node<K,V> implements Map.Entry<K,V> {final int hash;final K key;V value;Node<K,V> next;//因为可能会拓展为链表,next存在}-除了table是一个node数组之外的其他属性transient Node<K,V>[] table;transient Set<Map.Entry<K,V>> entrySet;//Node的上层接口,用来存放所有的Entry,用于遍历等操作transient int size;//是table中被使用的实际的元素数量transient int modCount;//计数器,记录HashMap结构发生变化的次数,比如put了一个新的值,发生了扩容等int threshold;//扩容的临界值threshold = size * loadFactor,final float loadFactor;//负载因子
  • 扩容

    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    // static final int MAXIMUM_CAPACITY = 1 << 30;
    

    当table中的元素被使用到了75%以上的时候,就要进行对这个hashMap 进行扩容,threshold就是这个扩容的临界值,table中的元素达到这个threshold之后,就会进行扩容,每次进行扩容的时候都是成倍的,扩容之后的大小就是之前的2倍,扩容是一个比较耗资源的操作,但是它的好处就是说,因为你的数组长度更长了,所以Hash算法的结果能够更均匀,这样的话也可以减少hash碰撞的次数。

  • 为什么负载因子是0.75: 当负载因子是1.0的时候,也就意味着,只有当数组的8个值全部填充了,才会发生扩容。这就带来了很大的问题,因为Hash冲突时避免不了的。当负载因子是1.0的时候,意味着会出现大量的Hash的冲突,底层的红黑树变得异常复杂。对于查询效率极其不利。这种情况就是牺牲了时间来保证空间的利用率。
    负载因子是0.5的时候,这也就意味着,当数组中的元素达到了一半就开始扩容,既然填充的元素少了,Hash冲突也会减少,那么底层的链表长度或者是红黑树的高度就会降低。查询效率就会增加。
    但是,兄弟们,这时候空间利用率就会大大的降低,原本存储1M的数据,现在就意味着需要2M的空间。 一句话总结就是负载因子太小,虽然时间效率提升了,但是空间利用率降低了。

  • 为什么table的长度是2的次幂: 为了在取模的时候做优化吧,当你判断一个元素要进入数组的哪个桶的时候,就要对这个key的HashCode进行取模运算,但是这个取模运算是一个比较重操作吧,它比较消耗性能,但是当这个table数组的长度为2的幂次方的时候,用hashcode & (table.length - 1)的运算等价于对table.length的取模运算结果,位于运算相比于取模运算的话它的性能更好,因为位运算的操作是直接对二进制的数据进行计算的,而取模操作需要将hash值转换为十进制之后在进行运算,所以&运算再性能上有很大的优势。

    static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}
    // 在扩容时,将旧的table中的元素添加到新的table中的时候,
    //使用了将e.hash & (newCap - 1)的这样一个操作,
    
  • 为什么将key的hash值右移16位在进行亦或的好处: 主要是为了保留高位和低位的信息,这样的画就能表现目标元素的特征,相当于减少了hash碰撞的次数。

    例子:
    A:1110 1110 & 111 的时候,它的高四位的数据就会全部丢失,因为&的111的高四位全部是0,所以得出的结果就是110,对A进行右移四位之后得到0000 1110 ,再将你这旧A的值和新的A的值进行异或(^)操作之后,得到的结果就是1110 0000,再将这个异或之后的值和table.length -1 进行与运算时就可以使计算出来的Hash更分散吧,因为异或之后的值混合了高四位和低四位的数据信息。

  • 为什么hash % table.length == hash & (table.length - 1): 对无符号数的取模和取余是一样的,假设length == 2的幂次,哈希值对2 ^ n取余,就是将hash值的二进制右移 n 位,移动的n位就是余数 , length = 2^n,length -1 的结果就是 11…1 ,n个1组成, 它和这个hash值做&操作结果和取模一样

    假设hash值是 1101 1101(221)% 10(2) 取模的结果1,
    将hash值和length -1 相与的结果也是1
    
  • put操作: 当我们想往一个 HashMap 中添加一对 key-value 时,系统首先会计算 key 的 hash 值,然后根据 hash 值确认在 table 中存储的位置。若该位置没有元素,则直接插入。否则迭代该处元素链表并依次比较其 key 的 hash 值。如果两个 hash 值相等且 key 值相等(e.hash == hash && ((k = e.key) == key || key.equals(k))),则用新的 Entry 的 value 覆盖原来节点的 value。如果两个 hash 值相等但 key 值不等 ,则将该节点插入该链表的链头。

  • get操作:
    通过 key 的 hash 值找到在 table 数组中的索引处的 Entry,然后返回该 key 对应的 value 即可。在这里能够根据 key 快速的取到 value 除了和 HashMap 的数据结构密不可分外,还和 Entry 有莫大的关系。HashMap 在存储过程中并没有将 key,value 分开来存储,而是当做一个整体 key-value 来处理的,这个整体就是Entry 对象。同时 value 也只相当于 key 的附属而已。在存储的过程中,系统根据 key 的 HashCode 来决定 Entry 在 table 数组中的存储位置,在取的过程中同样根据 key 的 HashCode 取出相对应的 Entry 对象(value 就包含在里面)。

Java HashMap原理相关推荐

  1. Java HashMap 原理

    原文:https://tryenough.com/java-hashmap 本文涉及HashMap的: HashMap的简单使用 HashMap的存储结构原理 HashMap的扩容方法原理 HashM ...

  2. Java HashMap原理及内部存储结构

    本文将通过如下简单的代码来分析HashMap的内部数据结构的变化过程. public static void main(String[] args) {Map<String, String> ...

  3. 【Java基础】HashMap原理详解

    [Java基础]HashMap原理详解 HashMap的实现 1. 数组 2.线性链表 3.红黑树 3.1概述 3.2性质 4.HashMap扩容死锁 5. BATJ一线大厂技术栈 HashMap的实 ...

  4. Java基础-hashMap原理剖析

    Java基础-hashMap原理剖析 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任.   一.什么是哈希(Hash) 答:Hash就是散列,即把对象打散.举个例子,有100000条数 ...

  5. Java HashMap的工作原理 及各种Map区别

    2019独角兽企业重金招聘Python工程师标准>>> 一.Java HashMap的工作原理 jdk1.7下HashMap数据结构:数组加链表,链表长度没有8的限制: jdk1.8 ...

  6. java基础--java中HashMap原理

    java中HashMap原理 内推军P21 P22 1.为什么用HashMap? HashMap是一个散列桶(数组和链表),它存储的内容是键值对(key-value)映射HashMap采用了数组和链表 ...

  7. Java HashMap的底层实现原理

    一.Java HashMap的底层实现原理(以jdk7为例) 1.HashMap map = new HashMap(); 在实例化以后,才在底层创建了一个长度为16的一维数组 Entry [] ta ...

  8. Java HashMap工作原理深入探讨

    大部分Java开发者都在使用Map,特别是HashMap.HashMap是一种简单但强大的方式去存储和获取数据.但有多少开发者知道 HashMap内部如何工作呢?几天前,我阅读了java.util.H ...

  9. Java集合篇:HashMap原理详解(JDK1.8)

    概述 JDK 1.8对HashMap进行了比较大的优化,底层实现由之前的"数组+链表"改为"数组+链表+红黑树",本文就HashMap的几个常用的重要方法和JD ...

  10. hashmap原理_想要彻底搞懂HashMap?你得恶补下HashMap原理

    引言 唉! 金九银十转眼已过, 面试现场不知所措: 不懂原理是我过错, 今日弥补只为求过. ====================================================== ...

最新文章

  1. python环境下,执行系统命令方法
  2. JWT对称加密非对称加密
  3. java中获取文件总行数_关于java:如何以有效的方式获取文件中的行数?
  4. 并发队列ConcurrentLinkedQueue和阻塞队列LinkedBlockingQueue用法
  5. 第5章 Python 数字图像处理(DIP) - 图像复原与重建16 - 约束最小二乘方滤波、几何均值滤波
  6. 大牛逝世 = 新人上位 = 科学进步?新研究表明确实如此
  7. JAVA蓝桥杯: 01字串
  8. 敏捷开发般若敏捷系列之五:如何推广敏捷(中)(无寿者,回报,破我执)...
  9. 安徽理工大学大学计算机科学与技术学院,安徽理工大学计算机科学与工程学院介绍...
  10. RuntimeError: ./xxx.pth is a zip archive (did you mean to use torch.jit.load()?)
  11. 基于element插件的表单验证及重置
  12. 开源和互联网是天生的一对好基友
  13. Docker部署各种服务
  14. 华为复制加密门禁卡_将多种累赘门禁卡归一合并的最佳选择
  15. 432偏计算机编程,统计学考研432笔记
  16. 斐讯路由器(K2P)解决DNS污染问题
  17. (四)Linux环境部署(Centos+Nginx+Tomcat+Mysql) - 安装Tomcat和JDK 以及 Nginx与Tomcat整合
  18. python 爬取淘宝第一弹(淘宝登录)
  19. 【 深度 】华为芯片的自研之旅!
  20. 小白opencv的入门处理技巧

热门文章

  1. bzoj 5281: [Usaco2018 Open]Talent Show【dp】
  2. Xcode包管理工具Alcatraz
  3. 字符串交错组成--很优美的递归算法
  4. DHTML3(表格动态创建,删除行/列,表格行排序,行颜色交替高亮显示)
  5. 布局--------动态添加 相对布局
  6. 在宿舍如何使用IPv6免费上网(非第三方软件)
  7. html中一条横线代码怎么写_Vue VS React 在开发同一记账项目中二者的对比
  8. 拓端tecdat|R语言进行数据结构化转换:Box-Cox变换、“凸规则”变换方法
  9. 拓端tecdat|Python中基于网格搜索算法优化的深度学习模型分析糖尿病数据
  10. 拓端tecdat|R语言线性判别分析(LDA),二次判别分析(QDA)和正则判别分析(RDA)