HashMap原理及面试题(精髓)

HashMap集合简介

HashMap基于哈希表的Map接口实现,是以key-value存储形式存在,即主要用来存放键值对。HashMap 的实现不是同步的,这意味着它不是线程安全的。它的key、value都可以为null。此外,HashMap中的映射不是有序的。

  • JDK1.8 之前 HashMap 由 数组+链表 组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突**(两个对象调用的hashCode方法计算的哈希码值一致导致计算的数组索引值相同)**而存在的(“拉链法”解决冲突)
  • JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(或者红黑树的边界值,默认为 8)并且当前数组的长度大于64时,此时此索引位置上的所有数据改为使用红黑树存储。

将链表转换成红黑树前会判断,即使阈值大于8,但是数组长度小于64,此时并不会将链表变为红黑树。而是选择进行数组扩容
这样做的目的是因为数组比较小,尽量避开红黑树结构,这种情况下变为红黑树结构,反而会降低效率,因为红黑树需要进行左旋,右旋,变色这些操作来保持平衡 。同时数组长度小于64时,搜索时间相对要快些。所以综上所述为了提高性能和减少搜索时间,底层在阈值大于8并且数组长度大于64时,链表才转换为红黑树。具体可以参考 treeifyBin方法。
当然虽然增了红黑树作为底层数据结构,结构变得复杂了,但是阈值大于8并且数组长度大于64时,链表转换为红黑树时,效率也变的更高效。
总结

  • 存取无序的
  • 键和值位置都可以是null,但是键位置只能是一个null
  • 键位置是唯一的,底层的数据结构控制键的
  • jdk1.8前数据结构是:链表 + 数组 jdk1.8之后是 : 链表 + 数组 + 红黑树
  • 阈值(边界值) > 8 并且数组长度大于64,才将链表转换为红黑树,变为红黑树的目的是为了高效的查询

HashMap集合底层的数据结构

数据结构概念

数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法索引技术有关。

数据结构:就是存储数据的一种方式。

  • 在JDK1.8 之前 HashMap 由 数组+链表 数据结构组成的
  • 在JDK1.8 之后 HashMap 由 数组+链表 +红黑树数据结构组成的

HashMap底层的数据结构存储数据的过程

public class Demo01 {
    public static void main(String[] args) {
        HashMap<String, Integer> map = new HashMap<>();
        map.put("刘德华", 53);
        map.put("柳岩", 35);
        map.put("张学友", 55);
        map.put("郭富城", 52);
        map.put("黎明", 51);
        map.put("林青霞", 55);
        map.put("刘德华", 50);
    }
}

在这里插入图片描述
在这里插入图片描述

  1. 面试题:HashMap中hash函数是怎么实现的?还有哪些hash函数的实现方式?

对于key的hashCode做hash操作,无符号右移16位然后做异或运算。
还有平方取中法,伪随机数法和取余数法。这三种效率都比较低。而无符号右移16位异或运算效率是最高的。至于底层是如何计算的我们下面看源码时给大家讲解。

  1. 面试题:当两个对象的hashCode相等时会怎么样?

会产生哈希碰撞,若key值内容相同则替换旧的value.不然连接到链表后面,链表长度超过阈值8和数组的长度大于64就转换为红黑树存储。

  1. 面试题:何时发生哈希碰撞和什么是哈希碰撞,如何解决哈希碰撞?

只要两个元素的key计算的哈希码值相同就会发生哈希碰撞。jdk8前使用链表解决哈希碰撞。jdk8之后使用链表+红黑树解决哈希碰撞。

  1. 面试题:如果两个键的hashcode相同,如何存储键值对?

hashcode相同,通过equals比较内容是否相同。
相同:则新的value覆盖之前的value
不相同:则将新的键值对添加到哈希表中

  • 在不断的添加数据的过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。
  • JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度(阀值)超过 8 时且当前数组的长度 > 64时,将链表转换为红黑树,这样大大减少了查找时间。jdk8在哈希表中引入红黑树的原因只是为了查找效率更高。

哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的
在这里插入图片描述

** 但是这样的话问题来了,传统hashMap的缺点,1.8为什么引入红黑树?这样结构的话不是更麻烦了吗,为何阈值大于8换成红黑树?**

JDK 1.8 以前 HashMap 的实现是 数组+链表,即使哈希函数取得再好,也很难达到元素百分百均匀分布。当 HashMap 中有大量的元素都存放到同一个桶中时,这个桶下有一条长长的链表,这个时候 HashMap 就相当于一个单链表,假如单链表有 n 个元素,遍历的时间复杂度就是 O(n),完全失去了它的优势。针对这种情况,JDK 1.8 中引入了 红黑树(查找时间复杂度为 O(logn))来优化这个问题。 当链表长度很小的时候,即使遍历,速度也非常快,但是当链表长度不断变长,肯定会对查询性能有一定的影响,所以才需要转成树。

