HashMap源码分析——Java全栈知识(8)

jdk1.7和jdk1.8的HashMap的原理有一点出入我们就分开讲解:

1、JDK1.7中的HashMap

JDK1.7中的HashMap是通过数组加链表的方式存储数据。他的底层维护了一个Entry数组,通过哈希函数的计算出来哈希值,将待填数据根据计算出来的哈希值填入到对应位置。如果发生哈希冲突就以链表的方式存储在对应位置。形成链式结构。

2、JDK1.8中的HashMap

JDK1.8中的HashMap是数组+链表+红黑树的方式实现,底层维护了一个Node数组,当我们哈希冲突比较严重的时候,并且数组长度大于64(如果不足64优先扩容数组),链表长度大于等于8的时候,HashMap会将该链表转化为红黑树。值得注意的是如果此时我们减少红黑树中的节点,当节点数量小于等于6的时候,HashMap才会将红黑树转化位链表。

红黑树的好处

当哈希冲突比较严重的时候,也就是链表过长的时候,我们查找元素的时间复杂度就变成了O(n),也就是链表查找元素的时间复杂度,到那时我们哈希表就体现不出优势了。此时我们将链表转化位红黑树,而红黑树的查找时间复杂度是O(logN),就优化了HashMap的查找效率/

转化位红黑树的阈值为什么是6和8

因为如果我们频繁添加删除元素,使得链表的长度恰好是7左右,如果转化阈值是7则会频繁的转化位链表和红黑树,从而占用大量的CPU资源,设置为6和8就可以很好的避免此类情况。

哈希负载因子

默认的哈希负载因子是0.75,也就是如果当前元素/数组大小>=0.75,数组就必须要进行扩容。这个负载因子可以在构造函数中传入进行自定义。

3、HashMap的构造函数

    public HashMap(int initialCapacity, float loadFactor) {if (initialCapacity < 0)throw new IllegalArgumentException("Illegal initial capacity: " +initialCapacity);if (initialCapacity > MAXIMUM_CAPACITY)initialCapacity = MAXIMUM_CAPACITY;if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new IllegalArgumentException("Illegal load factor: " +loadFactor);this.loadFactor = loadFactor;this.threshold = tableSizeFor(initialCapacity);}/*** Constructs an empty <tt>HashMap</tt> with the specified initial* capacity and the default load factor (0.75).** @param  initialCapacity the initial capacity.* @throws IllegalArgumentException if the initial capacity is negative.*/public HashMap(int initialCapacity) {this(initialCapacity, DEFAULT_LOAD_FACTOR);}/*** Constructs an empty <tt>HashMap</tt> with the default initial capacity* (16) and the default load factor (0.75).*/public HashMap() {this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted}/*** Constructs a new <tt>HashMap</tt> with the same mappings as the* specified <tt>Map</tt>.  The <tt>HashMap</tt> is created with* default load factor (0.75) and an initial capacity sufficient to* hold the mappings in the specified <tt>Map</tt>.** @param   m the map whose mappings are to be placed in this map* @throws  NullPointerException if the specified map is null*/public HashMap(Map<? extends K, ? extends V> m) {this.loadFactor = DEFAULT_LOAD_FACTOR;putMapEntries(m, false);}

HashMap有如上四个构造函数,参数分别是空,initialCapacity,initialCapacity和loadFactor,Map<? extends K, ? extends V> m

  • initialCapacity:指定HashMap的大小。
  • loadFactor:哈希负载因子。
  • Map<? extends K, ? extends V> m:Map集合。

值得注意的是如果我们不指定数组大小,那么HashMap的默认大小就是16。如果我们指定大小,例如33,那么会调用如下函数:

    /*** Returns a power of two size for the given target capacity.*/static final int tableSizeFor(int cap) {int n = cap - 1;n |= n >>> 1;n |= n >>> 2;n |= n >>> 4;n |= n >>> 8;n |= n >>> 16;return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;}

我第一眼看到这个函数直接一句卧槽,这都什么呀。我们仔细算一下就知道,这个函数会返回比我们传入的值更大的最近的一个2进制值,例如传入33,返回的就是64.

也就是无论我们指定的数是几HashMap都会以2进制的大小进行初始化。这个跟HashMap的扩容机制有关,HashMap是以2倍的方式进行扩容

4、HashMap的put的过程

