HashMap相关问答 | 面试前专攻

377 阅读6分钟

1. 你知道HashMap嘛?

答:
HashMap采用Entry数组来存储key-value,每一个键值对组成了一个Entry实体(jdk1.7,jdk1.8改成了Node),Entry类实际上是一个单向的链表结构,它具有next指针,可以连接下一个Entry实体。在JDK1.8中,链表长度大于8的时候,链表会转成红黑树!

1.7(数组+链表)

1.8(数组+链表+红黑树)

HashMap线程不安全,而Hashtable线程安全

HashMap可以接受null键值和值,而Hashtable则不能

另:

1、HashMap是线程不安全的,在多线程环境下,使用Hashmap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap

2、HashTable线程安全的策略实现代价却太大了,简单粗暴,get/put所有相关操作都是synchronized的,这相当于给整个哈希表加了一把大锁。

多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞,相当于将所有的操作串行化,在竞争激烈的并发场景中性能就会非常差

**2. 你说下它的put方法的整个过程吧? **

答:

大概流程:

1、根据key通过哈希算法与与操作(数组的长度减一)得到一个数组的下标

2、如果数组下标的元素为空的话,则封装成一个enty对象(1.7叫entry对象,1.8叫node对象),并放入该位置

3、如果数组下标不为空,则分情况讨论

    a.如果是1.7,则先判断是否需要扩容,如果需要扩容则进行扩容,如果不需要扩容,则生成

    entry对象,用头插法插入到当前位置的链表中

    b.如果是1.8,则先判断当前位置的Node类型是链表还是红黑树

        i:如果是红黑树的Node,则将key和value封装成一个红黑树节点添加到红黑树中,在这

        个过程中会判断是否存在key值,如果存在则更新value

        ii:如果此对象上的Node对象是链表节点,则将key和value封装成一链表节点通过尾插法添加到链表的最后位置,因为是尾插法,所以就要

        遍历链表,在遍历链表的过程中,判断是否存在key,如果存在则更新value。当遍历完链表后,将新链表Node插入到链表中,插入到链表

        后,会看当前链表的节点个数,如果大于等于8,则将链表转成红黑树

        iii:将key和value封装为Node插入到链表或者红黑树后,再判断是否需要扩容,如果需要则扩容,如果不需要则调用对象的哈希函数获取key对应的hash值,再计算其数组下标;如果没有出现哈希冲突,则直接放入数组;如果出现哈希冲突,则以链表的方式放入链表后面;如果链表长度超过了阈值( TREEIFY THRESHOLD==8 ),就把链表转成红黑树。链表长度低于6时,把红黑树转回链表;如果结点的key已经存在,则替换value值;如果集合中的键值对个数大于12,则调用resize方法对数组进行扩容
        get方法 通过链对象调用equals()方法,
        put方法 调用hashcode方法计算hashcode ,计算后找到地址存放entry对象

3. hashmap为什么是线程不安全的

答:put的时候导致的多线程不一致
两个线程a b同时做put操作,b先计算出hash的索引值将数据插入,恰好a计算出的hash索引值与b相同,这是a就覆盖了b的数据 ,造成线程安全问题
resize而引起死循环
当自动扩容的时候,两个线程同时修改一个链表结构会产生一个循环链表,接下来个get的时候,就会出现死循环

根据看过的源码加自己的理解,一步一步说出来就好了

4. 它怎么扩容?说下扩容机制吧

答:默认capacity=16,loadfactor=0.75f
达到threshold = capacity * loadfactor后,扩为2倍.
之后进行rehash,重新计算下标。根据rehash前后结果是否相同,分为低位链表和高位链表进行rehash

5. 具体代码是怎么实现的?

答:对原大小进行位运算左移一位

**6. 为啥说扩容的大小必须是2的幂次方?**2.

答:因为用了与运算,提高hash值计算效率
因为HashMap找对应桶位的算法使用了与运算来代替传统的取模运算
hash % length == hash&(length - 1) 的前提是 length是2的幂次方.

1.为了保证得到的新的数组索引和老数组索引一致

HashMap的初始容量是2的n次幂,扩容也是2倍的形式进行扩容,是因为容量是2的n次幂,可以使得添加的元素均匀分布在HashMap中的数组上,减少hash碰撞,避免形成链表的结构,使得查询效率降低

2.rehash时的取余操作,hash % length == hash & (length - 1)这个关系只有在length等于二的幂次方时成立,位运算能比%高效得多

7.红黑树的查找时间复杂度是多少?

答:O(logN)

8. 了解CocurrentHashMap吗?

答:主要就是为了应对hashmap在并发环境下不安全而诞生的,大量的利用了volatile,final等lock-free技术来减少锁竞争对于性能的影响
ConcurrentHashMap使用分段锁技术,将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问,能够实现真正的并发访问
ConcurrentHashMap定位一个元素的过程需要进行两次Hash操作。
1、Hash定位到Segment
2、Hash定位到元素所在的链表的头部
jdk1.7:数组+链表+segment分段锁(继承了ReentrantLock)
jdk1.8:数组+链表+红黑树 内部大量采用CAS操作(CAS是compare and swap的缩写,即我们所说的比较交换。cas是一种基于锁的操作,而且是乐观锁。)

9. 我们可以使用CocurrentHashMap来代替Hashtable吗?

答:我们知道Hashtable是synchronized的,但是ConcurrentHashMap同步性能更好,因为它仅仅根据同步级别对map的一部分进行上锁。ConcurrentHashMap当然可以代替HashTable,但是HashTable提供更强的线程安全性