1、什么是HashMap?

HashMap通常提起他,我们想到的就是键值对方式存储(key-value型式),可以接收null键值和null值。基于Map接口的非同步实现(也就是线程不安全),并不保证映射的顺序,特别不保证这个顺序恒久不变

下图是HashMap的源码,可以看到它继承自AbstractMap,实现Map,Cloneable,Serializable接口。其一些默认值都一一列出:

其中,modCount这个属性值是记录HashMap内部结构发生变化(指的是内部结构,相同的key,put()一个value这种覆盖原来的值不属于结构变化)的次数,主要用于迭代的快速失败。

threshold来判断HashMap的最大容量,threshold = (int)(capacity * loadFactor);

loadFactor负载因子:散列表的实际元素数目/散列表的容量。

其衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高(装东西越满),使用链表法 的散列 表空间利用越充分,查找时间则变长效率降低;越小则结论与越大相反。

Entry为HashMap的静态内部类,从上面可以看出Entry为承载key-value的值,并且默认table为Entry数组。下面看一下Entry的源码:

  put时如果key传入为null,那么value一定会为null,无论value输入什么.并且map.put()/get()的返回值是value的类型,并且当存入空的时候为hashmap中数组第一位。下面是对应的小栗子:

1、

Map<String, String> map2 = new HashMap<String,String>();
String r2 = map2.put(null, null);
System.out.println("r2 "+r2);
String r3 = map2.get(null);
System.out.println("r3 "+r3);
String r4 = map2.put(null, "32");

System.out.println("r4 "+r4);

输出 结果:r2 null

r3 null

r4 null

2、

3、从此可以看出hashmap的hash方法如果key为空则默认0,否则key取hashcode异或运算h无符号右移16位

2、HashMap的数据结构是什么样的呢?

jdk的1.8之前的版本和1.8及之后的版本HashMap的数据结构出现了一点变化。我们这里主要介绍1.8之前的数据结构。主要是数组+链表的结构,上面给出的属性源码也可以看出HashMap是这样的数据结构(table数组,Entry类为一个链表的节点来存储Key-value),也叫拉链法(链表数组)。

1.8之后HashMap的数据结构出现了改变,变成数组+链表+红黑树,默认值增加了树的相关默认值,Entry类改成Node但是类里面内容没有改变。

并且增添了TreeNode节点,继承自LinkedHashMap,LinkedHashMap则继承自HashMap。下面是TreeNode内容:

红黑树是一种自平衡二叉查找树。它的统计性能要好于平衡二叉树(AVL树)。这种树结构从根节点开始,左子节点小于它,右子节点大于它。每个节点都符合这个特性,所以易于查找,是一种很好的数据结构。但是它有一个问题,就是容易偏向某一侧,这样就像一个链表结构了,失去了树结构的优点,查找时间会变坏。(这里就详细讲解这个结构了以后会开一个数据结构的专栏)

阅读源码可以看出树比链表占了更多的空间。8后,决定如何使用这两种数据结构呢? 
           1、如果一个内部表的索引超过8个节点,链表会转化为红黑树

2、 如果内部表的索引少于6个节点,树会变回链表

          当链表元素个数大于等于8时,链表转换成树结构;若桶中链表元素个数小于等于6时,树结构还原成链表。因为红黑树的平均查找长度是log(n),长度为8的时候,平均查找长度为3,如果继续使用链表,平均查找长度为8/2=4,这才有转换为树的必要。链表长度如果是小于等于6,6/2=3,虽然速度也很快的,但是转化为树结构和生成树的时间并不会太短。还有选择6和8,中间有个差值7可以有效防止链表和树频繁转换。假设一下,如果设计成链表个数超过8则链表转换成树结构,链表个数小于8则树结构转换成链表,如果一个HashMap不停的插入、删除元素,链表个数在8左右徘徊,就会频繁的发生树转链表、链表转树,效率会很低。

3、HashMap的工作原理

它的原理就是Hashing原理。在上一部分我们讲到了HashMap的数组结构,每添加(put())或是获取(get())都是向每个数组位(array[i])对应为一个bucket里的Entry中添加键值对。而通过hashCode()得出一个hash值来算出bucket(水桶)的位置来进行存取(这里的运算都是位运算)。

4、HashMap的存取

  1、存入主要是put()方法:                

