Java程序员必须掌握的数据结构:HashMap

简介: HashMap底层原理实现是每个Java Boy必须掌握的基本技能,HashMap也是业务开发每天都需要遇到的好伙伴。如此基础且核心的底层数据结构,JDK也给其赋予了线程安全的功能,我们来看看~

HashMap底层原理实现是每个Java Boy必须掌握的基本技能,HashMap也是业务开发每天都需要遇到的好伙伴。如此基础且核心的底层数据结构,JDK也给其赋予了线程安全的功能类,我们来看看~

🌱以【面试官面试】形式覆盖Java程序员所需掌握的Java核心知识、面试重点,本博客收录在我开源的《Java学习指南》中,会一直完善下去,希望收到大家的 ⭐ Star ⭐支持,这是我创作的最大动力: https://github.com/hdgaadd/JavaGetOffer

在这里插入图片描述

1. HashMap内部结构

面试官:你说下HashMap的内部结构?

好的面试官。

HashMap内部存储数据的对象是一个实现Entry接口的Node数组,也称为哈希桶transient Node<K,V>[] table,后面我们称Node数组为Entry数组。Entry数组初始的大小是16

Node节点的内部属性key、value分别代表键和值,hash代表key的hash值,而next则是指向下一个链表节点的指针。

static class Node<K,V> implements Map.Entry<K,V> {
   
   
    final int hash;
    final K key;
    V value;
    Node<K,V> next;
}

1.1 键值的添加流程

面试官:那一个键值是怎么存储到HashMap的?

首先会调用hash方法计算key的hash值,通过key的hashCode值与key的hashCode高16位进行异或运算,使hash值更加随机与均匀。

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

再通过该hash值与Entry数组的长度相与,得到要存储到的索引位置int index = (table.length - 1) & hash。如果该索引位置是空的,会把键值直接添加到表头,如果哈希冲突了则会用链表法形成一条链表。

数据添加后,会判断当前容量是否到达了threshold阈值,threshold等于负载因子loadFactor * table.length。负载因子默认是0.75,threshold第一次扩容时为0.75 16 = *12

如果到达阈值了则会对Entry数组进行扩容,扩容成为原来两倍容量的Entry数组。

1.2 红黑树

面试官:HashMap链表还会转换成什么?

当链表长度 >= 8时,会把链表转换为红黑树

是这样的,HashMap的链表元素如果数量过多,查询效率会越来越低,所以需要将链表转换为其他数据结构。而二叉搜索树这种数据结构是绝对的子树平衡,左节点比父节点小,右节点比父节点大,在极端情况会退化为链表结构

而红黑树放弃了绝对的子树平衡,转而追求的是一种大致平衡,在极端情况下的数据查询效率更优。

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
   
   
    TreeNode<K,V> parent;  // red-black tree links
    TreeNode<K,V> left;
    TreeNode<K,V> right;
    TreeNode<K,V> prev;    // needed to unlink next upon deletion
    boolean red;
}

2. 线程安全的Map

2.1 线程不安全的HashMap

面试官:HashMap为什么线程不安全?

一、在多线程环境下,可能会出现数据覆盖的问题。

例如前面提到如果索引位置为空则直接添加到表头,如下面源码所示。此时如果有两个线程同时进入if语句,线程A把数据插入到表头,接着线程B把他的数据覆盖到表头,这样就产生了数据覆盖的问题,线程A的数据相当于消失了。

if ((p = tab[i = (n - 1) & hash]) == null)
    tab[i] = newNode(hash, key, value, null);

二、另外在多线程环境下,还可能会出现数据不一致的问题。

在插入数据后,判断是否需要扩容是以下代码。

if (++size > threshold)
    resize();

若两个线程同时进入了++size代码块,对size的修改分为三个步骤:读取、计算、赋值。线程A、线程B同时读取了size是0,两者计算时size都为1,后面赋值时把size = 1赋值给了size两次。

但实际上期望的size应该是2,此时就出现了数据不一致的问题,Entry数组的容量会出现错误。

2.2 线程安全的ConcurrentHashMap

面试官:有线程安全的Map吗?

有的,JDK提供了线程安全的ConcurrentHashMap。

ConcurrentHashMap对于底层Entry数组、size容量都添加了可见性的修饰,保证了其他线程能实时监听到该值的最新修改

transient volatile Node<K,V>[] table;
private transient volatile int sizeCtl;

在添加键值的操作,对元素级别进行加锁。若该索引位置不存在元素,则使用乐观锁CAS操作来添加值,而CAS是原子操作,不怕多线程的干扰。

在这里插入图片描述

若该索引位置存在元素,则使用synchronized对该索引位置的头节点进行加锁操作,保证整条链表同一时刻只有一个线程在进行操作。

在这里插入图片描述

另外在JDK7版本中ConcurrentHashMap的实现和JDK8不同。

JDK7版本的数据结构是大数组Segment + 小数组HashEntry,其中小数组HashEntry的每个元素是一条链表,一个Segment是一个HashEntry数组。对每个Segment即每个分段,使用ReentrantLock进行加锁操作。

