前两个要求容易达到,第三个要求几乎不可能,著名的hash算法MD5\SHA\CRC等哈希算法也无法避免散列冲突
散列冲突无法避免,怎么解决?
核心思想 如果出现散列冲突,就重新探测一个空闲位置,将其插入,如何探测(线性探测),从当前位置依次往后查找,直到找的空闲位置 查找的过程中,通过散列函数映射key到下标,对比下标中的key是否和查找的key相同,不同就依次往下找,直到找到一个空闲位置,还没找到就说明该元素不在散列表中 删除操作,不能直接把要删除的元素设置为空,不然线性探测方法会失效,可以将删除的元素标记为deleted 存在问题当散列表中插入的数据越来越多时,散列冲突发生的可能性就会越来越大,空闲位置会越来越少,线性探测的时间就会越来越久。极端情况下,我们可能需要探测整个散列表,所以最坏情况下的时间复杂度为 O(n)。同理,在删除和查找时,也有可能会线性探测整张散列表,才能找到要查找或者删除的数据。 还有两种经典的探测方法二次探测(探测的步长变为原来的二次方),双重散列(使用多个散列函数,当地一个散列函数位置被占领,使用第二个散列函数)
无论哪种探测,都要保证装载因子足够大 散列表的装载因子=填入表中的元素个数/散列表的长度
链表法比较常用,简单。 插入时间复杂度O(1) 查找和删除时间复杂度O(k),k为链表的长度,k= n/m n为元素的个数,m为槽的个数
word怎么进行英文单词拼写检查?
假设我们有 10 万条 URL 访问日志,如何按照访问次数给 URL 排序?
有两个字符串数组,每个数组大约有 10 万条字符串,如何快速找出两个数组中相同的字符串?
优点
散列表数据储存在数组中,可以有效利用cpu缓存加快查询速度。序列化实现简单当数据量比较小、装载因子小的时候,适合采用开放寻址法,这也是 Java 中的ThreadLocalMap使用开放寻址法解决散列冲突的原因缺点
删除数据比较麻烦,需要特殊标记已经删除的数据冲突的代价更高,对比链表法,装载因子不能太高,因为一旦过高,效率损失太大浪费内存空间优点
内存利用率高,链表可以需要的时候再创建,不需要像开放寻址法那样事先申请好。对大的装载因子的容忍度高,开放寻址装载因子不能大于等于1,链表法的装载因子变为10,查找效率也不会下降很多缺点
链表法需要存储指针,对于较小对象储存非常消耗内存链表节点是零散分布在内存中,不连续,对cpu缓存是不友好的,这方面执行效率也会降低改进
可以将链表改进为其他更高效的动态数据结构,例如 、跳表、红黑树、这样即使出现散列冲突,最差查找时间为O(logn)基于链表的散列冲突处理方法比较适合存储大对象、大数据量的散列表,而且,比起开放寻址法,它更加灵活,支持更多的优化策略,比如用红黑树代替链表初始大小
初始默认值大小为16默认值可以设置,预先知道数据规模,可以减少动态扩容,提升效率装载因子和动态扩容
最大装载因子默认为0.75当装载因子超过0.75,就会动态扩容每次扩容为原来的2倍散列冲突解决方法
HashMap 底层采用链表法来解决冲突即使负载因子和散列函数设计得再合理,也免不了会出现拉链过长的情况,一旦出现拉链过长,则会严重影响 HashMap 的性能引入红黑树,当链表长度超过(默认为8)时,链表转换为红黑树链表长度小于8时,转化为链表,因为红黑树要维护平衡,数据量较小时,优势不明显散列函数
设计简单高效、分布均匀。 int hash(Object key) { int h = key.hashCode(); return (h ^ (h >>> 16)) & (capicity -1); //capicity表示散列表的大小 } // String类型的对象hashCode() public int hashCode() { int var1 = this.hash; if(var1 == 0 && this.value.length > 0) { char[] var2 = this.value; for(int var3 = 0; var3 < this.value.length; ++var3) { var1 = 31 * var1 + var2[var3]; } this.hash = var1; } return var1; }结合已经学习过的散列知识,我觉得应该有这样几点要求:
支持快速地查询、插入、删除操作;内存占用合理,不能浪费过多的内存空间;性能稳定,极端情况下,散列表的性能也不会退化到无法接受的情况。如何实现这样一个散列表呢? 从这三个方面来考虑设计思路:
设计一个合适的散列函数;定义装载因子阈值,并且设计动态扩容策略;选择合适的散列冲突解决方法。关于散列函数、装载因子、动态扩容策略,还有散列冲突的解决办法,我们前面都讲过了,具体如何选择,还要结合具体的业务场景、具体的业务数据来具体分析。不过只要我们朝这三个方向努力,就离设计出工业级的散列表不远了
LRU算法
一个按照时间从大到小的有序排列的链表结构缓存大小有限,需要淘汰一个数据,就将链表头部节点删除当要缓存某个元素时,先在链表中查找,没有找到就插入末尾找到,就移动到末尾查找需要遍历,所以时间复杂度为O(n) 散列表优化单纯使用链表,添加、删除、查找都要经过查找的操作,时间复杂度O(n)散列表和链表结合,优化三个操作都为O(1)散列表+双向链表,多了一个hnext,是为了散列表冲突时,连接冲突节点查找通过散列表,时间复杂度为O(1)删除,通过散列表查找到删除掉,时间复杂度为O(1)添加,稍微复杂,先查找是否存在,存在的话,移动到链表尾部,时间复杂度为O(1)