可以从上面源码看出在put时,根据hash值来确定存放数组的索引位置。如果该位置上已经存放了Entry那么,那么将会以链表的形式存放键值对,原来的Entry放在next进行链接,及新加入的放链头,原来的放链尾。如果该位置上没有Entry则直接存放到对应的数组索引的位置上。

 2、取出get()方法:

从源码上可以看出get()也是先算取key的hash值,然后找到hash值对应的索引,看是否有链表,若有则看是否和链表中Entry的key是否相等,相等则equals()取value值。实际上就是将key-value看成一个整体Entry,来用hashCode()来查找hash值,如果有链表就判断链表,没有链表就直接取对应的数组索引。

 3、扩容resize()(rehash)方法:

5、HashMap的一些问题思考

1、什么时候会出现扩容呢?

源码上addEntry是size大于threshold时开始调用resize()方法。也就是HashMap中,数组元素超过了数组阀值的时候就重新扩容,以降低实际的负载因子。(threshold>capacity*loadfactor)默认的的负载因子 0.75是对空间和时间效率的一个平衡选择。当容量超出此最大容量时, resize后的HashMap 容量是原容量的两倍。

2、HashMap容量一定为2的幂呢?

首先,我们希望元素存放的更均匀,最理想的效果是每个bucket中存放一个Entry(key-value)。这样查询的时候效率高,不需要遍历链表,也不需要equals去比较链表中的key的内容。而且,这样空间利用率最大,时间复杂度最优。因为HashMap的底层数组的长度为2^n次方,不同的key算得的index的相同几率较小,数组上分布就比较均匀,也就是碰撞几率小,相对查询时效率会高。

假设:数组长度为32时,即为2的n次方时,2n-1得到的二进制数的每个位上的值都为1,这使得在低位上&时,得到的和原hash的低位相同,加之hash(int h)方法对key的hashCode的进一步优化,加入了高位计算,就使得只有相同的hash值的两个值才会被放到数组中的同一个位置上形成链表。

3、Hash冲突(碰撞)及解决Hash冲突的方法?     

hash冲突就是当hash值相同,此时他们确定的索引位置相同,这时他们的key如果不相同,则为hash冲突。

解决hash冲突的办法:

通常有:1、开放定址法 (基本思想是通过一个探测算法,当某个槽位已经被占据的情况下据需查找下一个可以使用的槽位)。

2、再哈希法(基本思想是同事构造多个哈希函数,当函数1冲突时,再用下一个方法计算,直到冲突不再产生)。这种方法不已长生聚焦,但增加计算时间。

3、链地址法(基本思想是将相同的hash值的对象组成一个成为同义词链的单链表,并将单链表的头指针存在哈希表的这个hash值中)。适用于经常插入和删除。

4、建立公共溢出区(将哈希表分为基本表和溢出表两部分,凡是发生冲突的都放在溢出表中)。

Java.until.HashMap就是采用链表法的方式。当发生碰撞,对象将会存储在LinkedList的下一结点中。HashMap在每个LinkedList节点中存储键值对对象。

4、重新调整HashMap大小存在的问题 

HashMap数组扩容后很消耗性能:原数组中的数据必须重新计算其出现在新数组中的位置,并存储进去。

在多线程下,HashMap扩容后可能会产生条件竞争(race condition)。如果两个线程都发现HashMap需要重新调整大小,他们会同时试着调整大小,在调整大小的过程中,存储在LinkedList中的元素次序会反过来,因为移动到新的bucket位置的时候Hashmap并不会将元素放在LinkedList的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争产生,就会出现死循环。

5、Fail-First机制

java.util.HashMap 不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了 hashmap,那么将抛出 ConcurrentModificationException,这就是所谓 fail-fast 策略。

在迭代过程中,判断 modCount 跟 expectedModCount 是否相等,如果不相等就表示已经有其他线程修改了 Map,则会抛出异常,下图是源代码:

解决办法:

1、在遍历过程中所有涉及到改变modCount值得地方全部加上synchronized或者直接使用Collections.synchronizedList,这样就可以解决。但是不推荐,因为增删造成的同步锁可能会阻塞遍历操作。

2、使用CopyOnWriteArrayList来替换ArrayList。推荐使用该方案。

6、为什么String, Interger这样的wrapper类适合作为键?

因为他们由final修饰,使用不可变类就能保证hashCode是不变的,而且重写了equals和hashcode方法,避免了键值对改写。如果两个不相等的对象返回不同的hashcode的话,那么碰撞的几率就会小些,提高HashMap性能。

