散列表
1.原理:基于数组下标可随机访问原理,用于快速查询某个数据,时间复杂度O(1),但不绝对,跟装载因子,散列函数,冲突函数啥的都有关系
组成:table=Hash(key),key是原始值,table表示经过哈希处理的哈希值,Hash表示哈希函数,是table 与key的一种映射关系.
散列函数选取原则:散列函数得到的值非负;对于key1=key2,Hash(key1)==Hash(key2),同样的,对于
key1!=key2,Hash(key1)!=Hash(key2)
2.散列冲突解决办法:
(1)链表法:冲突的部分放那个位置,加链表扩展位置
(2)开放寻址法:hash后的位置被占用,则挪到其他位置。比如线性探测(遇到冲突,table=Hash(key)+0,table=Hash(key)+1,table=Hash(key)+2 往后移一位,直到遇到空位置),二次探测(table=Hash(key)+0,table=Hash(key)+12,table=Hash(key)+22),双重散列(两个hash函数,一个冲突用另一个)
装载因子=填入表中的元素个数/表的长度,表征散列表空位程度,越大空位越小。
3.两种方法的对比:
(1)链表法:对内存运用率高,开放地址装载因子只能小于等于1,而链表法装载因子很大只是链表变长而已。但存储链表需要指针,对小对象耗内存,但大对象可以忽略这一点,而且链表占据内存不连续,对CPU不友好。适合大对象大数据量,而且可以进一步优化为红黑树,如LinkedHashMap(这个数据结构是用散列表和双向链表结合实现的,LRU机制)
(2)开放地址法:可以利用CPU缓存加快速度,序列化比较容易,但冲突发生几率高,处理冲突比较麻烦,负载因子不能过大,占用空间大。数据小装载因子小的时候,适合用开放地址办法,如ThreadLocalMap
几个处理的小方法:
(1)删除时,不是真的删除,而是标记为deleted,这样再查找时不会破坏原有的结构
(2)遇到扩容出了新数组,在插入时,有时数据太大不能忍受搬迁的过程,可以插入一个数据的同时搬迁原数组的数据,慢慢搬移,这样每个数据插入的时间复杂度依然是O(1),这样在查找时,就应该现在新数组查找,找不到再去原数组查找
JAVA实例:HashMap
初始大小16,
装载因子 0.75,
动态扩容一次扩大为原来的二倍,
冲突解决方法:链表法,JDK1.8后,链表长度大于8用红黑树,小于8自动退化为链表
散列函数:
int hash(Object key) {
int h = key.hashCode();
return (h ^ (h >>> 16)) & (capitity -1); //capicity 表示散列表的大小
}
4.哈希算法:
散列表是哈希算法的一个应用,其他的还有数字摘要,安全加密,还有判断信息完整性的作用。
哈希一致性用于解决扩容时搬迁大量数据问题