一.什么是哈希碰撞
哈希碰撞是指在哈希表中出现两个或多个键(key)产生了相同的哈希值的情况,这就是哈希冲突或哈希碰撞。在哈希表中,通过哈希函数将键映射到索引的过程中,由于数据的不确定性,可能会使多个键映射到相同的索引位置,这样就会产生哈希碰撞。为了解决哈希碰撞问题,通常会使用一些技术,如散列链表、开放定址、再哈希等。哈希碰撞会导致哈希表的性能下降,因为需要额外的时间来解决冲突。
二.HashMap中装载因子,以及扩容规则
HashMap中的装载因子(load factor)是指当哈希表中存储的元素个数达到总容量的百分比时,就会触发扩容操作。装载因子的大小可以通过构造函数指定,默认值为0.75。
扩容是当HashMap中元素的数目达到阈值时,自动增加容量,调整哈希表的大小,以便容纳更多的元素。根据Java官方文档指出,当HashMap中的元素数量超过负载因子×当前哈希表长度时,就需要对HashMap进行扩容。具体的扩容规则如下:
- 创建一个新的哈希表,大小是原先的两倍。
- 遍历原哈希表中所有的元素,重新计算它们在新哈希表中的位置,并将它们复制到新的哈希表中。
- 将新哈希表赋值给HashMap底层的数组,完成扩容。
注意,扩容涉及到重新哈希所有的原先存储的元素,耗时比较长,因此HashMap的初始容量应该根据需要预估,以减少扩容的次数。
三. HashMap的高频面试题和难点
-
HashMap底层实现原理是什么? HashMap底层实现原理是基于哈希表实现的。具体来说,它是由一个数组和一个链表构成的,每个数组元素被称为桶(bucket),每个桶里面有一个链表,用于解决哈希碰撞的问题。当使用put()方法将键值对存入HashMap时,首先根据键的哈希值计算出对应的桶,然后将键值对添加到相应的链表中。
-
什么情况下会引起HashMap的哈希冲突? 当两个不同的键具有相同的哈希值时,会发生哈希冲突。
-
HashMap的扩容机制是什么? 当HashMap存储的元素数量超过负载因子×当前哈希表长度时,就需要对HashMap进行扩容。具体的扩容规则如下:
- 创建一个新的哈希表,大小是原先的两倍。
- 遍历原哈希表中所有的元素,重新计算它们在新哈希表中的位置,并将它们复制到新的哈希表中。
- 将新哈希表赋值给HashMap底层的数组,完成扩容。
-
如何避免HashMap的哈希碰撞问题? 可以通过以下几种方式避免哈希碰撞:
- 实现好的哈希函数:实现一个好的哈希函数可以将哈希值的分布均匀化,减少哈希碰撞发生的概率。
- 扩容:扩容可以增加哈希表的空间,减少碰撞的概率。
- 开放地址法:开放地址法可以是空间连续,降低哈希冲突的概率。
- 链式存储法:使用链表来解决哈希碰撞问题。
-
HashMap和ConcurrentHashMap有什么区别? HashMap是非线程安全的,而ConcurrentHashMap是线程安全的,可以进行高并发读写操作。ConcurrentHashMap的主要实现方式是对哈希桶进行分段锁定,即将整个HashMap分成多个段,每个段独立进行加锁,这样可以在多线程并发访问中提高性能。
-
如何使用迭代器遍历HashMap? 使用迭代器可以遍历HashMap的元素,可以对键值进行迭代,例如:
Map<String, String> map = new HashMap<>(); map.put("key1", "value1"); map.put("key2", "value2"); Iterator<Map.Entry<String, String>> it = map.entrySet().iterator(); while(it.hasNext()){ Map.Entry<String, String> entry = it.next(); System.out.println("key=" + entry.getKey() + ", value=" + entry.getValue()); } -
如何避免HashMap死循环问题? 在多线程环境下,当HashMap被扩容时,如果有多个线程同时访问HashMap的话,就有可能导致HashMap出现死循环的情况。为了避免此问题,可以使用ConcurrentHashMap来代替HashMap,或者在使用HashMap时,将其限制于单线程环境下,或者采用Collections.synchronizedMap或者手动实现线程同步控制策略等方式来保证线程安全。
四.HashMap HashTable HashTree HashSet TreeSet ConcurrentHashMap 比较
- HashMap:
- 优点:顺序、速度非常快,具备极好的查找性能。
- 缺点:非线程安全,扩容代价较高
- HashTable:
- 优点:与HashMap类似,线程安全,支持并发读写。
- 缺点:同步代价较高,多线程效率低下
- HashTree:
- 优点:对元素有序,能够很快地查找到元素位置。
- 缺点:插入删除效率较低,占用较多内存空间。
- HashSet:
- 优点:存储元素无序,查询速度快。
- 缺点:非线程安全,无序。
- TreeSet:
- 优点:对元素有序,提供有关排序的方法。
- 缺点:插入删除效率较低,比HashSet占用更多的内存空间。
- ConcurrentHashMap:
- 优点:线程安全,高并发读写,性能好。
- 缺点:内部实现较为复杂,可能存在一定的问题风险。
需要根据具体需求和场景选择适合的集合。如果不需要考虑线程安全性和元素有序性,可以选择使用HashMap或HashSet,如果需要考虑线程安全性和高并发读写,可以选择ConcurrentHashMap,如果需要对元素有序并且需要进行排序操作,可以选择TreeSet,如果需要对元素有序但不需要排序操作,可以选择TreeMap或LinkedHashMap。