小言_互联网的博客

最通俗的 HashMap 常见面试题

411人阅读  评论(0)

Map类的简略关系图:

 

思维导图

 

HashTable和HashMap的区别:

1、HashMap 线程不安全,可以放空key(只能放一个)

2、HashTable 线程安全,不可以放空key

 

存放空Key的hash值放在数组的哪个位置上:

存放在下标为0的位置上,也就是第一个链表的位置

 

HashMap1.7和HashMap1.8有什么区别:

1.7 底层用的是数组+链表实现的

1.8 底层用的是数组+链表+转换红黑树实现的

 

HashMap是如何解决Hash冲突问题的:

使用链表存放hash值相等且内容不等,存放在统一个链表中

 

HashMap的put方法底层是如何实现:

1、判断key如果为空的情况下,存放数组0

2、默认HashMap初始容量为16、负载因子大小 16*0.75 = 12 , 每次扩容 2 倍

3、根绝key计算hash值,存放到数组下标等于hash值的位置

4、如果计算出的hash值的位置已经存在数据(hash冲突),但是内容不等,则通过链表进行存储

5、如果当前size > 负载因子阈值,开始*2扩容

备注:1.8 中链表长度如果大于8的情况下,开始将链表转换为红黑树

之前小编写过HashMap put方法的源码分析,大家有兴趣可以去看一下:传送门

 

为什么负载因子是0.75:

空间利用率高、冲突少、0.75最合适

 

HashMap 1.7中数组扩容死循环的问题:

因为HashMap1.7 数组的扩容的链表采用头插入法,在多线程的情况下操作hashMap的话,导致死循环的问题

 

HashMap 根据key 获取值的时间复杂度是多少:

1、index没有冲突, 直接获取 ,O(1)

2、index有冲突,遍历链表, O(n)

 

为什么1.8,HashMap要引入红黑树?

如果index冲突过多,导致链表过长,遍历链表的时间复杂度为O(n)

而引入红黑树之后,遍历红黑树的时间负责度为n(logn)

注:当链表长度大于8并且数组容量大于64的请情况下,开始将链表转换为红黑树

 

1.8的HashMap解决了1.7中的什么问题:

1、1.8采用的是尾插入法

2、解决了1.7HashMap死循环的问题

3、链表长度>8转换为红黑树

 

HashMap线程不安全,高并发的时候用什么呢?

ConcurrentHashMap 、或者使用Collecitons.synchronizedMap(map)

为什么不用HashTable呢?

因为HashTable用的是Synchronized锁,在高并发的情况下,很多线程去竞争一个锁,导致效率非常低下!

为什么用ConcurrentHashMap呢?

1.7中: ConcurrentHashMap 内部是通过 继承ReentrantLock 的内部类(Segment类)来实现线程安全的 ,而且 里边的节点(Entry)也通过 volatile 修饰保证它的内存可见性。

1.8中:它不再使用上述的Segment类,而是通过 cas + synchronized 保证并发安全。了解 CAS :传送门

而且ConcurrentHashMap在 1.7 和 1.8中也有所不同

具体可见:传送门

 

hashMap中的重要属性:

 

手写简单的HashMap


  
  1. package collection;
  2. public class MapHashMap<K,V> {
  3. // 数组的长度
  4. private int size;
  5. // 定义数组
  6. private Entry<K,V>[] table;
  7. // 数组的默认初始长度
  8. private final int CAPACITY = 8;
  9. /**
  10. * 初始化数组容量
  11. */
  12. public MapHashMap() {
  13. this.table = new Entry[CAPACITY];
  14. }
  15. public int getSize(){
  16. return size;
  17. }
  18. /***
  19. * 数组中的节点
  20. * @param <K> key
  21. * @param <V> value
  22. */
  23. static class Entry<K,V>{
  24. final int hash;
  25. public int getHash() {
  26. return hash;
  27. }
  28. public K getKey() {
  29. return key;
  30. }
  31. public V getValue() {
  32. return value;
  33. }
  34. public Entry<K, V> getNext() {
  35. return next;
  36. }
  37. final K key;
  38. V value;
  39. Entry<K,V> next;
  40. public Entry(int hash, K key, V value, Entry<K, V> next) {
  41. this.hash = hash;
  42. this.key = key;
  43. this.value = value;
  44. this.next = next;
  45. }
  46. }
  47. /***
  48. * 根据key获取value
  49. * @param key key
  50. * @return value
  51. */
  52. public Object get(Object key){
  53. int hash = key.hashCode();
  54. int i = hash % CAPACITY;
  55. for(Entry<K,V> entry = table[i]; entry != null; entry = entry.next){
  56. if(entry.key.equals(key)){
  57. return entry.value;
  58. }
  59. }
  60. return null;
  61. }
  62. public Object put(K key, V value){
  63. int hash = key.hashCode();
  64. int i = hash % CAPACITY;
  65. for(Entry<K,V> entry = table[i]; entry != null; entry = entry.next){
  66. if(entry.key.equals(key)){
  67. V oldValue = entry.value;
  68. entry.value = value;
  69. return oldValue;
  70. }
  71. }
  72. addEntry(hash,key,value,i);
  73. return null;
  74. }
  75. void addEntry(int hash,K key, V value, int index){
  76. Entry entry = new Entry(hash,key,value,table[index]);
  77. table[index] = entry;
  78. size ++;
  79. }
  80. // 测试
  81. public static void main(String[] args) {
  82. MapHashMap mapHashMap = new MapHashMap();
  83. mapHashMap.put( "213", "123");
  84. System.out.println(mapHashMap.get( "213"));
  85. }
  86. }

 

简做总结,如有不足,欢迎指出

 


转载:https://blog.csdn.net/lk1822791193/article/details/105611405
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场