文章目录

  • 一.HashMap是什么
  • 二.HashMap继承类对比分析
  • 三.HashMap源码相关单词含义
  • 四.HashMap如何确定哈希桶数组索引位置
  • 五. HashMap 的 put 方法分析
  • 六.HashMap扩容机制
  • 七.HashMap线程安全性

一.HashMap是什么

  • HashMap是Java程序员使用频率最高的用于映射(键值对)处理的数据类型。
  • HashMap是一个用于存储Key-Value键值对的集合,每一个键值对也叫做Entry。这些个键值对(Entry)分散存储在一个数组当中,这个数组就是HashMap的主干。
  • 数据结构(JDK1.8):Node<K,V>[] table; 数组+链表+红黑树

二.HashMap继承类对比分析

  • Java为数据结构中的映射定义了一个接口java.util.Map,此接口主要有四个常用的实现类,分别是HashMap、Hashtable、LinkedHashMap和TreeMap
  • 类继承关系如下图所示:
  • HashMap :它根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。 HashMap最多只允许一条记录的键为null,允许多条记录的值为null。HashMap非线程安全,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以CollectionssynchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap。
  • Hashtable :Hashtable是遗留类,很多映射的常用功能与HashMap类似,不同的是它承自Dictionary类,并且是线程安全的,任一时间只有一个线程能写Hashtable,并发性不如ConcurrentHashMap,因为ConcurrentHashMap引入了了分段锁。Hashtable不建议在新代码中使⽤用,不需要线程安全的场合可以⽤用HashMap替换,需要线程安全的场合可以用ConcurrentHashMap替换。
  • LinkedHashMap :LinkedHashMap是HashMap的一个子类,保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序。
  • TreeMap :TreeMap实现SortedMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。如果使用排序的映射,建议使用TreeMap。在使用TreeMap时,key必须实现Comparable接口或者在构造TreeMap传入自定义的Comparator,否则会在运行时抛出java.lang.ClassCastException类型的异常。
  • 小结:HashMap线程不安全,HashTable安全但是一般不会使用,如果要排序可以使用TreeMap

三.HashMap源码相关单词含义

  • size,k-v的数量,map集合元素的个数

  • initialCapacity,初始容量,默认值为16,是一个折中值,不是太小也不是太大

  • capacity,容量,桶的数量=数组的长度

  • loadFactor,装载因子=size/ capacity,用来衡量表满的程度,默认为0.75是一个经验值,不会太满,也不会太少

  • threshold,扩容阀值,当表的size超过threshold时执行扩容操作= capacity * loadFactor

  • capacity+ loadFactor共同确定了hash表扩容时机


  • 下面功能上会从以下四个方面分析

四.HashMap如何确定哈希桶数组索引位置

  • HashMap定位数组索引位置,直接决定了hash方法的离散性能。
  • 源码分析
    //⽅法⼀:static final int hash(Object key) { //jdk1.8 & jdk1.7int h;// h = key.hashCode() 为第⼀步 取hashCode值// h ^ (h >>> 16) 为第⼆步 ⾼位参与运算return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}//⽅法⼆:static int indexFor(int h, int length) { //jdk1.7的源码,jdk1.8没有这个⽅方法,但是实现原理理⼀样的return h & (length-1); //第三步 取模运算}
  • 这里的Hash算法本质上就是三步:取key的hashCode值、高位运算、取模运算

五. HashMap 的 put 方法分析

  • 步骤如图所示

  • ①判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;
  • ②根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;
  • ③判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;
  • ④判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;
  • ⑤遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;
  • ⑥插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。
  • 步骤小结:先会根据key的hashcode计算出要存储的位置,如果存储位置相同则会以链表的形式排在其后,如果链表的长度大于8则会转换成红黑树存储,默认数组长度16,加载因子0.75,如果数组存储超过其容量(12)则会自己扩容
  • jdk1.8源码分析
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;//检查是否需要初始化操作if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;//检查是否桶为空[链表为空]if ((p = tab[i = (n - 1) & hash]) == null)//直接创建一个新节点tab[i] = newNode(hash, key, value, null);else {Node<K,V> e; K k;//p是链表头节点,比较hash同时判断equals//判断key是否相同,相同则标记e插入操作的表头节点//hashif (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;//链表头节点是树结构else if (p instanceof TreeNode)//对树结构进行插入操作e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {//hash相同equals不同,执行普通的插入操作//这里的beancount为链表元素的个数for (int binCount = 0; ; ++binCount) {//到达链表尾端if ((e = p.next) == null) {//创建新节点p.next = newNode(hash, key, value, null);//连表长度满足优化要求if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}//找到一个key相同的if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;//继续迭代p = e;}}//key存在,执行更新操作if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}//修改次数++++modCount;//检查是否需要扩容if (++size > threshold)resize();//afterNodeInsertion(evict);return null;}

