Java高频面试题- 每日三连问?【Day11】 — 集合容器篇(三)

2022-02-23 16:10:01 浏览数 (1)

问题导读

一、HashMap 和 Hashtable 的区别?

二、ConcurrentHashMap 和 Hashtable 的区别?

三、HashSet 如何检查重复?

01

HashMap 和 Hashtable 的区别?

正经回答:

线程是否安全:

HashMap 是非线程安全的,Hashtable 是线程安全的,因为 Hashtable 内部的方法基本都经过 synchronized 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!);

效率:

因为线程安全的问题,HashMap 要比 Hashtable 效率高一点。另外,Hashtable 基本被淘汰,不要在代码中使用它;

对 Null key 和 Null value 的支持:

HashMap 可以存储 null 的 key 和 value,但 null作为键只能有一个,null 作为值可以有多个;

Hashtable 不允许有 null 键和 null 值,否则会抛出 NullPointerException。

初始容量大小和每次扩充容量大小的不同 :

① 创建时如果不指定容量初始值,Hashtable 默认的初始大小为 11,之后每次扩充,容量变为原来的 2n 1。HashMap 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。

② 创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为 2 的幂次方大小(HashMap 中的 tableSizeFor()方法保证,下面给出了源代码)。也就是说 HashMap 总是使用 2 的幂作为哈希表的大小,后面会介绍到为什么是 2 的幂次方。

底层数据结构:

JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。

02

ConcurrentHashMap和Hashtable的区别?

正经回答:

ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。

底层数据结构:

JDK1.7 的 ConcurrentHashMap 底层采用 分段的数组 链表 实现,JDK1.8 采用的数据结构跟 HashMap1.8 的结构一样,数组 链表/红黑二叉树。

Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 数组 链表 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;

实现线程安全的方式(重要):

① 在 JDK1.7 的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。

到了 JDK1.8 的时候已经摒弃了 Segment 的概念,而是直接用 Node 数组 链表 红黑树 的数据结构来实现,并发控制使用synchronized 和 CAS 来操作。

(JDK1.6 以后对 synchronized 锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在 JDK1.8 中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;

② Hashtable(同一把锁) :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。

03

HashSet 如何检查重复?

正经回答:

当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他加入的对象的 hashcode 值作比较,如果没有相符的 hashcode,HashSet 会假设对象没有重复出现。

但是如果发现有相同 hashcode 值的对象,这时会调用equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让加入操作成功。

在 openjdk8 中,HashSet 的 add()方法只是简单的调用了 HashMap 的 put()方法,并且判断了一下返回值以确保是否有重复元素。

也就是说,在 openjdk8 中,实际上无论HashSet 中是否已经存在了某元素,HashSet 都会直接插入,只是会在 add()方法的返回值处告诉我们插入前是否存在相同元素。

0 人点赞