赞
踩
散列表又称为哈希表,英文“Hash Table”。
散列表思想利用的是数组支持按照下标随机访问数据的特性。
散列函数:
通过key,计算出value,value就是表示将数据放入数组的那个位置。
哈希冲突:
不同的key,可能会得到相同的value,这是就产生了哈希冲突。
解决哈希冲突可以使用开放寻址法(线性探测、二次探测、双散列等)或者链地址法来解决。
装载因子:
装载因子 = 散列表中存储的数据个数/散列表的大小
装载因子越大,说明散列表中的元素越多,空闲位置越少,散列冲突的概率就越大。不仅插入数据的过程要多次寻址或者拉很长的链,查找的过程也会因此变得很慢。
Redis中采用的散列函数有:times33和siphash算法,PHP中也采用了times33作为散列函数,还使用了或运算。
当装载因子超过设置的阈值(一般是0.75)的时候,就需要对散列表重新扩容,这样就会导致所有数据的重新散列。如果当前散列表中的数据很大,需要把散列表扩容为原大小的2倍,则之前的数据都需要重新扩容,这个过程很消耗时间,因此扩容操作不能一次性完成。
Redis中的底层数据结构字典就使用了散列表结构,它的扩容方式是:
开放寻址法
优点:
缺点:
适用性:
链表法
优点:
缺点:
适用性:
链表法解决哈希冲突的优化
在Java8中,HashMap底层实现就用了散列表,解决冲突的办法是链地址法+红黑树,当链表长度超过8的时候,链表就会转换为红黑树,当红黑树节点小于6的时候,红黑树又会装换为链表。
总之,散列表的设计要满足的要求为:
有些时候,我们需要维护元素的有序性,即元素取出时按照插入的顺序有序,而散列表中的元素是经过散列函数打乱顺序的。
使用散列表和链表的组合方式维护元素输出的有序性,大概就像下面这样:
在PHP的数组底层实现中,由于要保证元素的增、删性能,因此也用了哈希表数据结构来实现。下图中显示的是映射表(类似哈希表的作用)的-2号位置指向了数组的0号位置:
当再插入一个元素的时候,如果该元素的哈希值也是哈希表中的-2号位置,由于新元素要存入数组的1号位置(保证了数组的地址连续性和有序性),因此将哈希表-2号位置的值由0变成1,然后数组1号位置的数据的next指针指向数组0号位置,如下图所示:
这样既解决了哈希冲突,又解决了元素的有序性,还是挺巧妙的。
哈希表在Redis底层数据结构中的应用:《Redis源码解析(3)字典》
哈希表在PHP底层数据结构中的应用:《PHP数组源码解读和底层实现分析》
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。