六.HashMap扩容机制

  • jdk1.8源码分析
/** * Initializes or doubles table size.  If null, allocates in * accord with initial capacity target held in field threshold. * Otherwise, because we are using power-of-two expansion, the * elements from each bin must either stay at same index, or move * with a power of two offset in the new table. * * @return the table */  final Node<K,V>[] resize() {  Node<K,V>[] oldTab = table;  int oldCap = (oldTab == null) ? 0 : oldTab.length;  int oldThr = threshold;  int newCap, newThr = 0;  /*如果旧表的长度不是空*/  if (oldCap > 0) {  if (oldCap >= MAXIMUM_CAPACITY) {  threshold = Integer.MAX_VALUE;  return oldTab;  }
/*把新表的长度设置为旧表长度的两倍,newCap=2*oldCap*/  else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&  oldCap >= DEFAULT_INITIAL_CAPACITY)  /*把新表的门限设置为旧表门限的两倍,newThr=oldThr*2*/  newThr = oldThr << 1; // double threshold  }  /*如果旧表的长度的是0,就是说第一次初始化表*/  else if (oldThr > 0) // initial capacity was placed in threshold  newCap = oldThr;  else {               // zero initial threshold signifies using defaults  newCap = DEFAULT_INITIAL_CAPACITY;  newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);  }  if (newThr == 0) {  float ft = (float)newCap * loadFactor;//新表长度乘以加载因子  newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?  (int)ft : Integer.MAX_VALUE);  }  threshold = newThr;  @SuppressWarnings({"rawtypes","unchecked"})
/*下面开始构造新表,初始化表中的数据*/  Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];  table = newTab;//把新表赋值给table  if (oldTab != null) {//原表不是空要把原表中数据移动到新表中      /*遍历原来的旧表*/        for (int j = 0; j < oldCap; ++j) {  Node<K,V> e;  if ((e = oldTab[j]) != null) {  oldTab[j] = null;  if (e.next == null)//说明这个node没有链表直接放在新表的e.hash & (newCap - 1)位置  newTab[e.hash & (newCap - 1)] = e;  else if (e instanceof TreeNode)  ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
/*如果e后边有链表,到这里表示e后面带着个单链表,需要遍历单链表,将每个结点重*/  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;//记录下一个结点  //新表是旧表的两倍容量,实例上就把单链表拆分为两队,  //e.hash&oldCap为偶数一队,e.hash&oldCap为奇数一对  if ((e.hash & oldCap) == 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) {//lo队不为null,放在新表原位置  loTail.next = null;  newTab[j] = loHead;  }  if (hiTail != null) {//hi队不为null,放在新表j+oldCap位置  hiTail.next = null;  newTab[j + oldCap] = hiHead;  }  }  }  }  }  return newTab;  }
  • 扩容会将原来的链表通过计算e.hash&oldCap==0分成两条链表,再将两条链表散列到新数组的不同位置上
  • 注意: 多线程情况,对hashmap进行put操作会引起resize,并可能会造成数组元素的丢失。

七.HashMap线程安全性

  • 在多线程使用场景中,应该尽量避免使用线程不安全的HashMap,而使用线程安全的ConcurrentHashMap。主要是多线程同时put时,如果同时触发了rehash操作,会导致HashMap中的链表中出现循环节点,进而使得后面get的时候,会死循环。
  • 小结:想要线程安全使用ConcurrentHashMap。


Don’t be afraid to shoot a single horse. What about being alone and brave? You can cry all the way, but you can’t be angry. You have to go through the days when nobody cares about it to welcome applause and flowers.

2020.03.01

