Map集合的常见面试题

Q:HashMap 的工作原理?

A:HashMap 底层是 hash 数组单向链表实现,数组中的每个元素都是链表,由 Node 内部类(实现 Map.Entry<K,V>接口)实现,HashMap 通过 put & get 方法存储和获取。

存储对象时,将 K/V 键值传给 put() 方法:①、调用 hash(K) 方法计算 K 的 hash 值,然后结合数组长度,计算得数组下标;②、调整数组大小(当容器中的元素个数大于 capacity * loadfactor 时,容器会进行扩容resize 为 2n);
③、i.如果 K 的 hash 值在 HashMap 中不存在,则执行插入,若存在,则发生碰撞
ii.如果 K 的 hash 值在 HashMap 中存在,且它们两者 equals 返回 true,则更新键值对
iii. 如果 K 的 hash 值在 HashMap 中存在,且它们两者 equals 返回 false,则插入链表的尾部(尾插法)或者红黑树中(树的添加方式)

JDK 1.7 之前使用头插法JDK 1.8 使用尾插法
(注意:当碰撞导致链表大于 TREEIFY_THRESHOLD = 8 时,就把链表转换成红黑树)

获取对象时,将 K 传给 get() 方法:①、调用 hash(K) 方法(计算 K 的 hash 值)从而获取该键值所在链表的数组下标;②、顺序遍历链表,equals()方法查找相同 Node 链表中 K 值对应的 V 值。

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


Q:当两个对象的 hashCode 相同会发生什么?

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


Q:你知道 hash 的实现吗?为什么要这样实现?

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

Q:为什么要用异或运算符?
A:保证了对象的 hashCode 的 32 位值只要有一位发生改变,整个 hash() 返回值就会改变。尽可能的减少碰撞。


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

A:①、table 数组大小是由 capacity 这个参数确定的,默认是16,也可以构造时传入,最大限制是1<<30;
②、loadFactor 是装载因子,主要目的是用来确认table 数组是否需要动态扩展,默认值是0.75,比如table 数组大小为 16,装载因子为 0.75 时,threshold 就是12,当 table 的实际大小超过 12 时,table就需要动态扩容;
③、扩容时,调用 resize() 方法,将 table 长度变为原来的两倍(注意是 table 长度,而不是 threshold)
④、如果数据很大的情况下,扩展时将会带来性能的损失,在性能要求很高的地方,这种损失很可能很致命。


Q:HashMap 的遍历方式及其性能对比

A:主要四种方式:
NO.1:for-each map.keySet() -- 只需要K值的时候,推荐使用

for (String key : map.keySet()) { map.get(key); } 

NO.2:for-each map.entrySet() -- 当需要V值的时候,推荐使用

for (Map.Entry<String, String> entry : map.entrySet()) { entry.getKey(); entry.getValue(); } 

NO.3:for-each map.entrySet() + 临时变量

Set<Map.Entry<String, String>> entrySet = map.entrySet(); for (Map.Entry<String, String> entry : entrySet) { entry.getKey(); entry.getValue(); } 

NO.4:for-each map.entrySet().iterator()

Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, String> entry = iterator.next(); entry.getKey(); entry.getValue(); } 

Q:HashMap,LinkedHashMap,TreeMap 有什么区别?

A:HashMap 参考其他问题;
LinkedHashMap 保存了记录的插入顺序,在用 Iterator 遍历时,先取到的记录肯定是先插入的;遍历比 HashMap 慢;
TreeMap 实现 SortMap 接口,能够把它保存的记录根据键排序(默认按键值升序排序,也可以指定排序的比较器)


Q:HashMap & TreeMap & LinkedHashMap 使用场景?

A:一般情况下,使用最多的是 HashMap
HashMap:在 Map 中插入、删除和定位元素时;
TreeMap:在需要按自然顺序或自定义顺序遍历键的情况下;
LinkedHashMap:在需要输出的顺序和输入的顺序相同的情况下。


Q:HashMap 和 HashTable 有什么区别?

A:①、HashMap 是线程不安全的,HashTable线程安全的;
②、由于线程安全,所以 HashTable 的效率比不上 HashMap;
③、HashMap最多只允许一条记录的键为null,允许多条记录的值为null,而 HashTable 不允许;
④、HashMap 默认初始化数组的大小为16HashTable11,前者扩容时,扩大两倍,后者扩大两倍+1
⑤、HashMap 需要重新计算 hash 值,而 HashTable 直接使用对象的 hashCode


Q:Java 中的另一个线程安全的与 HashMap 极其类似的类是什么?同样是线程安全,它与 HashTable 在线程同步上有什么不同?

