HashMap底层原理详解
文章目录
- HashMap
- HashMap是什么
- HashMap的底层
- HashMap存储结构
- jdk1.8之后为什么用红黑树
- HashMap的几个重要参数:
- HashMap基本操作源码分析
- put(k,v)方法解析
- hash()函数解析
- get(k)方法解析
- HashMap与HashTable的区别
- ConcurrentHashMap与Hansmap的区别
- jdk1.7
- jdk1.8
HashMap
HashMap是什么
HashMap继承了AbstractMap类,并且通过基于哈希表的 Map接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和null键。
HashMap的底层
HashMap存储结构
HashMap底层采用哈希表结构(JDK1.8后为数组+链表+红黑树)实现,结合了数组和链表的优点:
- 数组优点:通过数组下标可以快速实现对数组元素的访问,效率极高;
- 链表优点:插入或删除数据不需要移动元素,只需修改节点引用,效率极高。
jdk1.8之后为什么用红黑树
- 在JDK1.7及之前,是用数组加链表的方式存储的,当链表的长度特别长的时候,查询效率将直线下降,查询的时间复杂度为 O(n)。jdk1.8之后,在链表节点数超过阈值后,会将链表转化为红黑树(高度平衡二叉树),使查询的时间复杂度降为O(logn)。
- 如果用普通的二叉树,可能会出现树的大多数节点都出现在一边,极端情况下形成一条链表,查询的时间复杂度很高。
HashMap的几个重要参数:
// 默认初始化容量为 16 (必须为2的n次幂) static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16//最大容量为 2^30static final int MAXIMUM_CAPACITY = 1 << 30;//默认的加载因子0.75,乘以数组容量得到的值,用来表示元素个数达到多少时,需要扩容。//为什么设置 0.75 这个值呢,简单来说就是时间和空间的权衡。//若小于0.75如0.5,则数组长度达到一半大小就需要扩容,空间使用率大大降低,//若大于0.75如0.8,则会增大hash冲突的概率,影响查询效率。static final float DEFAULT_LOAD_FACTOR = 0.75f;//链表长度过长时,会有一个阈值,超过这个阈值8就会转化为红黑树static final int TREEIFY_THRESHOLD = 8;//当红黑树上的元素个数,减少到6个时,就退化为链表static final int UNTREEIFY_THRESHOLD = 6;//链表转化为红黑树,除了有阈值的限制,还有另外一个限制,需要数组容量至少达到64,才会树化。//这是为了避免,数组扩容和树化阈值之间的冲突。static final int MIN_TREEIFY_CAPACITY = 64;
HashMap基本操作源码分析
put(k,v)方法解析
//put方法首先调用一个hash方法,计算出k的hash值//将k的hash值,key,value传进来public V put(K key, V value) {return putVal(hash(key), key, value, false, true);}//这里onlyIfAbsent如果为true,表明不能修改已经存在的值,因此我们传入falsefinal V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;//判断table是否为空,如果空的话,会先调用resize扩容if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;//根据当前key的hash值找到它在数组中的下标,判断当前下标位置是否已经存在元素,//若没有,则把key、value包装成Node节点,直接添加到此位置。//[i = (n - 1) & hash]相当于取模运算的位运算形式if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);else {//如果当前位置已经有元素了(产生了哈希碰撞),分为三种情况。Node<K,V> e; K k;//1.当前位置元素的hash值等于传过来的hash,并且他们的key值也相等,//则把p赋值给e,后续需要做值的覆盖处理if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;//2.如果当前是红黑树结构,则把它加入到红黑树 else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);//3.此位置已存在元素,并且是普通链表结构,则采用尾插法,把新节点加入到链表尾部else {for (int binCount = 0; ; ++binCount) {//如果头结点的下一个节点为空,则插入新节点if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);//如果在插入的过程中,链表长度超过了8,则转化为红黑树if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);//插入成功之后,跳出循环到nextbreak;}//若在链表中找到了相同key的话,直接退出循环,跳转到nextif (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}// next// 此时新值要覆盖旧值if (e != null) { V oldValue = e.value;//用新值替换旧值,并返回旧值。if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}++modCount;//如果当前数组中的元素个数超过阈值,则扩容if (++size > threshold)resize();afterNodeInsertion(evict);return null;}
hash()函数解析
先判断key是否为空,若为空则返回0。这也说明了hashMap是支持key传 null 的。若非空,则先计算key的hashCode值,赋值给h,然后把h右移16位,并与原来的h进行异或处理。
static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}
Q: 为什么要用h>>>16,并和h异或这种方法计算hash值?
A: 这样做相当于把高16位值和当前h的低16位进行了混合,这样可以尽量保留高16位的特征,从而降低哈希碰撞的概率。
get(k)方法解析
public V get(Object key) {Node<K,V> e;//如果节点为空,则返回null,否则返回节点的value。这也说明,hashMap是支持value为null的。return (e = getNode(hash(key), key)) == null ? null : e.value;}//final Node<K,V> getNode(int hash, Object key) {Node<K,V>[] tab; Node<K,V> first, e; int n; K k;//首先要确保数组不能为空,然后取到当前hash值计算出来的下标位置的第一个元素if ((tab = table) != null && (n = tab.length) > 0 &&(first = tab[(n - 1) & hash]) != null) {//若hash值和key都相等,直接返回if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k))))return first;//如果不是的话,就遍历当前链表(或红黑树)if ((e = first.next) != null) { //如果是红黑树结构,则找到当前key所在的节点位置if (first instanceof TreeNode)return ((TreeNode<K,V>)first).getTreeNode(hash, key);//如果是普通链表,则向后遍历查找,直到找到或者遍历到链表末尾为止。do {if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))return e;} while ((e = e.next) != null);}}//都没找到,返回nullreturn null;}
HashMap与HashTable的区别
- HashMap允许空值作为键和值,而哈希表不允许空。
- HashMap是非同步的,而Hashtable是同步的(Synchronized同步锁实现),这意味着哈希表线程安全,可以在多个线程之间共享,但是HashMap如果没有适当的同步,就不能在多个线程之间共享。
- 由于线程安全性和同步性,如果在单线程环境中使用,Hashtable比HashMap慢得多。不需要同步,并且HashMap仅由一个线程使用,那么它的性能将优于Java中的Hashtable。
ConcurrentHashMap与Hansmap的区别
HashMap是线程不安全的,因此为了解决线程安全问题,提出了两个类:HashTable和CurrentHashMap。
HashTable相关操作都是对方法加synchronized的大锁,效率比较低。ConcurrentHashMap避免了对全局加锁改成了局部加锁操作,这样就极大地提高了并发环境下的操作速度。
jdk1.7
- 在JDK1.7中ConcurrentHashMap采用了数组+Segment+分段锁的方式实现。ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表,同时又是一个ReentrantLock(Segment继承了ReentrantLock)。
- ConcurrentHashMap使用分段锁技术,将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问,能够实现真正的并发访问。
- ConcurrentHashMap定位一个元素的过程需要进行两次Hash操作。Hash的过程要比普通的HashMap要长。
jdk1.8
- 在JDK1.8中ConcurrentHashMap,数据结构上,首先整体上是数组+链表+红黑树的结构与HashMap保持一致,其次取消了Segment分段锁的数据结构,取而代之的是Node,Node的value和next都是由volatile关键字进行修饰,可以保证可见性。将细化的粒度从段进一步降低到节点。线程安全实现上,采用CAS+Synchronized替代Segment分段锁。
HashMap底层原理详解相关推荐
- 并发编程五:java并发线程池底层原理详解和源码分析
文章目录 java并发线程池底层原理详解和源码分析 线程和线程池性能对比 Executors创建的三种线程池分析 自定义线程池分析 线程池源码分析 继承关系 ThreadPoolExecutor源码分 ...
- 动态代理——拦截器——责任链——AOP面向切面编程底层原理详解(迪丽热巴版)
目录 动态代理模式详解 前言 什么是代理模式 如何进行代理 静态代理 动态代理 JDK动态代理 CGLIB动态代理 拦截器 责任链模式 博客文章版权申明 动态代理模式详解 前言 代理模式是设计模式中非 ...
- 大三专科实习第一个月——HTTPS底层原理详解
简介:脱变从现在开始,以上文章讲述的是广度问题接下来方向将是深度的问题.觉得我还可以的可以加群探讨技术QQ群:1076570504 个人学习资料库http://www.aolanghs.com/ 微信 ...
- HashMap put原理详解(基于jdk1.8)
前言 本文是个人对Hashmap的一些个人见解,主要通过使用hashmap put的一些代码来阐述其底层实现原理,在面试中也会经常会用到,如有不对的地方望大家指正. (1)先描述一下hashmap的一 ...
- 镜像底层原理详解和基于Docker file创建镜像
目录 一.镜像底层原理 1.联合文件系统(UnionFS) 2.镜像加载原理 3.为什么Docker里的centos的大小才200M? 二.Dockerfile 1.简介 2.Dockerfile操作 ...
- Spring Boot底层原理详解及整合
Spring Boot框架 通过Spring Boot 可以构建一个基于Spring框架的Java Application,简化配置,自动装配,开箱即用 JavaConfiguration用Java类 ...
- 【你好面试官】008 Java内存模型指volatile底层原理详解、多处理器原子操作实现原理
微信公众号:你好面试官 这里没有碎片化的知识,只有完整的知识体系. 专注于系统全面的知识点讲解,面试题目解析; 如果你觉得文章对你有帮助,欢迎关注.分享.赞赏. ###前言 二蛋几天没有收到面试通知, ...
- 大三专科实习第一个月——Socket底层原理详解与应用
简介:脱变从现在开始,以上文章讲述的是广度问题接下来方向将是深度的问题.觉得我还可以的可以加群探讨技术QQ群:1076570504 个人学习资料库http://www.aolanghs.com/ 微信 ...
- java面试题:voliate底层原理——详解
1. voliate底层原理 1.1 voliate变量的特点 可见性: 当一个线程修改了声明为volatile变量的值,新值对于其他要读该变量的线程来说是立即可见的. 有序性: volatile变量 ...
- mysql索引失效_MySQL索引失效的底层原理详解,终于有人讲清楚了
前言 吊打面试官又来啦,今天我们讲讲MySQL索引为什么会失效,很多文章和培训机构的教程,都只会告诉你,在什么情况下索引会失效. 比如:没遵循最佳左前缀法则.范围查询的右边会失效.like查询用不到索 ...
最新文章
- angularjs 学期下拉列表指令
- Unix/Linux操作系统中如何在sqlplus/rman中使用方向键
- python常用模块(一)
- 两个多精度十进制数加法程序设计_Fortran程序设计基础
- 我会永远永远的爱你,直到你不爱我的那一天
- 精准高效估计多人3D姿态,美图北航分布感知式单阶段模型(CVPR 2022)
- 续航超600km新能源汽车扎堆发布,零部件供应商“放血”,这届车展都拼了...
- c语言ftell函数,C语言中ftell函数的使用方法
- 心理学推荐书籍——《色眼识人》
- 持续更新 iText in Action 2nd Edition中文版 个人翻译
- 一只青蛙一次可以跳上1级台阶也可以跳上2级求该青蛙跳上一个n级的台阶总共有多少种跳法?
- android穿山甲主题冲突,Flutter 接头条穿山甲广告 Android 总述篇
- C# 调用打印机 打印 Excel
- /proc/sysrq-trigger文件的功能
- 查询 maven 依赖 的最新版本号
- 电脑超时空保卫者——光华反病毒软件(转)
- 2022好看的校园表白墙程序源码Ver2.0
- 使用python,目前最全的Python使用手册
- 黑客流水线作业 自动吸取大量黑金
- java+selenium——查找定位元素,elements复数定位(driver.findElementsByClassName(mnav);)002...