HashMap原理分析及性能优化相关推荐

  1. webpack 原理分析与性能优化(2w字精华)

    webpack webpack 最出色的功能之一就是,除了 JavaScript,还可以通过 loader 引入任何其他类型的文件. Webpack 核心概念: Entry(入口):Webpack 执 ...

  2. 秋色园QBlog技术原理解析:性能优化篇:打印页面SQL,全局的SQL语句优化(十三)...

    文章回顾: 1: 秋色园QBlog技术原理解析:开篇:整体认识(一) --介绍整体文件夹和文件的作用 2: 秋色园QBlog技术原理解析:认识整站处理流程(二) --介绍秋色园业务处理流程 3: 秋色 ...

  3. vin-slam中调用ceres库内部代码分析与性能优化

    vin-slam中调用ceres库内部代码分析与性能优化 1,vin-slam中后端参数优化调用流程代码 2,ceres内部的求解流程(未完待续) 首先,很抱歉前几次上传的关于一些图像算法代码不全,主 ...

  4. 秋色园QBlog技术原理解析:性能优化篇:access的并发极限及超级分库分散并发方案(十六)...

    上节回顾: 上节 秋色园QBlog技术原理解析:性能优化篇:数据库文章表分表及分库减压方案(十五) 中, 介绍了 秋色园QBlog 在性能优化方面,从技术的优化手段,开始步入数据库设计优化,并从数据的 ...

  5. 秋色园QBlog技术原理解析:性能优化篇:用户和文章计数器方案(十七)

    2019独角兽企业重金招聘Python工程师标准>>> 上节概要: 上节 秋色园QBlog技术原理解析:性能优化篇:access的并发极限及分库分散并发方案(十六)  中, 介绍了 ...

  6. 秋色园QBlog技术原理解析:性能优化篇:数据库文章表分表及分库减压方案(十五)...

    文章回顾: 1: 秋色园QBlog技术原理解析:开篇:整体认识(一) --介绍整体文件夹和文件的作用 2: 秋色园QBlog技术原理解析:认识整站处理流程(二) --介绍秋色园业务处理流程 3: 秋色 ...

  7. MySQL第12天:MySQL索引优化分析之性能优化案例实践

    MySQL索引优化分析之性能优化案例实践 执行计划中各select_type含义可以看:MySQL第11天:MySQL索引优化分析之性能分析 https://weibo01.blog.csdn.net ...

  8. 多维泰勒网matlab,非线性系统多维泰勒网控制的稳定性分析及性能优化

    非线性系统多维泰勒网控制的稳定性分析及性能优化 [摘要]:非线性是自然界和工程技术中最普遍的现象,自动控制领域一直寻求能够直接对非线性系统进行设计与处理的方法,设计一种用于非线性系统控制的高性能控制器 ...

  9. Android面试题--HashMap原理分析

    目录 一.序言 二 .HashMap原理分析 二.HashMap和Hashtable区别? 一.序言 作为Android程序员,出去找工作面试,HashMap应该是最常被问到的一种数据类型.那它是怎么 ...

最新文章

  1. Android之一起玩转下拉刷新控件
  2. 有必要学python吗-学习Python有必要去培训机构吗?
  3. FASTICA独立成分分析matlab代码实现
  4. 好用的下拉第三方——nicespinner
  5. 自己做的一个小demo
  6. Vue 过滤器/watch/computed
  7. 初识ProtoBuf(3.18.1)
  8. 计算机组成原理时序电路报告,时序电路实验报告
  9. 计算机综合布线课程,综合布线工程课程教与学(教学大纲)
  10. ignite配置文件编写
  11. GPS测量定位的类型
  12. html5的canvas绘制迷宫地图
  13. ipa文件生成扫码安装二维码的方法
  14. 第六章 (数据库) 第3节 数据库高级
  15. ps制作视:_制作自己的PS4游戏:“梦想”入门
  16. solidwork 侵权 证据_企业常见版权侵权问题咨询总结
  17. android studio蓝牙低功耗,arduino ESP32 AndroidStudio BLE低功耗蓝牙 物联网
  18. Buffer(缓冲器)
  19. 【OCC学习3】OCC与VTK整合
  20. 纯CSS边框渐变动画

热门文章

  1. iphone保存html视频格式,将本地视频转成iPhone X播放格式教程
  2. 如何实时监测多个仓库?仓储行业高效管理
  3. 180天Linux小白到大神-Linux快速入门
  4. 连续两年发布白皮书,京东试图通过区块链扳回一局 |追击上市公司
  5. PHP投票系统如何防刷票
  6. 【分布式应用】消息队列之卡夫卡 + EFLFK集群部署
  7. 大学之路:大一时蠢蠢欲动,大四拉箱出校园时,你留下了多少遗憾?
  8. python导入模块错误-No module named XXX-图文解决方案
  9. path png转svg_SVG2PNG(前台和后台将SVG转换为PNG)--amcharts导出png
  10. java中模糊查询sql怎么写,mybatis模糊查询sql,