A:ConcurrentHashMap 类(是 Java并发包 java.util.concurrent 中提供的一个线程安全且高效的 HashMap 实现)。
HashTable 是使用 synchronize 关键字加锁的原理(就是对对象加锁);
而针对 ConcurrentHashMap,在 JDK 1.7 中采用 分段锁的方式;JDK 1.8 中直接采用了CAS(无锁算法)+ synchronized


Q:HashMap & ConcurrentHashMap 的区别?

A:除了加锁,原理上无太大区别。

另外,HashMap 的键值对允许有null,但是ConCurrentHashMap 都不允许


Q:为什么 ConcurrentHashMap 比 HashTable 效率要高?

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

ConcurrentHashMap
JDK 1.7 中使用分段锁(ReentrantLock + Segment + HashEntry),相当于把一个 HashMap 分成多个段,每段分配一把锁,这样支持多线程访问。锁粒度:基于 Segment,包含多个 HashEntry。
JDK 1.8 中使用 CAS + synchronized + Node + 红黑树。锁粒度:Node(首结点)(实现 Map.Entry<K,V>)。锁粒度降低了。

有可能问道你都知道哪些常用的Map集合?

  HashMap、HashTable、LinkedHashMap、ConcurrentHashMap。

Collection集合接口和Map接口有什么关系?

  没关系,Collection是List、Set父接口不是Map父接口。

HashMap是线程安全的吗?线程安全的Map都有哪些?性能最好的是哪个?

  HashMap不是线程安全的。线程安全的有HashTable、ConcurrentHashMap、SynchronizedMap,性能最好的是ConcurrentHashMap。

使用HashMap有什么性能问题吗?

  使用HashMap要注意避免集合的扩容,它会很耗性能,根据元素的数量给它一个初始大小的值。

HashMap的数据结构是怎样的?默认大小是多少?内部是怎么扩容的?

  HashMap是数组和链表组成的,默认大小为16,当hashmap中的元素个数超过数组大小*loadFactor(默认值为0.75)时就会把数组的大小扩展为原来的两倍大小,然后重新计算每个元素在数组中的位置。

怎么按添加顺序存储元素?怎么按A-Z自然顺序存储元素?怎么自定义排序? 

 按添加顺序使用LinkedHashMap,按自然顺序使用TreeMap,自定义排序TreeMap(Comparetor c)。

HashMap的链表结构设计是用来解决什么问题的?

  HashMap的链表结构设计是用来解决key的hash冲突问题的。

HashMap的键、值可以为NULL吗?HashTable呢?

  HashMap的键值都可以为NULL,HashTable不行。

HashMap使用对象作为key,如果hashcode相同会怎么处理?

  key的hash冲突,如果key equals一致将会覆盖值,不一致就会将值存储在key对应的链表中。

HashMap中的get操作是什么原理?

  先根据key的hashcode值找到对应的链表,再循环链表,根据key的hash是否相同且key的==或者equals比较操作找到对应的值。

HashMap 和 Hashtable 有什么区别?

  存储:HashMap 允许key 和 value 为 null,⽽ Hashtable 不允许。
  线程安全:Hashtable 是线程安全的,⽽ HashMap 是⾮线程安全的。
  推荐使⽤:在 Hashtable 的类注释可以看到,Hashtable 是保留类不建议使⽤,推荐 在 单 线 程 环 境 下 使 ⽤ HashMap 替 代 , 如 果 需 要 多 线 程 使 ⽤ 则 ⽤ConcurrentHashMap 替代。

如何决定使⽤ HashMap 还是 TreeMap?

  对于在 Map 中插⼊、删除、定位⼀个元素这类操作,HashMap 是最好的选择,因为相对⽽⾔ HashMap 的插⼊会更快,但如果你要对⼀个 key 集合进⾏有序的遍历,那TreeMap 是更好的选择。 

说⼀下 HashMap 的实现原理?

  HashMap 基于 Hash 算法实现的,我们通过 put(key,value)存储,get(key)来获取。当传⼊ key 时,HashMap 会根据 key. hashCode() 计算出 hash 值,根据 hash 值将value 保存在 bucket ⾥。当计算出的 hash 值相同时,我们称之为 hash 冲突,HashMap 的做法是⽤链表和红⿊树存储相同 hash 值的 value。当 hash 冲突的个数⽐较少时,使⽤链表否则使⽤红⿊树。
  ⾃⼰理解:两个不同名称的key但是计算出的hashCode值相同的数据存储在链表或者红⿊树中。

哪些集合类是线程安全的?(可能问道)

  Vector、Hashtable、Stack 都是线程安全的,⽽像 HashMap 则是⾮线程安全的,不过在 JDK 1.5 之后随着 Java. util. concurrent 并发包的出现,它们也有了⾃⼰对应的线程安全类,⽐如 HashMap 对应的线程安全类就是 ConcurrentHashMap。 

猜你喜欢

转载自www.cnblogs.com/yjc1605961523/p/12505712.html