面经-Map

Map在Java里边是一个接口,常见的实现类有HashMap,LinkedHashMap,TreeMap和ConcurrentHashMap
在Java里边,哈希表的实现是由数组+链表组成

HashMap底层数据结构是数组+链表/红黑树
LinkedHashMap底层数据结构是数组+链表+双向链表
TreeMap底层数据结构是红黑树
ConcurrentHashMap底层数据结构也是数组+链表/红黑树

Q&A
1、你能讲讲new一个HashMap的时候,会发生什么吗?
如果我们不指定大小,默认HashMap的大小是16,负载银子的大小是0.75
还有就是:HashMap的大小只能是2次幂的
我们把原素放进HashMap的时候,需要算出这个元素所在的位置(hash值)
在HashMap里用的是位运算来取代取模,能够更加高效的算出元素所在位置
而负载因子的大小决定着哈希表的扩容和哈希冲突
比如现在我默认的HashMap大小是16,负债因子是0.75,那么16*0.75=12,这意味着这个数组最多只能存放12个元素,一旦超过12个元素,那么这个哈希表就要扩容了
由于HashMap的大小只能是2次幂,所以HashMap扩容机制为2倍扩容

2、为什么HashMap的大小只能是2次幂?
因为只有大小为2次幂时,才能合理用位运算代替取模

3、扩容这个肯定是耗时的操作,那能不能把负载因子调高一点,比如调到1,那么我的HashMap就等到存放16个元素的时候才开始扩容呢?
当然可以,但是不推荐。负载因子调高了,哈希冲突概率增高,同样会耗时(因为查找的速度边慢了)

4、在put元素的时候,传递的Key是怎么算哈希值的?
从源码可以发现,它是先算出正常的哈希值,然后与高位16位做异或运算,产生最终的哈希值
这样做的好处可以增加随机性,减少了碰撞冲突的可能性
面经-Map
 

 5、你能简单的说一下put和get方法的实现吗?
在put的时候,首先对key做hash运算,计算出该key所在的index
如果没有碰撞,直接放到数组中,如果碰撞了,需要判断目前数据结构是链表还是红黑树,根据不通情况来进行插入
假设key是相同的,则替换到原来的值。最后判断哈希表是否满了,满了则扩容。
面经-Map
在get的时候,还是对key做hash运算,计算出该key所在的index,然后判断是否有hash冲突
假设没有冲突直,则直接返回。如果有冲突则判断当前数据结构是链表还是红黑树,分辨从不通的数据结构中取出。
面经-Map
 

 6、HashMap中式怎么判断一个元素是否相同的呢?
首先会比较hash值,随后会用==运算符和equals()来判断该元素是否相同
说白了就是:如果只有hash值相同,那么说明该元素哈希冲突了,如果hash值和equals() || ==都相同,那么说明该元素是同一个

7、HashMap的数据结构是数组+链表/红黑树,那么什么i情况下才会用红黑树?
当数组的大小大于64且链表的大小大于8的时候才会将链表改为红黑树,当红黑树的大小为6时,会退化为链表。
这里转红黑树退化为链表的操作主要出于查询和插入时对性能的考量
链表查询时间复杂度O(N),插入时间复杂度O(1),红黑树查询和插入时间复杂度O(logN)

8、在日常开发中LinkedHashMap用的多吗?
LinkedHashMap底层结构时数据+链表+双向链表,实际上它继承了HashMap,在HashMap的基础上维护了一个双向链表
有了双向链表,我们的插入可以是有序的,这里的有序不是指大小有序,儿是插入有序
LinkedHashMap在便利的时候实际用的是双向链表来遍历的,所以LinkedHashMap的大小不会影响到遍历的性能

9、介绍一下TreeMap
TreeMap在显示开发中用的也不多,TreeMap的底层数据结构是红黑树
TreeMap的key不能为null(如果为null,那还怎么排序呢)
TreeMap有序是通过Comparator来进行比较的,如果comparator为null,那么就使用自然顺序

10、讲一讲线程安全的Map
HashMap不是线程安全的,在多线程环境下,HashMap有可能丢失数据和获取不到最新数据的问题。比如说:线程A put进去了,线程B get不出来
我们想要线程安全,一般使用ConcurrentHashMap
还有一个HashTable也是线程安全的
当然也可以向ArrayList一样,使用Collections来包装一个线程安全的Map
无论是HashTable还是Coleecttions包装出来的都比较低效(因为是直接在外层套一个synchronize),所以我们一般有线程安全问题考量,都使用ConcurrentHashMap

ConcurrentHashMap通过在部分加锁和利用CAS算法来实现同步,在get的时候没有枷锁,Node都用了volatile给修饰
在扩容时,会给每个线程分配对应的区间,并且为了防止putVal导致数据不一致,会给线程的所负责的区间加锁

上一篇:Java 集合类笔记


下一篇:TreeMap的使用