散列表 Hashtable

散列表,哈希表,hash表,Hashtable 都是同一个概念

1. 散列表来源于数组,它借助散列函数对数组这种数据结构进行扩展,利用的是数组支持按照下标随机访问元素的特性。

2. 散列函数,即通过一个方法让hash(key)尽可能均匀的分布到预置容器长度内,但几乎不可能避免散列冲突。散列函数的设计不能太复杂。过于复杂的散列函数,势必会消耗很多计算时间,也就间接地影响到散列表的性能。其次,散列函数生成的值要尽可能随机并且均匀分布,这样才能避免或者最小化散列冲突,而且即便出现冲突,散列到每个槽里的数据也会比较平均,不会出现某个槽内数据特别多的情况。

3. 解决散列冲突的办法,一是寻址法(核心思想是:如果出现散列冲突,就重新探测一个空闲位置,将其插入),二是更常用的链表法(插入的时候,我们需要通过散列函数计算出对应的散列槽位,将其插入到对应的链表中即可)。

4. 装载因子(load factor)散列表的装载因子=填入表中的元素个数 / 散列表的长度, 装载因子越大,说明空闲位置越少,冲突越多,散列表的性能会下降。

一般如果装载因子达到阈值,需要对散列表动态扩容,同时也会涉及到数据的搬迁,因为散列表的大小变了,数据的存储位置也变了,所以我们需要通过散列函数重新计算每个数据的存储位置。

5. 工业级散列表的要求

  a.  合理的散列函数,简单,不占用太多cpu时间,让hash(key)尽量少发生碰撞

  b.  合适的初始值,比如java中HashMap,默认是16个,如果知道数据量多少,可以指定合适的初始值,避免频繁搬迁数组

  c.  合适的装载因子,比如0.75阈值只有启动动态扩容

  d. 散列冲突解决,链表法,但数据量很大是,java会采用红黑树结构来加快查询和删除,提高性能,但数据量少于8个时,又退化为简单的链表结构。

6. 应用

  a. 负载均衡

   用户IP或者会话ID利用hash函数计算hash值,然后与服务器列表大小取模,最终得到的值就是服务器编号,这样我们就可以把同一个 IP 过来的所有请求,都路由到同一个后端服务器上。

  b.  数据分片

  场景一,假如我们有 1T 的日志文件,这里面记录了用户的搜索关键词,我们想要快速统计出每个关键词被搜索的次数,该怎么做呢?

  多个机器一起读,读取出关键字,然后利用固定的hash函数计算出hash值,然后对机器数量取模, 这样就得出被分配的机器编号,这样,哈希值相同的搜索关键词就被分配到了同一个机器上。也就是说, 同一个搜索关键词会被分配到同一个机器上。每个机器会分别计算关键词出现的次数,最后合并起来就是最终的结果

  场景二,如何从海量图库中找出图片是否存在?一台计算机内存不够,需要采用分布式存储,再各自机器中构建散列表。

  先把所有图片按照固定的算法得出hahs值,然后与服务器列表总数取模,然后把图片数据分配到对应的一台服务器中,然后这台服务器内部再构建一个hash table,后面查询时,对图片做hash计算,再到对应的服务器的hashtable中去查询是否存在这张图片。

  c. 分布式存储

  海量数据经过hash计算后分配到不同的机器中存储,但是涉及到扩容和缩容操作,需要一致性hash算法,让数据搬移操作最小。

  假设我们有 k 个机器,数据的哈希值的范围是[0, MAX]。我们将整个范围划分成 m 个小区间(m 远大于 k),每个机器负责 m/k 个小区间。当有新机器加入的时候,我们就将某几个小区间的数据,从原来的机器中搬移到新的机器中。这样,既不用全部重新哈希、搬移数据,也保持了各个机器上数据数量的均衡。

特别声明:这里多数内容都是从极客时间里王争老师的《数据结构与算法之美》专栏的例子,感兴趣的同学,可以去查阅。

原文地址:https://www.cnblogs.com/roy1/p/13568836.html