public V put(K key, V value) {return putVal(hash(key), key, value, false, true);
}// 第四个参数 onlyIfAbsent 如果是 true,那么只有在不存在该 key 时才会进行 put 操作
// 第五个参数 evict 我们这里不关心
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;// 第一次 put 值的时候,会触发下面的 resize(),类似 java7 的第一次 put 也要初始化数组长度// 第一次 resize 和后续的扩容有些不一样,因为这次是数组从 null 初始化到默认的 16 或自定义的初始容量if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;// 找到具体的数组下标,如果此位置没有值,那么直接初始化一下 Node 并放置在这个位置就可以了if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);else {// 数组该位置有数据Node<K,V> e; K k;// 首先,判断该位置的第一个数据和我们要插入的数据,key 是不是"相等",如果是,取出这个节点if (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 {// 到这里,说明数组该位置上是一个链表for (int binCount = 0; ; ++binCount) {// 插入到链表的最后面(Java7 是插入到链表的最前面)if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);// TREEIFY_THRESHOLD 为 8,所以,如果新插入的值是链表中的第 8 个// 会触发下面的 treeifyBin,也就是将链表转换为红黑树if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}// 如果在该链表中找到了"相等"的 key(== 或 equals)if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))// 此时 break,那么 e 为链表中[与要插入的新值的 key "相等"]的 nodebreak;p = e;}}// e!=null 说明存在旧值的key与要插入的key"相等"// 对于我们分析的put操作,下面这个 if 其实就是进行 "值覆盖",然后返回旧值if (e != null) {V oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}++modCount;// 如果 HashMap 由于新插入这个值导致 size 已经超过了阈值,需要进行扩容if (++size > threshold)resize();afterNodeInsertion(evict);return null;
}
  1. 当我们调用Put方法的时候,会先检查数组是否为空,如果为空则初始化数组。
  2. 判断key是否为null,如果是null则会把对应的value值放到数组下标为0位置。
  3. 根据key算出来hash值,找到对应位置查找,如果有该key,则覆盖原来的value值
  4. 如果没有该key(新值),则把key和value以Node的形式存放到当前位置,jdk1.7采用头插法,jdk1.8采用尾插法。
  5. 判断是否需要转化为红黑树,也就是链表长度是否到达8.
  6. 判断是否扩容。

值得注意的是JDK1.7是先扩容再插入,1.8是先插入再扩容。

5、HashMap的扩容原理

HashMap是默认以两倍的大小进行扩容。

例如我们的HashMap的数组从16扩容到32,如图所示

链表中的节点值会有序的分布在新链表的 i 位置和 i+16 的位置。省去了重新计算hash值的过程,而且分布的更加均匀。

6、HashMap线程安全吗?

HashMap是一个线程不安全的集合,如果两个线程同时操作Map扩容,就会产生循环链表的现象。

如何得到一个线程安全的Map

  1. 使用Collections工具类,将线程不安全的Map包装成线程安全的Map;
  2. 使用java.util.concurrent包下的Map,如ConcurrentHashMap;
  3. 不建议使用Hashtable,虽然Hashtable是线程安全的,但是性能较差。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/127633.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

何恺明:在cuhk解答科研问题

文章目录 1. 大模型的未来:数据效益是个问题2. 未来三年研究重点:视觉自监督学习3. 选择课题的标准:好奇心和热情4. AI将成为几乎所有事情的基础工具5. 用疑问解答AI模型可解释性问题AcknowledgementReference何恺明最近在香港中文大学参加一个讲座过程中所述: 1. 大模型的…

循环神经网络(RNN)与长短期记忆网络(LSTM)

前言&#xff1a; 通过前面的学习&#xff0c;我们以BP神经网络为基础&#xff0c;认识到了损失函数&#xff0c;激活函数&#xff0c;以及梯度下降法的原理&#xff1b;而后学习了卷积神经网络&#xff0c;知道图像识别是如何实现的。今天这篇文章&#xff0c;讲述的就是计算机…

【数据结构】树形结构所有路径复原为链表

目录 1. 树形结构可视化 2. 树形结构转为链表 此目标是要还原树形结构的所有路径。树形结构是一种常见的数据结构&#xff0c;它表示元素之间层次关系。在树形结构中&#xff0c;每个节点可能拥有一个或多个子节点&#xff0c;形成了一个分层的结构。为了还原树形结构的路径&…

自定义微信公众号源码系统 带完整搭建教程

在我们现在的互联网时代&#xff0c;越来越多的人开始尝试通过微信公众号来传播自己的信息、提供服务或者进行营销。但是&#xff0c;市面上的微信公众号平台往往功能有限&#xff0c;不能满足部分用户的需求。这时&#xff0c;自定义微信公众号源码系统就成为了解决问题的最佳…

AUC的解释,以及其他指标

因为做比赛&#xff0c;又开始看一些关于评价指标的问题&#xff0c;下面这篇帖子不错&#xff0c;贴过来吧。 参考 ROC曲线画法。 https://www.zhihu.com/question/22844912

Http代理与socks5代理有何区别?如何选择?(二)

上篇文章我们基本分别了解了http代理与socks5代理的定义与优缺点&#xff0c;接下来我们继续来了解http代理与socks5代理之间的比较与区别。 一、两者的比较 1、功能比较 HTTP代理专门用于Web流量&#xff0c;并在处理HTTP和HTTPS协议方面非常高效。它们可以修改正在传输的数…

怎么扫码下载视频?一招生成可下载的视频二维码

怎么做可以下载视频的二维码呢&#xff1f;当我们用二维码来分享时&#xff0c;很多视频二维码生成器制作的二维码都只有展示功能&#xff0c;无法让扫码者选择下载视频&#xff0c;那么这个问题有什么解决方法呢&#xff1f;对于有这方面需求的小伙伴&#xff0c;小编可以给大…

