大家好,我是三叔,很高兴这期又和大家见面了,一个奋斗在互联网的打工人。
什么是hash表
哈希表是一种常见的数据结构,也被称为散列表(Hash Table)或哈希映射(Hash Map)。它通过使用哈希函数(Hash Function)将键(Key)映射到存储桶(Bucket)中,以实现高效的数据存储和检索。
hash表常见的数据结构有:数组、Set、Map。
- 数组:哈希表中关键码就是数组的索引下标,然后通过下标直接访问数组中的元素。
数组是一种最基本的数据结构,它可以连续地存储多个相同类型的元素。数组的访问速度非常快,因为可以通过索引直接访问元素。然而,数组的大小是固定的,一旦创建后无法动态改变。此外,数组不提供内置的键值对关系。
适用场景:当需要使用一个简单的、固定大小的数据结构来存储一组元素时,可以选择数组。例如,存储一组整数、浮点数或自定义对象等。
例如要查询一个班级学生是否在这所学校里,要枚举的话时间复杂度是O(n),但如果使用哈希表的话, 只需要O(1)就可以做到。
只需要初始化把这所学校里学生的名字都存在哈希表里,在查询的时候通过索引直接就可以知道这位同学在不在这所学校里了。将学生姓名映射到哈希表上就涉及到了hash function ,也就是哈希函数。
哈希函数如下图所示,通过hashCode把名字转化为数值,一般hashcode是通过特定编码方式,可以将其他数据格式转化为不同的数值,这样就把学生名字映射为哈希表上的索引数字了。
如果hashCode得到的数值大于 哈希表的大小了,也就是大于tableSize了,怎么办呢?
此时为了保证映射出来的索引数值都落在哈希表上,我们会在再次对数值做一个取模的操作,就要我们就保证了学生姓名一定可以映射到哈希表上了。
- Set
Set是一种集合类型的数据结构,它存储一组唯一的元素,不允许重复。Set的实现类包括HashSet、LinkedHashSet和TreeSet。HashSet使用哈希表实现,具有较快的插入和查找速度;LinkedHashSet在HashSet的基础上维护了元素的插入顺序;TreeSet使用红黑树实现,可以对元素进行排序。
适用场景:当需要存储一组唯一元素,并且不关心元素的顺序时,可以选择Set。常见的应用包括去重 等。
- Map
Map是一种键值对的数据结构,每个元素由一个键和一个对应的值组成。Map的实现类包括HashMap、LinkedHashMap和TreeMap。HashMap使用哈希表实现,提供了快速的键值对存储和查找;LinkedHashMap在HashMap的基础上维护了键值对的插入顺序;TreeMap使用红黑树实现,可以对键进行排序。
适用场景:当需要按照键值对关系存储和检索数据时,可以选择Map。常见的应用包括缓存、索引、配置信息等。
那谁的效率更高?
如果数据量不大的情况下,使用数组效率更高,Set和Map进行映射需要时间;如果是数据量比较大,使用Set和Map效率更高,因为底层是树,树的查找效率更高。
hash碰撞
既然是hash函数进行计算,数据量足够多的时候或者hash函数分布不够均匀,总是会有不同学生的hash值一样,那么就会产生hash碰撞。
如图所示,小李和小王都映射到了索引下标 1 的位置,这一现象叫做哈希碰撞。哈希碰撞指的是不同的键通过哈希函数计算后得到相同的哈希值,导致它们被映射到同一个存储桶中。哈希碰撞是不可避免的,因为哈希函数的输出范围有限,而键的数量可能是无限的。
hash碰撞常见处理方法
- 拉链法:
将发生冲突的数据通过链表的形式进行存储:
- 放寻址法
开放寻址法是另一种处理哈希碰撞的方法。当发生碰撞时,它尝试将碰撞的键值对直接存储在其他可用的存储桶中,而不是创建链表。一种常见的开放寻址法是线性探测法,它在发生碰撞时顺序地检查下一个存储桶,直到找到一个空的存储桶。 - 再哈希法(多次hash)
再哈希法是一种解决哈希碰撞的策略,它通过应用另一个哈希函数来处理碰撞。当发生碰撞时,使用第二个哈希函数重新计算键的哈希值,并将键值对放置在新的存储桶中。如果新的存储桶也发生碰撞,则继续使用再哈希法,直到找到一个空的存储桶。再哈希法可以减少碰撞的概率,但需要选择和设计合适的哈希函数。 - 负载因子和动态调整
负载因子是指哈希表中存储的键值对数量与存储桶数量的比值。为了保持哈希表的性能,通常需要控制负载因子的大小。当负载因子超过某个阈值时,可以动态调整哈希表的大小,即增加存储桶的数量。调整哈希表大小时,需要重新计算键的哈希值,并重新分配键值对到新的存储桶中。
在Java中,常见的哈希表实现(如HashMap、LinkedHashMap)已经内置了处理哈希碰撞的策略,采用了链表法和开放寻址法的组合,以及负载因子和动态调整的机制。它们通过优化的哈希函数和合理的碰撞处理策略,提供了高效的键值对存储和检索功能。
最后
哈希表是一种重要的数据结构,通过哈希函数将键映射到存储桶中。哈希碰撞是不可避免的,但我们可以通过链表法、开放寻址法、再哈希法以及负载因子的动态调整等策略来处理碰撞,以提高哈希表的性能和效率。在实际应用中,我们需要根据具体场景和需求选择合适的哈希表实现和碰撞处理策略。