总结 :结合一个存储流程图来进一步说明一下:(jdk8存储过程)
在这里插入图片描述
注意:

  • size表示 HashMap中K-V的实时数量 , 注意这个不等于数组的长度 。
  • threshold( 临界值) =capacity(容量) * loadFactor( 加载因子 )。这个值是当前已占用数组长度的最大值。size超过这个临界值就重新resize(扩容),扩容后的 HashMap 容量是之前容量的两倍 。

HashMap继承关系

HashMap继承关系如下图所示
在这里插入图片描述

  • Cloneable 空接口,表示可以克隆。 创建并返回HashMap对象的一个副本。
  • Serializable 序列化接口。属于标记性接口。HashMap对象可以被序列化和反序列化。
  • AbstractMap 父类提供了Map实现接口。以最大限度地减少实现此接口所需的工作。

补充:通过上述继承关系我们发现一个很奇怪的现象, 就是HashMap已经继承了AbstractMap而AbstractMap类实现了Map接口,那为什么HashMap还要在实现Map接口呢?同样在ArrayList中LinkedList中都是这种结构。

据 java 集合框架的创始人Josh Bloch描述,这样的写法是一个失误。在java集合框架中,类似这样的写法很多,最开始写java集合框架的时候,他认为这样写,在某些地方可能是有价值的,直到他意识到错了。显然的,JDK的维护者,后来不认为这个小小的失误值得去修改,所以就这样存在下来了。

HashMap集合类的成员

  • 序列化版本号
    private static final long serialVersionUID = 362498820763181265L;
  • 集合的初始化容量( 必须是二的n次幂 )

//默认的初始容量是16 – 1<<4相当于12的4次方—116
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

问题: 为什么必须是2的n次幂?如果输入值不是2的幂比如10会怎么样?
HashMap构造方法还可以指定集合的初始化容量大小:

HashMap(int initialCapacity) 构造一个带指定初始容量和默认加载因子 (0.75) 的空 HashMap。

面试题:

  • HashMap容量怎么扩容的
  1. HashMap的初始容量,我们可以通过构造方法设置,如果设置了,就是设置的值向上最接近2的n次幂
  2. 如果没有设置 默认长度就是16
  3. 构造方法除了设置容量以外,我们还可以设置阈值,默认为0.75
  4. 如果hashMap的entry数组长度 大于阈值的时候,会触发扩容 扩容是2倍
  • HashMap怎么解决的hash冲突的

1.再拿hash值的时候,会有扰动函数,来降低hash冲突
2. 如果hash冲突了,会通过链地址法解决冲突,假如冲突过多,链表过长,会导致查询越来越慢。
如果超过8,会先判断数组的长度是否大于64,如果小于64,扩容,如果大于就会把链表转换为红黑树。
如果数组的下标的数的节点小于6了又会转换为链表。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
HashMap 是 Java 中常用的数据结构,面试中经常会涉及到与之相关的问题。以下是一些常见的 HashMap试题及其答案: 1. HashMap 是如何工作的? - HashMap 是基于哈希表实现的,使用键值对(key-value)存储数据。 - 当插入一个键值对时,HashMap 会根据键的哈希值计算其在数组中的位置,然后将值存储在该位置上。 - 当获取一个值时,HashMap 会根据键的哈希值找到对应的位置,并返回该位置上的值。 2. HashMap 和 Hashtable 有什么区别? - HashMap 是非线程安全的,而 Hashtable 是线程安全的。 - HashMap 允许 null 键和 null 值,而 Hashtable 不允许。 - HashMap 是不保证元素顺序的,而 Hashtable 是按插入顺序排序的。 3. 如何处理 HashMap 的冲突(Collision)? - 当两个不同的键具有相同的哈希值时,产生冲突。 - HashMap 使用链表法解决冲突,即在相同位置的链表中顺序存储多个键值对。 - 当链表长度超过阈值(默认为8)时,链表将转换为红黑树,以提高查找效率。 4. 如何遍历 HashMap? - 可以使用迭代器(Iterator)遍历 HashMap 的键值对。 - 也可以通过键集(keySet)或值集(values)获取对应的迭代器进行遍历。 5. HashMap 在扩容过程中会发生什么? - 当 HashMap 中的元素数量超过负载因子(默认为0.75)乘以数组大小时,会触发扩容。 - 扩容会创建一个新的两倍大小的数组,并重新计算每个键值对的位置。 - 扩容过程中,原来链表中的键值对会按照新的哈希值重新分配位置。 以上是一些常见的 HashMap试题及其答案,希望对你有帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值