Tomcat下载地址(详细)

Apache Tomcat - Apache Tomcat 8 Software Downloadshttps://tomcat.apache.org/download-80.cgi2.找到Archives 3.选择下载的把版本 4.选择具体下载那个版本 5. 6.一般选择tar.gz结尾的压缩包

安全狗亮相厦门市工信领域数据安全宣贯培训会

10月31日&#xff0c;厦门市工业和信息化局&#xff08;市大数据管理局&#xff09;顺利举办厦门市工信领域数据安全宣贯培训。 作为国内云原生安全领导厂商&#xff0c;安全狗以厦门市工业领域数据安全管理支撑单位身份受邀出席此次会议。 据悉&#xff0c;此次活动旨在贯彻…

Java 设计模式——命令模式

目录 1.概述2.结构3.案例实现3.1.命令接口3.2.具体命令3.3.接受者3.4.调用者3.5.测试 4.优缺点5.使用场景6.JDK 源码解析——Runnable 1.概述 &#xff08;1&#xff09;日常生活中&#xff0c;我们出去吃饭都会遇到下面的场景&#xff1a; &#xff08;2&#xff09;命令模…

Unity AssetBundle批量打包、加载(场景、Prefab)完整流程

目录 1、文章介绍 2、具体思路和写法 &#xff08;1&#xff09;AB包的打包 &#xff08;2&#xff09;AB包的加载 &#xff08;3&#xff09;AB包卸载 3、结语 1、文章介绍 本篇博客主要起记录和学习作用&#xff0c;简单的介绍一下AB包批量的打包和加载AB包的方式&…

【HeidiSql_01】python在heidisql当中创建新表的注意事项

python在heidisql当中创建新表的注意事项 假设你已经在python当中弄好了所有的结果&#xff0c;并且保存在df_all这个dataframe当中&#xff0c;然后要将其导入数据库当中并创建一张新的表进行保存。 # 构建数据库连接,将merged_df写回数据库 from sqlalchemy import create_e…

Spring-创建非懒加载的单例Bean源码

补充&#xff1a;关于扫描的逻辑 /*** Scan the class path for candidate components.* param basePackage the package to check for annotated classes* return a corresponding Set of autodetected bean definitions*/ public Set<BeanDefinition> findCandidateCo…

【python】爬取豆瓣电影排行榜TOP250存储到CSV文件中

一、导入必要的模块&#xff1a; 代码首先导入了需要使用的模块&#xff1a;requests、lxml和csv。 import requests from lxml import etree import csv 如果出现模块报错 进入控制台输入&#xff1a;建议使用国内镜像源 pip install 模块名称 -i https://mirrors.aliyun.co…

【k8s】pod详解

一、Pod介绍 1、Pod的基础概念 Pod是kubernetes中最小的资源管理组件&#xff0c;Pod也是最小化运行容器化应用的资源对象&#xff0c;一个pod代表着集群中运行的一个进程。kubernetes中其它大多数组件都是围绕着pod来进行支持和扩展pod功能的。 例如&#xff0c;用于管理po…

构建强大的Web应用之Django详解

引言&#xff1a; Django是一个功能强大且灵活的Python Web框架&#xff0c;它提供了一套完整的工具和功能&#xff0c;帮助开发者快速构建高效的Web应用。本篇文章将带您逐步了解Django的基本概念和使用方法&#xff0c;并通过实际的代码案例&#xff0c;帮助您从零开始构建自…

Collction的List方法,list特有方法,遍历方式,迭代器选择

[to] list特有方法 //插入指定元素//list.add(1,"ddd");//System.out.println(list);//[aaa, ddd, bbb, ccc]//这个表示在一索引的位置插入ddd//他会把原来一索引位置的元素往后移动一位在添加//删除指定元素//String remove list.remove(1);//System.out.println(…

常用排序算法

目录 直接插入排序 希尔排序 ​编辑 选择排序 堆排序 冒泡排序 快速排序 hoare版 挖坑法 前后指针法 非递归 归并排序 非递归 计数排序 直接插入排序 直接插入排序跟依次模扑克牌一样&#xff0c;将最后一张牌依次与前面的牌比较&#xff0c;最后将牌插入到指定位…

创建ABAP数据库表和ABAP字典对象-使用已存在的数据元素增加城市字段04

基于内置域增加一个字段 1.在编辑器中&#xff0c;输入字段的名称&#xff0c;后跟冒号:city:。暂时忽略这个错误。2. 输入/MOC/C并使用自动补全(**Ctrl空格**)&#xff0c;输入类型。3. 然后添加一个分号:city: /moc/city;4.在SAP GUI中查看&#xff0c;字段已经新增

MATLAB野外观测站生态气象数据处理分析实践应用

1.基于MATLAB语言 2.以实践案例为主&#xff0c;提供所有代码 3.原理与操作结合 4.布置作业&#xff0c;答疑与拓展 示意图&#xff1a; 以野外观测站高频时序生态气象数据为例&#xff0c;基于MATLAB开展上机操作&#xff1a; 1.不同生态气象要素文件的数据读写与批处理实现 …