散列表
散列表来源于数组,它借助散列函数对数组这种数据结构进行扩展,利用的是数组支持按照下标随机访问元素的特性。散列表两个核心问题是散列函数设计和散
列冲突解决。
散列冲突
- 开放寻址法
- 优点:散列表中的数据都存储在数组中,可以有效利用cpu缓存加快查询速度,序列化简单
- 缺点:删除数据的时候比较麻烦,需要特殊标记已经删除掉的数据。负载因子的上限不能太大,更浪费内存
- 适用场景:数据量比较小,装载因子小(ThreadLocalMap使用)
- 链表法
- 比较适合存储大对象,大数据量
- 更加灵活,支持更多的优化策略,比如用红黑树代替链表
问题
- 假设我们有10万条URL访问日志,如何按照访问次数给URL排序?
遍历 10 万条数据,以 URL 为 key,访问次数为 value,存入散列表,同时记录下访问次数的最大值 K,时间复杂度 O(N)。 如果 K 不是很大,可以使用桶排序,时间复杂度 O(N)。如果 K 非常大(比如大于 10 万),就使用快速排序,复杂O(NlogN)。
- 有两个字符串数组,每个数组大约有10万条字符串,如何快速找出两个数组中相同的字符串?
以第一个字符串数组构建散列表,key 为字符串,value 为出现次数。再遍历第二个字符串数组,以字符串为 key 在散列表中查找,如果 value 大于零,说明 存在相同字符串。时间复杂度 O(N)
LRU缓存淘汰算法
image-20191127104608366
我们的散列表是通过链表法解决散列冲突的,所以每个结点会在两条链中。一个链是双向链表,另一个是散列表中的拉链。前驱和后驱指针是为了将结点串在双向链表中,hnext指针是为了将结点串在散列表的拉链中。
Redis有序集合
- 什么是有序集合
- 在有序集合中,每个成员对象有2个重要的属性,即key(键值)和score(分值)
- 不仅会通过score来查找数据,还会通过key来查找数据
Java LinkedHashMap
- 和LRU缓存淘汰策略实现一模一样。支持按照插入顺序遍历数据,也支持按照访问顺序遍历数据。
- 通过双向链表和散列表组合实现的,Linked实际指的是双向链表,并非指用链表法解决散列冲突