文章目录

  • 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的区别

  1. HashMap允许空值作为键和值,而哈希表不允许空。
  2. HashMap是非同步的,而Hashtable是同步的(Synchronized同步锁实现),这意味着哈希表线程安全,可以在多个线程之间共享,但是HashMap如果没有适当的同步,就不能在多个线程之间共享。
  3. 由于线程安全性和同步性,如果在单线程环境中使用,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底层原理详解相关推荐

  1. 并发编程五:java并发线程池底层原理详解和源码分析

    文章目录 java并发线程池底层原理详解和源码分析 线程和线程池性能对比 Executors创建的三种线程池分析 自定义线程池分析 线程池源码分析 继承关系 ThreadPoolExecutor源码分 ...

  2. 动态代理——拦截器——责任链——AOP面向切面编程底层原理详解(迪丽热巴版)

    目录 动态代理模式详解 前言 什么是代理模式 如何进行代理 静态代理 动态代理 JDK动态代理 CGLIB动态代理 拦截器 责任链模式 博客文章版权申明 动态代理模式详解 前言 代理模式是设计模式中非 ...

  3. 大三专科实习第一个月——HTTPS底层原理详解

    简介:脱变从现在开始,以上文章讲述的是广度问题接下来方向将是深度的问题.觉得我还可以的可以加群探讨技术QQ群:1076570504 个人学习资料库http://www.aolanghs.com/ 微信 ...

  4. HashMap put原理详解(基于jdk1.8)

    前言 本文是个人对Hashmap的一些个人见解,主要通过使用hashmap put的一些代码来阐述其底层实现原理,在面试中也会经常会用到,如有不对的地方望大家指正. (1)先描述一下hashmap的一 ...

  5. 镜像底层原理详解和基于Docker file创建镜像

    目录 一.镜像底层原理 1.联合文件系统(UnionFS) 2.镜像加载原理 3.为什么Docker里的centos的大小才200M? 二.Dockerfile 1.简介 2.Dockerfile操作 ...

  6. Spring Boot底层原理详解及整合

    Spring Boot框架 通过Spring Boot 可以构建一个基于Spring框架的Java Application,简化配置,自动装配,开箱即用 JavaConfiguration用Java类 ...

  7. 【你好面试官】008 Java内存模型指volatile底层原理详解、多处理器原子操作实现原理

    微信公众号:你好面试官 这里没有碎片化的知识,只有完整的知识体系. 专注于系统全面的知识点讲解,面试题目解析; 如果你觉得文章对你有帮助,欢迎关注.分享.赞赏. ###前言 二蛋几天没有收到面试通知, ...

  8. 大三专科实习第一个月——Socket底层原理详解与应用

    简介:脱变从现在开始,以上文章讲述的是广度问题接下来方向将是深度的问题.觉得我还可以的可以加群探讨技术QQ群:1076570504 个人学习资料库http://www.aolanghs.com/ 微信 ...

  9. java面试题:voliate底层原理——详解

    1. voliate底层原理 1.1 voliate变量的特点 可见性: 当一个线程修改了声明为volatile变量的值,新值对于其他要读该变量的线程来说是立即可见的. 有序性: volatile变量 ...

  10. mysql索引失效_MySQL索引失效的底层原理详解,终于有人讲清楚了

    前言 吊打面试官又来啦,今天我们讲讲MySQL索引为什么会失效,很多文章和培训机构的教程,都只会告诉你,在什么情况下索引会失效. 比如:没遵循最佳左前缀法则.范围查询的右边会失效.like查询用不到索 ...

最新文章

  1. angularjs 学期下拉列表指令
  2. Unix/Linux操作系统中如何在sqlplus/rman中使用方向键
  3. python常用模块(一)
  4. 两个多精度十进制数加法程序设计_Fortran程序设计基础
  5. 我会永远永远的爱你,直到你不爱我的那一天
  6. 精准高效估计多人3D姿态,美图北航分布感知式单阶段模型(CVPR 2022)
  7. 续航超600km新能源汽车扎堆发布,零部件供应商“放血”,这届车展都拼了...
  8. c语言ftell函数,C语言中ftell函数的使用方法
  9. 心理学推荐书籍——《色眼识人》
  10. 持续更新 iText in Action 2nd Edition中文版 个人翻译
  11. 一只青蛙一次可以跳上1级台阶也可以跳上2级求该青蛙跳上一个n级的台阶总共有多少种跳法?
  12. android穿山甲主题冲突,Flutter 接头条穿山甲广告 Android 总述篇
  13. C# 调用打印机 打印 Excel
  14. /proc/sysrq-trigger文件的功能
  15. 查询 maven 依赖 的最新版本号
  16. 电脑超时空保卫者——光华反病毒软件(转)
  17. 2022好看的校园表白墙程序源码Ver2.0
  18. 使用python,目前最全的Python使用手册
  19. 黑客流水线作业 自动吸取大量黑金
  20. java+selenium——查找定位元素,elements复数定位(driver.findElementsByClassName(mnav);)002...

热门文章

  1. 自学mysql还是sql好_如何自学SQL?
  2. linux redis-连接命令
  3. Echarts2的使用——绘制中国地图
  4. 阿里矢量图标及其引入方式
  5. 流加密,一次性密码本的原理,简介,事例以及攻击方式
  6. 通俗易懂的TextCNN
  7. 步进电机为何需要驱动器
  8. Devexpress WPF教程
  9. 空洞卷积(Dilated Convolution)简介
  10. JAVA通过FDFS上传以及加载文件原理简析