散列表

本文深入探讨了散列表(哈希表)的概念,包括其作为数组的扩展如何工作,散列函数的设计原则,以及常见的散列冲突解决方案如开放寻址法和链表法。特别分析了Java HashMap的实现细节,如初始大小、装载因子、动态扩容策略和散列函数设计。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是散列表?

散列表也被称“Hash Table”,我们称之为“哈希表”或者“Hash表”。散列表用的是数组支持按照下标随机访问数据的特性,所以散列表其实就是数组的一种扩展,由数组演化而来。可以说,没有数组,就没有散列表。

 

最简单的散列表

最简单的散列表就是key对应数组的下标,value就是数组下标内的元素值。

 

我们把key到value的这个过程赋予一个函数,称为散列函数。散列函数有3个基本要求:

1.散列函数计算的散列值是一个非负整数

2.如果key1 = key2,那hash(key1) == hash(key2)

3.如果key1 != key2,那hash(key1) != hash(key2) ------------- 散列冲突

散列函数的设计尽量使的哈希值随机且均匀分布

 

散列冲突

第三点好理解,但是难实现。目前业界著名的MD5,SHA,CRC等哈希算法也完全无法避免这种哈希冲突。而且,因为数组的存储空间有限,也会加大散列冲突的概率。

 

既然几乎无法找到一个完美的无冲突的散列函数,即使可以,也需要花费很多的人力物力,那么我们针对散列冲突问题,就需要其他途径来解决。

 

开放寻地址法

1.线性探测法

简单来说就是依次找下去

插入:如果发生了散列冲突,就往后找一个空位子,然后插进去,如果后面没了,就从头开始

查询:如果发生了散列冲突,就接着找,直到找到元素(找到)或者找到空的(没找到)

删除:删除要注意的点就是,得标记删除后的格子为deleted,不然在查询的时候会出现问题

 

可以看得出来,这个方法很低效

一般有一个装载因子,装载因子是数组最大可容纳的数据所占的比例。装载因子越大,数组使用率越高,冲突几率越高。javaHashMap一般设定为0.75

 

2.二次探测法

线性探测法是hash(key) + 1,hash(key) + 2 ~~~~~

二次探测法就是hash(key) + 1^2,hash(key) +2^2~~~~~

 

3.双重散列法

双重散列法的意思就是,我们不是使用一个散列函数,而是使用一组散列函数,当hash1(key)被占用的时候,就是用hash2(key),以此类推到hashn(key),直至找到空的格子

 

链表法

这种方法很好理解,就是我接受你的散列冲突,然后在格子里面加个链表。这样的话就算冲突也没事了,删除的话也更好。

 

散列碰撞

以链表法为例,hash(key)为同一个值,然后把数据都塞到同一个槽里面,查询的时间复杂度就是O(1)退化成O(n)。这样就可能因为查询操作消耗大量CPU或线程资源,导致系统无法响应其他请求,从而达到拒绝服务攻击(Dos)的目的。这也就是散列表碰撞攻击的基本原理。

 

如何避免低效的扩容

这只针对于以数组为底层数据结构的散列表。

采用的方式是当使用比例超过装载因子的时候申请一个新的资源,然后新数据和老数据同时插入,老数据一次只插一个,这样子的话,动态扩容过程无感。不会像之前一样整体copy之后再插入。

 

工业级散列表举例分析

1.初始大小

HashMap默认值为16,如果提前知道数据集大小可以提高效率

 

2.装载因子和动态扩容

最大装载因子默认为0.75

当HashMap中元素个数超过0.75*capacity的时候,就会启动扩容,每次为2倍

 

3.散列冲突解决方法

在JDK1.8中,当链表长度太长(默认超过8时),链表就转为红黑树,反之亦然。

 

4.散列函数

设计规则简单高效,随即均匀

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值