Map面试常见问题

Map的特点有哪些?

Java中的Map是一种接口,它表示一种将键映射到值的对象。Map的特点主要有以下几点:

  1. 键的唯一性:每个键在Map中只能出现一次,不能重复。
  2. 不保证键的顺序:Map不保证键的插入顺序或者遍历顺序。例如,HashMap在迭代时键的顺序与插入顺序可能不一致。
  3. 可以为null的键和值:Map允许使用null作为键和值。但是需要注意的是,如果使用null作为键,那么在获取值时可能会抛出NullPointerException。
  4. Map不是线程安全的:如果多个线程同时修改Map,可能会引发并发问题。如果需要线程安全的Map,可以使用ConcurrentHashMap。

Map有几种类型?

Java中的Map接口有两个主要的实现类:HashMap和TreeMap。

  1. HashMap:HashMap是一种基于哈希表的Map实现。它的主要特点是快速查询,即在给定键的情况下,能够在O(1)时间内获取对应的值。但是它不保证键的顺序。
  2. TreeMap:TreeMap是一种基于红黑树的Map实现。它的主要特点是按键的顺序存储和遍历元素。可以通过构造器传入一个自定义的Comparator来改变默认的按键顺序。

除了这两种基本的实现,Java还提供了其他一些Map的实现类,如LinkedHashMap、WeakHashMap等,它们各有自己的特点和适用场景。
Map面试常见问题_第1张图片
Map面试常见问题_第2张图片

​​​

HashMap数据结构是什么?

Java中的HashMap底层采用的是数组+链表(JDK1.8之前)或数组+链表+红黑树(JDK1.8之后)的数据结构。

在JDK1.8之前,HashMap内部采用数组+链表的结构来存储数据,数组是HashMap的主结构,而链表则是用来处理冲突的。当插入元素时,首先根据元素的hashCode值计算出在数组中的索引位置,如果该位置没有元素,直接将元素存储在该位置。如果有元素,则将该元素与索引位置处的元素形成链表,并比较两个元素的key值,如果key值相同,则将value值较大的元素放在链表的头,这样就可以保证每次遍历都能按照key值排序。

在JDK1.8之后,HashMap内部采用数组+链表+红黑树的结构来存储数据。当数组的长度大于一定阈值时,会将数组转化为红黑树,这样可以提高查找的效率。当红黑树的节点数量少于一定阈值时,会将红黑树转化为链表,这样可以减少树结构的维护成本。当链表的长度大于一定阈值时,会将链表转化为红黑树,这样可以保证链表在插入和删除节点时都能保持O(log n)的时间复杂度。

总之,HashMap底层的数据结构是不断优化的,以提供更好的性能和可扩展性。

HashMap是如何进行扩容的?

Java中的HashMap是一种常用的数据结构,用于存储键值对数据。HashMap的实现使用了散列(hashing)技术,通过将键转化为数组的索引,可以在O(1)的平均时间复杂度内完成数据的插入、查找和删除操作。

当HashMap中的元素数量达到一定的阈值时,就需要进行扩容操作。扩容操作的过程如下:

  1. 创建一个新的数组,其大小是原数组大小的2倍(或者更大,具体取决于Java的内存分配策略)。
  2. 使用一个新的散列函数,将原数组中的所有键值对重新散列到新数组中。
  3. 将原数组中的元素逐个复制到新数组中。
  4. 更新HashMap的内部变量,使其指向新的数组。

这个过程可能会导致CPU资源的消耗,特别是在大量数据的情况下。因此,为了保证HashMap的性能,需要合理地选择数组的大小。如果数组的大小选择过小,会导致频繁的扩容操作,影响程序的性能;如果数组的大小选择过大,会浪费内存空间。

在Java中,HashMap的扩容操作是由Java的垃圾回收机制自动完成的。当HashMap中的元素数量达到一定的阈值时,Java的垃圾回收机制会自动触发扩容操作。这个阈值是由HashMap的负载因子(load factor)决定的,负载因子是当前元素数量与数组大小的比值。当负载因子大于等于阈值时,就会触发扩容操作。默认的阈值是0.75,但是可以通过构造函数进行设置。

HashMap是线程安全的吗?

HashMap不是线程安全的。

有哪些线程安全的map?

在Java中,有几个并发安全的Map实现可以使用。以下是一些选项:

  1. ConcurrentHashMap​: 这是Java中最常用的并发安全的Map实现。它被设计为线程安全的,并且可以在多线程环境中进行无阻塞(非阻塞)操作。ConcurrentHashMap​使用分段锁机制,允许多个修改操作并行进行。
Map<String, String> map = new ConcurrentHashMap<>();
  1. Collections.synchronizedMap(HashMap)​: 这是一个包装器方法,可以将任何Map包装为线程安全的。但是,由于包装后的Map在单个操作上不是线程安全的,所以在迭代时需要外部同步。
Map<String, String> map = new HashMap<>();
Map<String, String> synchronizedMap = Collections.synchronizedMap(map);
  1. Hashtable​: 这是一个古老的线程安全的Map实现,但它的性能低于ConcurrentHashMap​。它不提供并发性,因此在多线程环境中效率低下。
Map<String, String> map = new Hashtable<>();
  1. ConcurrentSkipListMap​: 这是一个实现了NavigableMap​接口的并发Map。它提供了线程安全和并发性,并且可以进行排序操作。然而,它并不是一个完全的Map实现,因为它不支持所有的Map操作。
Map<String, String> map = new ConcurrentSkipListMap<>();

在选择并发安全的Map实现时,你应该根据你的具体需求来决定使用哪个。例如,如果你需要一个线程安全的Map,并且需要高并发性能,那么ConcurrentHashMap​可能是最好的选择。但是,如果你需要一个可以排序的线程安全的Map,那么ConcurrentSkipListMap​可能更适合你的需求。

你可能感兴趣的:(面试,面试,职场和发展)