面试中的HashMap、Hashtable与ConcurrentHashMap

面试中的HashMap、Hashtable与ConcurrentHashMap

近两年出场率最高的 Java 面试知识点之一

问题1:说说你了解的 HashMap、HashTable 和 ConcurrentHashMap

作死:平时用用,没看的那么深
简答:这三者都是 Map 的实现,HashMap 是基于哈希表的 Map 接口的实现, Hashtable 同样是基于哈希表,但是 Hashtable 是线程安全的,ConcurrentHashMap 是从1.5 版本出现的,线程安全的可以替代 Hashtable 的 Map 实现。
详答:(以上重复的不再多说) HashMap 采用哈希表来管理元素,在调用 put 方法向 HashMap 中写入值的时候,先计算 key 的哈希值来进行快速定位,然后写入值或者替换值。
当然,也会有多个不同元素计算出同一个哈希值,这就是我们说的哈希碰撞,此时再配合 equals 方法,共同判断这个 Key 是否真正的存在,然后再进行下面的操作。

关于键值存储:HashMap 允许存在 null 的键和值,但是键值只能有一个为 null,Hashtable 不允许有 null 的键值存在。
Hashtable 的默认初始化大小是 11 ,影响因子同样是 0.75,扩容采用的是在影响因子的约束下 的 rehash() 方法 newCapacity = (oldCapacity << 1) + 1

public Hashtable() {
        this(11, 0.75f);
}

Hashtable 可以算是1.4 遗留类,现在还有很多冗余和可优化项。而且单线程下也要加锁,所以现在的代码中,多线程通常采用 ConcurrentHashMap 来实现。

ConcurrentHashMap 见问题4~

问题2:听说 HashMap 在 1.7 和 1.8 版本有不同,说说看

作死:有区别么,不是一样的么?
简答:是底层数据结构的实现发生了变化
详述:在 1.7 版本中,HashMap 底层采用了 Entry 数组+链表的数据结构,通过对 Key 的 Hashcode 进行取模来决定 key 存放的位置。相同 Hashcode (或者说取模后) 的 key 值会存储到一个 Entry 中,他们会形成一个链表 Node<>,一旦链表过长,查找复杂度可能会达到O(n)。

在 1.8 版本之后,HashMap 底层采用数组 + 链表/红黑树的数据结构,在同 Hashcode 的 key 的数量小于等于8个的时候,还是采用链表的结构,如果大于8个,那么就采用红黑树的数据结构。

if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);

因为红黑树的性能特点,在大量 hashcode 值相同的时候,查找某个特定元素,也只是需要O(log n) 的开销.

但是想要使用好1.8版本的 HashMap,请正确的实现 Compare 接口,如果实现的不好或者没实现,效率可能还要低于 1.7 版本。

问题3:来说说 HashMap 的扩容机制

作死: 没太研究过,可能是翻倍吧
简答: 初始化容量16,超过的话是原来的二倍
详细:默认情况下,HashMap 初始化的容量大小为 16,同时还有约束 HashMap 什么时候扩容的影响因子为 0.75。

// 默认初始化大小
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
// 默认影响因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;

影响因子的存在,是为了维持 HashMap 中的空间开销与时间开销。
当 HashMap 的容量超过 16*0.75=12 的时候,HashMap 调用resize() 中 newCap = oldCap << 1 << 1 的方法开始第一次扩容。
如果说一个HashMap 中有12个键值对,那么它的大小为16,如果是13的时候,那么他它大小即为32。

我们在扩充HashMap的时候,不需要像JDK1.7的实现那样重新计算hash,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap”。

HashMap 中用到了大量的位运算,这是我们在设计自己的工具类中可以借鉴的。

在这,我从美团点评技术团队博客盗了个图,非常有益于我们理解 HashMap 的 put 和 扩容机制,感谢美团点评技术团队(无利益,侵删)~
这里写图片描述

问题4:详细说说你知道的 ConcurrentHashMap

对于ConcurrentHashMap,同样的,从 1.7 到 1.8 版本,Java开发者对 ConcurrentHashMap 也做了修改。

在 1.7 版本中,大小默认为16,ConcurrentHashMap 由一个 Segment 数组和多个 HashEntry 组成。使用 Segment 是锁分离技术的实现,将一个大的 table 转换为多个小的 table 来进行加锁,每个 Segment 元素存储的是一个Entry数组+链表,与 HashMap 结构相同。

到了 1.8 版本,初始化大小为 16 ,0.75。ConcurrentHashMap 抛弃了 Segment 数组,采用 Node数组 + 链表 + 红黑树 的数据结构实现,当链表长度大于8的时候会构建为红黑树。并发控制使用Synchronized和CAS来操作(整个看起来就像是优化过且线程安全的HashMap)。

ConcurrentHashMap 的 put 操作是并发进行的,所以可能会发生 put 与扩容操作同时进行的情况,那么先扩容,再 put 。只有发生冲突的时候,才采用乐观锁的方式进行并发处理。

关于 ConcurrentHashMap 涉及到并发和乐观锁的扩容,嗯….给你个链接
http://www.importnew.com/22007.html

总的来说,就是 put扩容 的同时,要考虑到锁的问题,不考虑锁的情况下,大体上和 HashMap 是相似的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值