Java基础 HashMap实现原理及方法相关推荐

  1. Java基础-hashMap原理剖析

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

  2. java原理教程,java基础之运行原理(一),java基础运行原理

    java基础之运行原理(一),java基础运行原理 java的核心配置:JDK JDK主要包括三个部分 1.Jre:java的运行环境 2.Java的工具:java的编译器(java.c.exe). ...

  3. Java执行引擎工作原理:方法调用

    Java执行引擎工作原理:方法调用 方法调用如何实现 函数指针和指针函数 CallStub源码详解 Git链接(有HotSpot源码) 1 方法调用如何实现 计算机核心三大功能:方法调用.取指.运算 ...

  4. 相位噪声基础及测试原理和方法

    相位噪声指标对于当前的射频微波系统.移动通信系统.雷达系统等电子系统影响非常明显,将直接影响系统指标的优劣.该项指标对于系统的研发.设计均具有指导意义.相位噪声指标的测试手段很多,如何能够精准的测量该 ...

  5. Java基础-Integer的==和equals方法

    Java基础-Integer的==和equals方法 1.首先说下 equals 方法: ​ equals 方法接受的参数为 Object 类型 equals(Object obj),首先会判断参数中 ...

  6. Java:HashMap实现原理

    HashMap的实现原理 HashMap的主干是一个Entry数组,Entry是HashMap的基本组成单元,每一个Entry包含一个key-value键值对.(其实所谓Map其实就是保存了两个对象之 ...

  7. java基础—Map集合的常见方法操作(java集合八)

    Map集合的常见方法操作 Map集合中的元素取出并打印的三种方式 import java.util.Collection; import java.util.HashMap; import java. ...

  8. Java基础—序列化底层原理

    原文地址:https://blog.csdn.net/xlgen157387/article/details/79840134 目录 一.基本概念 二.Java如何实现序列化和反序列化 三.相关注意事 ...

  9. (good)相位噪声基础及测试原理和方法

    摘要:相位噪声指标对于当前的射频微波系统.移动通信系统.雷达系统等电子系统影响非常明显,将直接影响系统指标的优劣.该项指标对于系统的研发.设计均具有指导意义.相位噪声指标的测试手段很多,如何能够精准的 ...

最新文章

  1. Gartner发布2021年重要战略科技趋势!
  2. sublime编辑器中文乱码的问题
  3. 新发现判断一个点在多边形的最高效率算法 推荐******
  4. 工业以太网在工业控制中的运用
  5. 微信开发修改button里的字体大小_微信小程序全栈开发课程【视频版】2.2 index页面完善...
  6. Codeforces 862D. Mahmoud and Ehab and the binary string 【二分】(交互)
  7. 【Python学习】 - sklearn学习 - 评估指标precision_score的参数说明
  8. C/C++中的声明与定义
  9. ORA-28001: the password has expired (DBD ERROR: OCISessionBegin)解决办法
  10. Win7 下安装流程图绘制软件 Dia
  11. 【算法】Aho-Corasick多模式匹配算法
  12. 华为运营商级路由器配置示例 | 配置VPLS Multi-homing示例
  13. BIN文件和HEX文件互转合并
  14. 机房收费管理系统之退卡
  15. 扫码报修开启校园报修管理系统新时代
  16. oracle翻译Advanced,Oracle高级复制,Oracle advanced replication,音标,读音,翻译,英文例句,英语词典...
  17. RFID在市场上的兴起:谁是赢家,谁是输家?
  18. 三菱PLC说明书手册-DataSheet
  19. keil软件仿真看不到波形
  20. Cocos2d-x 3.0 红孩儿私家必修 - 第一章 初识Cocos2d-x 3.0工程

热门文章

  1. 考研 考研爬虫大数据分析专业热度
  2. PyQt4编程之模态与非模态对话框(一)
  3. Python中字符串常用处理函数
  4. php 数组格式的字符串转为数组_php将字符串转换为数组实例讲解
  5. Linux挂载新磁盘
  6. RocketMQ(一):Linux安装RocketMQ和常用命令
  7. linux安装配置jdk1.8
  8. oracle ORA-00604和BadImageFormatException的解决方法
  9. c调用按钮点击事件_React中事件的写法总结
  10. python生成序列数(1-10)的立方列表_Python 4.3 创建数值列表(动手试一试)