美文网首页
《数据结构与算法之美》15——散列表(二)如何实现工业级别的散列

《数据结构与算法之美》15——散列表(二)如何实现工业级别的散列

作者: 大杂草 | 来源:发表于2020-07-21 17:53 被阅读0次

通过上一节的学习,我们知道,散列表的查询效率并不能简单说成是O(1)。它跟散列函数、装载因子、散列冲突等地都有关系。

今天我们来学一下,<font color=red>如何设计一个可以应对各种异常情况的工业级散列表,来避免在散列冲突的情况下,散列表性能的急剧下降,并且能抵抗散列碰撞攻击?</font>

下面我们从散列函数、装载因子、散列冲突等方面介绍一下如何设计。

如何设计散列函数?

1.散列函数的设计不能太复杂。

过于复杂的散列函数,会消耗很多计算时间,间接影响到散列表的性能。

2.散列函数生成的值要尽可能随机并且均匀分布。

这样能避免或最小化散列冲突,即便出现冲突,散列到每个槽的数据也会比较平均。

常用的、简单的散列函数设计方法有数据分析法、ASCII码法、直接寻址法、平方取中法、折叠法、随机数法等等。这些只要了解就行了,不需要全都掌握。

装载因子过大了怎么办?

上一节讲到,散列表的装载因子越大,说明散列表中的元素越多,空闲位置越少,散列冲突的概率越大。

对于动态散列表来说,数据集合是频繁变动的,无法事先申请一个足够大的散列表。随着数据慢慢加入,装载因子就会慢慢变大,到一定程度后,散列冲突就会变得不可接受。这个时候可能通过“动态扩容”来解决。

针对散列列,当装载因子过大时,重新申请一个更大的散列表,将数据搬移到这个新散列表中。假设原来的散列表的装载因子是0.8,扩容2倍空间后,新散列表的装载因子就变成0.4了。

装载因子阈值需要选择得当。如果太大,会导致冲突过多;如果太小,会导致内存浪费严重。

装载因子阈值的设置要权衡时间、空间复杂度。

如何避免低效地扩容?

大部分情况下,动态扩容的散列表插入一个数据都很快,但在特殊情况下,当装载因子已经到达阈值,需要先进行扩容,再插入数据。这时候,插入数据就会变得很慢,甚至会无法接受。

举个极端例子,如果散列表当前大小为1GB,要想扩容为原来的两倍大小,就需要对1GB的数据重新计算哈希值,并从原来的散列表搬移到新的散列表,相当耗时。

为了解决一次性扩容耗时过多的情况,可以将扩容操作穿插在插入操作的过程中,分批完成。当装载因子触达阈值之后,只申请新空间,但并不将老的数据搬移到新的散列表中。

数据搬移优化

通过这样均摊的方法,将一次性扩容的代价,均摊到多次插入操作中,就避免了一次性扩容耗时过多的情况。这种实现方式,插入一个数据的时间复杂度是O(1)。

如何选择冲突解决方法?

1.开放寻址法

当数据量比较小、装载因子小的时候,适合采用开放寻址法。这也是Java中的ThreadLocalMap使用开放寻址法解决散列冲突的原因。

2.链表法
基于链表的散列冲突处理方法比较适合存储大对象、大数据量的散列表,而且比起开放寻址法,它更加灵活,支持更多的优化策略,比如用红黑代替链表。

工业级散列表举例分析

以Java中的HashMap举例分析一下,工业级的散列表是怎么做的。

1.初始大小

HashMap默认的初始大小是16,如果事先知道大概的数据量,可以设置。通过修改默认初始大小,减少动态扩容次数。

2.装载因子和动态扩容

最大装载因子默认是0.75,当HashMap中元素个数超过0.75 * capacity(capacity表示散列表的容量)的时候,就会扩容,每次扩容为原来的两倍大小。

3.散列冲突解决方法

HaspMap采用链表法来解决冲突。在JDK1.8中,引入了红黑树进行优化。

4.散列函数

散列函数的设计并不复杂,追求的是简单高效、分布均匀。

int hash(Object key) {
    int h = key.hashCode();
    return (h ^ (h >>> 16)) & (capitity -1); //capicity表示散列表的大小
}

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;
}

相关文章

  • 《数据结构与算法之美》15——散列表(二)如何实现工业级别的散列

    通过上一节的学习,我们知道,散列表的查询效率并不能简单说成是O(1)。它跟散列函数、装载因子、散列冲突等地都有关系...

  • 排序算法

    什么是算法 书籍推荐 《数据结构与算法分析》 表、栈和队列 树 散列(hash) 优先队列(堆) 排序 定义 问题...

  • HashMap深度分析疑问

    hashMap底层是基于散列算法实现,散列算法分为散列再探测和拉链式。hashmap使用了拉链式散列算法,在jdk...

  • 《数据结构与算法之美》16——散列表(三)为什么散列表和链表经常

    有两种数据结构(散列表和链表)经常会被放在一起使用。前面的章节中有两个地方讲到散列表和链表的组合使用,分别是: 0...

  • 查找

    二分查找适用于有序表查找, 包括二叉排序树 散列查找请看本博客数据结构与算法hash表文章http://www.j...

  • 散列表

    散列算法的作用是尽可能快地在数据结构中找到一个值。散列函数的作用是给定一个键值,然后返回值在表中的地址 基本实现 ...

  • 哈希算法

    什么是哈希算法 了解哈希算法需要了解以下几个概念。 散列表(hash table) 与散列函数 散列表也叫哈希表是...

  • 数据结构与算法

    数据结构与算法之美 数据结构与算法之美1--如何学数据结构与算法之美2--复杂度分析(上)数据结构与算法之美3--...

  • HashMap源码详解一篇就够

    概述 HashMap是基于哈希表(散列表),实现Map接口的双列集合,数据结构是“链表散列”,也就是数组+链表 ,...

  • hashMap源码分析

    hashMap是基于hash表(散列表),实现Map接口得双列集合,数据结构是--链表散列 也就是 数组+链表,k...

网友评论

      本文标题:《数据结构与算法之美》15——散列表(二)如何实现工业级别的散列

      本文链接:https://www.haomeiwen.com/subject/lxdwkktx.html