可以看到JDK8版本相比JDK版本的实现锁粒度更小,且JDK8版本的链表还可以升级为查询效率高的红黑树,所以JDK7版本的ConcurrentHashMap目前被JDK8版本的代替了。

2.3 HashTable和ConcurrentHashMap区别

面试官:HashTable和ConcurrentHashMap有什么区别吗?

HashTable也是线程安全的Map,不过它不仅对修改操作添加加锁操作,获取操作也进行了加锁。

public synchronized V put(K key, V value)
public synchronized V get(Object key)

而ConcurrentHashMap没有对get进行加锁处理,不适用于强一致性的场景。例如要求获取操作必须严格获取到最新的值,这种强一致性场景则更适合使用HashTable。

另外HashTable和HashMap、ConcurrentHashMap还有以下不同。

  1. HashTable继承了Dictionary,而HashMap、ConcurrentHashMap继承了AbstractMap
  2. HashTable初始容量为11,HashMap、ConcurrentHashMap是16
  3. HashTable扩容为原来的2n+1,HashMap、ConcurrentHashMap是扩容为原来的2n

未完待续。。。

创作不易,不妨点赞、收藏、关注支持一下,各位的支持就是我创作的最大动力❤️

相关文章
|
2天前
|
Java
Java之HashMap详解
本文介绍了Java中HashMap的源码实现(基于JDK 1.8)。HashMap是基于哈希表的Map接口实现,允许空值和空键,不同步且线程不安全。文章详细解析了HashMap的数据结构、主要方法(如初始化、put、get、resize等)的实现,以及树化和反树化的机制。此外,还对比了JDK 7和JDK 8中HashMap的主要差异,并提供了使用HashMap时的一些注意事项。
Java之HashMap详解
|
12天前
|
Java 程序员
JAVA程序员的进阶之路:掌握URL与URLConnection,轻松玩转网络资源!
在Java编程中,网络资源的获取与处理至关重要。本文介绍了如何使用URL与URLConnection高效、准确地获取网络资源。首先,通过`java.net.URL`类定位网络资源;其次,利用`URLConnection`类实现资源的读取与写入。文章还提供了最佳实践,包括异常处理、连接池、超时设置和请求头与响应头的合理配置,帮助Java程序员提升技能,应对复杂网络编程场景。
37 9
|
17天前
|
存储 Java
Java中的HashMap和TreeMap,通过具体示例展示了它们在处理复杂数据结构问题时的应用。
【10月更文挑战第19天】本文详细介绍了Java中的HashMap和TreeMap,通过具体示例展示了它们在处理复杂数据结构问题时的应用。HashMap以其高效的插入、查找和删除操作著称,而TreeMap则擅长于保持元素的自然排序或自定义排序,两者各具优势,适用于不同的开发场景。
31 1
|
2天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
15 6
|
8天前
|
存储 Java 索引
Java中的数据结构:ArrayList和LinkedList的比较
【10月更文挑战第28天】在Java编程世界中,数据结构是构建复杂程序的基石。本文将深入探讨两种常用的数据结构:ArrayList和LinkedList,通过直观的比喻和实例分析,揭示它们各自的优势与局限,帮助你在面对不同的编程挑战时做出明智的选择。
|
9天前
|
SQL Java 程序员
倍增 Java 程序员的开发效率
应用计算困境:Java 作为主流开发语言,在数据处理方面存在复杂度高的问题,而 SQL 虽然简洁但受限于数据库架构。SPL(Structured Process Language)是一种纯 Java 开发的数据处理语言,结合了 Java 的架构灵活性和 SQL 的简洁性。SPL 提供简洁的语法、完善的计算能力、高效的 IDE、大数据支持、与 Java 应用无缝集成以及开放性和热切换特性,能够大幅提升开发效率和性能。
|
15天前
|
IDE Java 程序员
C++ 程序员的 Java 指南
一个 C++ 程序员自己总结的 Java 学习中应该注意的点。
19 5
|
16天前
|
存储 算法 Java
Java 中常用的数据结构
【10月更文挑战第20天】这些数据结构在 Java 编程中都有着广泛的应用,掌握它们的特点和用法对于提高编程能力和解决实际问题非常重要。
21 6
|
16天前
|
存储 Java 程序员
Java面试加分点!一文读懂HashMap底层实现与扩容机制
本文详细解析了Java中经典的HashMap数据结构,包括其底层实现、扩容机制、put和查找过程、哈希函数以及JDK 1.7与1.8的差异。通过数组、链表和红黑树的组合,HashMap实现了高效的键值对存储与检索。文章还介绍了HashMap在不同版本中的优化,帮助读者更好地理解和应用这一重要工具。
39 5
|
17天前
|
存储 Java API
详细解析HashMap、TreeMap、LinkedHashMap等实现类,帮助您更好地理解和应用Java Map。
【10月更文挑战第19天】深入剖析Java Map:不仅是高效存储键值对的数据结构,更是展现设计艺术的典范。本文从基本概念、设计艺术和使用技巧三个方面,详细解析HashMap、TreeMap、LinkedHashMap等实现类,帮助您更好地理解和应用Java Map。
37 3
下一篇
无影云桌面