java 中的HashMap的底层实现和元素添加流程

目录
  • HashMap 底层实现
    • HashMap 插入流程
    • 为什么要将链表转红黑树?
    • 哈希算法实现
  • 总结

前言:

HashMap 是使用频率最高的数据类型之一,同时也是面试必问的问题之一,尤其是它的底层实现原理,既是常见的面试题又是理解 HashMap 的基石,所以重要程度不言而喻。

HashMap 底层实现

HashMap 在 JDK 1.7 和 JDK 1.8 的底层实现是不一样的,在 JDK 1.7 中,HashMap 使用的是数组 + 链表实现的,而 JDK 1.8 中使用的是数组 + 链表或红黑树实现的

HashMap 在 JDK 1.7 中的实现如下图所示: 

 HashMap 在 JDK 1.8 中的实现如下图所示: 

我们本文重点来学习主流版本 JDK 1.8 中的 HashMap。HashMap 中每个元素称之为一个哈希桶(bucket),

哈希桶包含的内容有 4 个:

  • hash 值
  • key
  • value
  • next(下一个节点)

HashMap 插入流程

HashMap 元素新增的实现源码如下(下文源码都是基于主流版本 JDK 1.8):

public V put(K key, V value) {
    // 对 key 进行哈希操作
    return putVal(hash(key), key, value, false, true);
}
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;
    // 根据 key 的哈希值计算出要插入的数组索引 i
    if ((p = tab[i = (n - 1) & hash]) == null)
        // 如果 table[i] 等于 null,则直接插入
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        // 如果 key 已经存在了,直接覆盖 value
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        // 如果 key 不存在,判断是否为红黑树
        else if (p instanceof TreeNode)
            // 红黑树直接插入键值对
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            // 为链表结构,循环准备插入
            for (int binCount = 0; ; ++binCount) {
                // 下一个元素为空时
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    // 转换为红黑树进行处理
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                //  key 已经存在直接覆盖 value
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    // 超过最大容量,扩容
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

上述的源码都添加了相应的代码注释,简单来说 HashMap 的元素添加流程是,先将 key 值进行 hash 得到哈希值,根据哈希值得到元素位置,判断元素位置是否为空,如果为空直接插入,不为空判断是否为红黑树,如果是红黑树则直接插入,否则判断链表是否大于 8,且数组长度大于 64,如果满足这两个条件则把链表转成红黑树,然后插入元素,如果不满足这两个条件中的任意一个,则遍历链表进行插入,

它的执行流程如下图所示: 

为什么要将链表转红黑树?

JDK 1.8 中引入了新的数据结构红黑树来实现 HashMap,主要是出于性能的考量。因为链表超过一定长度之后查询效率就会很低,它的时间复杂度是 O(n),而红黑树的时间复杂度是 O(logn),因此引入红黑树可以加快 HashMap 在数据量比较大的情况下的查询效率。

哈希算法实现

HashMap 的哈希算法实现源码如下:

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

其中,key.hashCode() 是 Java 中自带的 hashCode() 方法,返回一个 int 类型的散列值,后面 hashCode 再右移 16 位,正好是 32bit 的一半,与自己本身做异或操作(相同为 0,不同为 1),主要是为了混合哈希值的高位和低位,增加低位的随机性,这样就实现了 HashMap 的哈希算法。

总结

HashMap 在 JDK 1.7 时,使用的是数组 + 链表实现的,而在 JDK 1.8 时,使用的是数组 + 链表或红黑树的方式来实现的,JDK 1.8 之所以引入红黑树主要是出于性能方面的考虑。HashMap 在插入时,会判断当前链表的长度是否大于 8 且数组的长度大于 64,如果满足这两个条件就会把链表转成红黑树再进行插入,否则就是遍历链表插入。

到此这篇关于java 中的HashMap的底层实现和元素添加流程的文章就介绍到这了,更多相关Java中的HashMap内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java LinkedHashMap 底层实现原理分析

    在实现上,LinkedHashMap很多方法直接继承自HashMap,仅为维护双向链表覆写了部分方法.所以,要看懂 LinkedHashMap 的源码,需要先看懂 HashMap 的源码. 默认情况下,LinkedHashMap的迭代顺序是按照插入节点的顺序.也可以通过改变accessOrder参数的值,使得其遍历顺序按照访问顺序输出. 这里我们只讨论LinkedHashMap和HashMap的不同之处,LinkedHashMap的其他操作和特性具体请参考HashMap 我们先来看下两者的区别:

  • Java中HashMap集合的常用方法详解

    目录 public Object clone() 总结 public Object clone() 返回hashMap集合的副本 其余的方法都是实现Map集合的 //www.jb51.net/article/227296.htm 总结 本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

  • JAVA进阶之HashMap底层实现解析

    首先我们来通过下面的图看看JDK1.7时代的HashMap是如何通过数组+链表的形式进行值储存的. 由图中的描述可以清楚地看出来,当数组第一次被定义并且第一次被赋值的时候,这个时候的操作很简单,就是将这个值赋值到我们的table数组上面去.这个操作完成以后,然后我们进行二次put: 如图左下角描述所示的情况,当数组table下标出现了相等的情况的时候,此时此刻还是将肝铁侠2的值赋值给tablle[i]的,这里讲述的是JDK1.7版本下HashMap中插入的头插法,而JDK1.8版本中是用的尾插法

  • java中hashmap的底层数据结构与实现原理

    目录 Hash结构 HashMap实现原理 为何HashMap的数组长度一定是2的次幂? 重写equals方法需同时重写hashCode方法 总结 Hash结构 HashMap根据名称可知,其实现方法与Hash表有密切关系.在讨论哈希表之前,我们先大概了解下其他数据结构在新增,查找等基础操作执行性能. 数组:采用一段连续的存储单元来存储数据.对于指定下标的查找,时间复杂度为O(1):通过给定值进行查找,需要遍历数组,逐一比对给定关键字和数组元素,时间复杂度为O(n),当然,对于有序数组,则可采用

  • Java中ConcurrentHashMap是如何实现线程安全

    目录 语法: ConcurrentHashmap 的需要: 如何使 ConcurrentHashMap 线程安全成为可能? Hashtable.Hashmap.ConcurrentHashmap的区别 ConcurrentHashMap是一个哈希表,支持检索的全并发和更新的高预期并发.此类遵循与 Hashtable 相同的功能规范,并包含 Hashtable 的所有方法.ConcurrentHashMap 位于 java.util.Concurrent 包中. 语法: public class

  • Java中HashMap与String字符串互转的问题解决

    目录 背景: 原因: 背景: 当我们有需求将HashMap转为Json格式的String时,切记不要使用HashMap的toString()方法,需要使用FastJson/Gson将HashMap转为String.如果使用toString()方法进行转换时,是无法将字符串再转为HashMap的.它只会出现序列化报错: demo代码: HashMap<String, String> dataMap = new HashMap<>(4); dataMap.put("key1&

  • java 中的HashMap的底层实现和元素添加流程

    目录 HashMap 底层实现 HashMap 插入流程 为什么要将链表转红黑树? 哈希算法实现 总结 前言: HashMap 是使用频率最高的数据类型之一,同时也是面试必问的问题之一,尤其是它的底层实现原理,既是常见的面试题又是理解 HashMap 的基石,所以重要程度不言而喻. HashMap 底层实现 HashMap 在 JDK 1.7 和 JDK 1.8 的底层实现是不一样的,在 JDK 1.7 中,HashMap 使用的是数组 + 链表实现的,而 JDK 1.8 中使用的是数组 + 链

  • 深入理解Java中的HashMap

    一.HashMap的结构图示 ​本文主要说的是jdk1.8版本中的实现.而1.8中HashMap是数组+链表+红黑树实现的,大概如下图所示.后面还是主要介绍Hash Map中主要的一些成员以及方法原理. ​那么上述图示中的结点Node具体类型是什么,源码如下.Node是HashMap的内部类,实现了Map.Entery接口,主要就是存放我们put方法所添加的元素.其中的next就表示这可以构成一个单向链表,这主要是通过链地址法解决发生hash冲突问题.而当桶中的元素个数超过阈值的时候就换转为红黑

  • Java中的HashMap为什么会产生死循环

    目录 前置知识 死循环执行步骤1 死循环执行步骤2 死循环执行步骤3 解决方案 总结 前言: HashMap 死循环是一个比较常见.比较经典的问题,在日常的面试中出现的频率比较高,所以接下来咱们通过图解的方式,带大家彻底理解死循环的原因. 前置知识 死循环问题发生在 JDK 1.7 版本中,造成这个问题主要是由于 HashMap 自身的运行机制,加上并发操作,从而导致了死循环. 在 JDK 1.7 中 HashMap 的底层数据实现是数组 + 链表的方式, 如下图所示:   而 HashMap

  • Java 中的HashMap详解和使用示例_动力节点Java学院整理

    第1部分 HashMap介绍 HashMap简介 HashMap 是一个散列表,它存储的内容是键值对(key-value)映射. HashMap 继承于AbstractMap,实现了Map.Cloneable.java.io.Serializable接口. HashMap 的实现不是同步的,这意味着它不是线程安全的.它的key.value都可以为null.此外,HashMap中的映射不是有序的. HashMap 的实例有两个参数影响其性能:"初始容量" 和 "加载因子&quo

  • Java中使用HashMap改进查找性能的步骤

    Java中,HashMap,其实就是键值对.一个Key,对应一个值:写数据时,指定Key写对应值:读取时凭Key找到相应值.感觉就跟Redis差不多. // 创建 HashMap 对象 Sites HashMap<Integer, String> Sites = new HashMap<Integer, String>(); // 添加键值对 Sites.put(1, "Google"); Sites.put(2, "Runoob"); Si

  • 教你Java中的Lock锁底层AQS到底是如何实现的

    目录 前言 加锁 释放锁 总结 前言 相信大家对Java中的Lock锁应该不会陌生,比如ReentrantLock,锁主要是用来解决解决多线程运行访问共享资源时的线程安全问题.那你是不是很好奇,这些Lock锁api是如何实现的呢?本文就是来探讨一下这些Lock锁底层的AQS(AbstractQueuedSynchronizer)到底是如何实现的. 本文是基于ReentrantLock来讲解,ReentrantLock加锁只是对AQS的api的调用,底层的锁的状态(state)和其他线程等待(No

  • 全面解析Java中的HashMap类

    HashMap 和 HashSet 是 Java Collection Framework 的两个重要成员,其中 HashMap 是 Map 接口的常用实现类,HashSet 是 Set 接口的常用实现类.虽然 HashMap 和 HashSet 实现的接口规范不同,但它们底层的 Hash 存储机制完全一样,甚至 HashSet 本身就采用 HashMap 来实现的. 实际上,HashSet 和 HashMap 之间有很多相似之处,对于 HashSet 而言,系统采用 Hash 算法决定集合元素

  • 深入理解Java中的HashMap的实现机制

    如果任何人让我描述一下HashMap的工作机制的话,我就简单的回答:"基于Hash的规则".这句话非常简单,但是要理解这句话之前,首先我们得了解什么是哈希,不是么? 什么是哈希 哈希简单的说就是对变量/对象的属性应用某种算法后得到的一个唯一的串,用这个串来确定变量/对象的唯一性.一个正确的哈希函数必须遵守这个准则. 当哈希函数应用在相同的对象或者equal的对象的时候,每次执行都应该返回相同的值.换句话说,两个相等的对象应该有相同的hashcode. 注:所有Java对象都从Objec

  • Java中对HashMap的深度分析

    在Java的世界里,无论类还是各种数据,其结构的处理是整个程序的逻辑以及性能的关键.由于本人接触了一个有关性能与逻辑同时并存的问题,于是就开始研究这方面的问题.找遍了大大小小的论坛,也把<Java 虚拟机规范>,<apress,.java.collections.(2001),.bm.ocr.6.0.shareconnector>,和<Thinking in Java>翻了也找不到很好的答案,于是一气之下把JDK的 src 解压出来研究,扩然开朗,遂写此文,跟大家分享感

  • Java中map内部存储方式解析

    Map,即映射,也称为 键值对,有一个 Key, 一个 Value . 比如 Groovy 语言中,  def  map = ['name' : 'liudehua', 'age' : 50 ] ,则 map[ 'name' ]  的值是 'liudehua'. 那么 Map 内部存储是怎么实现的呢?   下面慢慢讲解. 一. 使用 拉链式存储 这个以 Java 中的 HashMap 为例进行讲解.   HashMap 的内部有个数组 Entry[]  table, 这个数组就是存放数据的. E

随机推荐