八股文-HashMap

是什么?谁发明的?用来做什么?特点是什么?

哈希表,JDK自带的存储容器,存储key-value数据,特点是访问快

为啥访问快?底层结构?原理?

底层采用数组+链表/红黑树,利用hash+取余操作快速定位数组下标,数组和红黑树主要解决哈希冲突
在这里插入图片描述

什么是哈希冲突?

不同的对象的hash函数+取余操作可能得到相同的值,这个时候数组只有一个位置,放不下多个元素,所以就出现了冲突,这个就叫哈希冲突。如果需要解决冲突,就是在当前位置拉出一个链表。

那么为啥要出现红黑树?

随着冲突的增加,链表长度会变长,搜索的时候是利用hash函数+取余计算下标,然后遍历链表匹配到值,如果链表太长,遍历链表性能会下降,所以采用红黑树进行优化,红黑树是一个树形数据结构,搜索时间复杂度更低。

为啥要扩容?

ArrayList扩容很容易理解,因为底层是数组,数组长度是固定的。HashMap底层是数组+链表/红黑树,链表和红黑树长度不固定,为啥要扩容呢?
性能考虑:其实这个很好理解,不扩容的话,链表长度或者红黑树层级越来越高,查询、插入、删除、修改效率会越来越慢
减少哈希冲突概率:数组长度越长,哈希冲突概率也就越小。

在JDK 1.7 和 JDK 1.8中的主要变化

程序员是人,不是神,所以JDK一开始也不完美
JDK 1.8,扩容 从头插法改成尾插法,避免了死循环(什么是死循环?
引入了红黑树
优化的哈希计算:Java 1.8 使用了更简单的哈希计算方法

扩容头插法死循环原因?

多个线程同时调用put方法,并发触发扩容导致扩容后的链表出现环形指针。主要还是因为 HashMap 不支持并发,可以用ConcurrentHashMap代替、或者加锁、或者HashTable(性能不行)
此时调用 HashMap 的get方法获取数据时,如果只是获取循环链上存在的数据,是不会有问题的,因为可以找到。就怕获取循环链上没有的数据,会进入无限循环中导致CPU使用率飙升。

扩容

扩容:在添加元素时,如果添加后的大小超过了阈值(容量 * 负载因子)
容量 :默认16
负载因子:默认0.75
容量可以通过构造函数指定,具体可以根据实际使用场景设置(尽量避免频繁扩容)。
负载因子0.75是JDK官方根据一些统计学方法,计算得到的合适的值,这样很少会出现哈希冲突
扩容后的大小是原来的 2 倍。

参考

https://cloud.tencent.com/developer/article/1830825

### HashMap 常见面试问题及答案 #### 什么是HashMap?其内部结构如何? `HashMap` 是 Java 中基于哈希表实现的集合类,允许存储键值对 (key-value pairs),其中 key 和 value 都可以为 null。内部由数组加链表/红黑树组成,在 JDK 8 及之后版本中当桶(bucket)内的节点数达到一定阈值时会自动转换成红黑树以提高查询效率[^1]。 #### 如何保证HashMap中的元素唯一性? 在 `HashMap` 中,元素的唯一性是由 key 的 hashcode() 方法和 equals() 方法共同决定的。当两个对象的 hashCode 不同,则认为这两个对象不同;如果两个对象的 hashCode 相同,则调用 equals 来进一步判断是否相同。只有两者都相等才被认为是同一个对象。 #### 解释下HashMap的工作原理? 每当向 `HashMap` 插入新 entry 或者获取已有 entry 时,都会先计算该 entry 对应 key 的 hash 值,再根据此 hash 值找到对应的 bucket 位置。对于插入操作而言,若目标 bucket 已存在其他 entries 则形成单项链表或红黑树保存多个 entries;读取数据时则依据相同的逻辑定位到具体的位置并返回相应 value。 #### HashMap是如何解决hash冲突的? 为了处理散列碰撞(`Hash Collision`)的情况,Java 使用拉链法(Chaining Method): 即让具有相同索引的不同 Entry 形成一条双向链接列表(linked list), 当发生 HashCollision 后新增的数据会被追加至这条 List 尾部; 而一旦某个 Bucket 下面挂载过多 Node(JDK8+)就会触发 Treeify 操作将其转化为 Red-Black Tree 结构从而加快检索速度. #### Collections.synchronizedMap()创建的是什么类型的Map?它有什么特点? 通过 `Collections.synchronizedMap()` 返回的新 Map 实现了同步控制机制,这意味着在同一时刻只有一个线程能够访问 map 容器完成增删改查动作,因此它是线程安全的。需要注意的是,这并不意味着所有对该实例的操作都是原子性的——比如迭代遍历仍然需要额外锁定整个映射才能确保安全性。 ```java // 创建一个线程安全的HashMap Map<String, String> syncMap = Collections.synchronizedMap(new HashMap<>()); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值