HashMap面试

1.HashMap的数据结构?

数组+链表实现,当链表长度超过8时,链表转换为红黑树

2.HashMap的工作原理?

底层是hash数组和单向链表实现,数组中每个元素都是链表,

K/V存储put的过程

1)调用hash(k)方法计算K的hash值,然后结合数组长度,计算数组下标;

2)调整数组大小(当容器的元素个数大于capacity * loadfactor时,容器会进行扩容resize为2n);

3)a)如果K的hash值在HashMap中不存在,则执行插入,若存在,则发生碰撞;

      b)如果K的hash值存在,而且两者equals返回true,则更新键值对;

      c)如果K的hash值存在,而且两者equals返回false,则插入链表的尾部(尾插法)或红黑树中。

获取对象,将K传给get()方法

1)调用hash(K)方法从而获取该键值所在链表的数组下标

2)顺序遍历链表,equals查找相同Node链表中K值对应的V值

总结:hashCode是定位的,存储位置;equals是定性的,比较两者是否相等

3.当两个对象的hashCode相同会发生什么?

因为 hashCode 相同,不一定就是相等的(equals方法比较),所以两个对象所在数组的下标相同,"碰撞"就此发生。又因为 HashMap 使用链表存储对象,这个 Node 会存储到链表中

4.hash如何实现?为什么要这样?

JDK1.8中,通过hashCode()的高16位异或低16位实现的:(h = k.hashCode()) ^(h>>>16),主要从速度、功效和质量来考虑的,减少系统的开销,也不会造成因为高位没有参与下标的计算,从而引起的碰撞。

5.为什么要用异或运算符?

保证对象的hashCode的32位值只要有一位发生改变,整个hash()返回值就会改变。尽可能减少碰撞。

6.HashMap的table的容量如何确定?loadFactor是什么?该容量如何变化?这种变化会带来什么问题?

1)table数组大小是由capacity这个参数确定的,默认16,也可以构造时传入,最大限制1<<30

2)loadFactor是装载因子,用来判断table数组是否要动态扩展,默认值是0.75,比如table大小为16,装载因子为0.75,当table实际大小超过12时就要动态扩容

3)扩容时,调用resize()方法,将table长度变为原来的两倍(是table的长度)

4)如果数据很大的情况下,扩展时将会带来性能的损失,在性能要求很高的地方,这种损失很可能很致命

7. 数组扩容的过程?

如果数据很大的情况下,扩展时将会带来性能的损失,在性能要求很高的地方,这种损失很可能很致命

8.拉链法导致的链表过深问题为什么不用二叉查找树代替,而选择红黑树?为什么不一直使用红黑树?

二叉树在特殊的情况下会变成一条先行结构,遍历查询会非常慢。而红黑树在插入新数据时可能需要左旋、右旋、变色这些操作来保持平衡,引入红黑树就是查找数据块,解决链表查询深度的问题,我们知道红黑树属于平衡二叉树,但是为了保持“平衡”是需要付出代价的,但是该代价所损耗的资源要比遍历线性链表要少,所以当长度大于8的时候,会使用红黑树,如果链表长度很短的话,根本不需要引入红黑树,引入反而会慢

9.说说对红黑树的见解?

1)每个节点非红即黑

2)根节点总是黑色的

3)如果节点是红色的,则它的子节点必须是黑色的(反之不一定)

4)每个叶子节点都是黑色的空节点(NIL节点)

5)从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)

10.HashMap、LinkedHashMap、TreeMap的区别?

1)LinkedHashMap:保存了记录的插入顺序,在用Iterator遍历时,选取到的记录肯定是先插入的;遍历比HashMap慢

2)TreeMap:实现SortMap接口,能够把它保存的记录根据键排序(默认按键值升序排序)

11. HashMap和HashTable有什么区别?

1)HashMap线程不安全,HashTable是线程安全的

2)由于线程安全,HashTable的效率比不上HashMap

3)HashMap最多只允许一条记录的键为null,允许多条记录的值为null,而HashTable不允许

4)HashMap 默认初始化数组的大小为16,HashTable 为 11,前者扩容时,扩大两倍,后者扩大两倍+1;

5)HashMap 需要重新计算 hash 值,而 HashTable 直接使用对象的 hashCode;

12.为什么ConcurrentHashMap比HashTable效率要高?

1)HashTable使用一把锁锁住整个链表结构来处理并发问题,多个线程竞争一把锁,容易阻塞

2)ConcurrentHashMap

    a)1.7中使用分段锁(ReentrantLock + Segment + HashEntry),相当于把一个HashMap分成多个段,每段分配一把锁,支持多线程访问。锁粒度:基于Segment,包含多个HashEntry。

    b)1.8中使用CAS+synchronized+Node+红黑树。锁粒度:Node(首节点)(实现Map.Entry)。锁力度降低了

12.针对ConcurrentHashMap锁机制具体分析(1.7 vs 1.8)

1.7中采用分段锁的机制,实现并发的更新操作,底层采用数组+链表的存储结构,包括两个核心的静态内部类Segment和HashEntry

1)Segment继承ReentrantLock(重入锁)用来充当锁的角色,每个Segment对象守护每个散列映射表的若干个桶;

2)HashEntry用来封装映射表的键值对

3)每个桶是由若干个HashEntry对象链接起来的链表

1.8中,采用Node + CAS + Synchronized来保证并发安全。取消类Segment,直接用table数组存储键值对;当HashEntry对象组成的链表长度超过TREEIFY_THRESHOLD时,链表转为红黑树,提升性能

13. ConcurrentHashMap在1.8中,为什么要使用内置锁Synchronized来替代重入锁ReentrantLock?

1)粒度降低了

2)JVM开发团队没有放弃synchronized,而且基于JVM的synchronized优化空间更大,更加自然

3)在大量的数据操作下,对于JVM的内存压力下,基于API的ReentrantLock会开销更多的内存

回答: HashMap是Java一个常用数据结构,它的底层是由hash数组和单向链表实现的。每个数组元素都是一个链表,通过Node内部类实现了Map.Entry接口来存储键值对。HashMap通过put和get方法来存储和获取数据。\[1\] 在重写equals方法时,我们需要同时重写hashCode方法。这是因为在HashMap,查找value是通过key的hashCode来进行的。当找到对应的hashCode后,会使用equals方法来比较传入的对象和HashMap的key对象是否相同。因此,为了保证正确的查找和比较,我们需要同时重写equals和hashCode方法。\[2\]\[3\] HashMap在什么时候进行扩容呢?当HashMap的元素数量超过了负载因子(默认为0.75)与当前容量的乘积时,就会进行扩容。扩容是为了保持HashMap的性能,因为当元素数量过多时,链表的长度会变长,查找效率会下降。扩容的过程是创建一个新的数组,将原数组的元素重新分配到新数组,然后将新数组替换为原数组。\[3\] #### 引用[.reference_title] - *1* *2* *3* [史上最全Hashmap面试总结,51道附带答案,持续更新...](https://blog.csdn.net/androidstarjack/article/details/124507171)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值