HashMap的负载因子默认是0.75?为什么不是1或者0.5,这篇文章将揭晓答案!

探讨Java中HashMap的加载因子为何默认为0.75,解析其背后的原因与考量,包括哈希冲突处理、空间利用率及性能之间的平衡。

点击上方蓝色“终端研发部”,选择“设为星标”

学最好的别人,做最好的我们 

前几天在一个群里看到有人讨论hashmap中的加载因子为什么是默认0.75。

HashMap源码中的加载因子

static final float DEFAULT_LOAD_FACTOR = 0.75f;

当时想到的是应该是“哈希冲突”和“空间利用率”矛盾的一个折衷。
跟数据结构要么查询快要么插入快一个道理,hashmap就是一个插入慢、查询快的数据结构。

加载因子是表示Hsah表中元素的填满的程度。
加载因子越大,填满的元素越多,空间利用率越高,但冲突的机会加大了。
反之,加载因子越小,填满的元素越少,冲突的机会减小,但空间浪费多了。

冲突的机会越大,则查找的成本越高。反之,查找的成本越小。

因此,必须在 “冲突的机会”与”空间利用率”之间寻找一种平衡与折衷。

但是为什么一定是0.75?而不是0.8,0.6

本着不嫌事大的精神继续深挖,在此之前先简单补充点本文需要的基础知识:

1.冲突定义:假设哈希表的地址集为[0,n),冲突是指由关键字得到的哈希地址为j(0<=j<=n-1)的位置上已经有记录。在关键字得到的哈希地址上已经有记录,那么就称之为冲突

2.处理冲突:就是为该关键字的记录扎到另一个“空”的哈希地址。即在处理哈希地址的冲突时,若得到的另一个哈希地址H1仍然发生冲突,则再求下一个地址H2,若H2仍然冲突,再求的H3,直至Hk不发生冲突为止,则Hk为记录在表中的地址。

处理冲突的几种方法:

一、 开放定址法

Hi=(H(key) + di) MOD m i=1,2,…k(k<=m-1)其中H(key)为哈希函数;m为哈希表表长;di为增量序列。

开放定址法根据步长不同可以分为3种:

1)线性探查法(Linear Probing):di=1,2,3,…,m-1
  简单地说就是以当前冲突位置为起点,步长为1循环查找,直到找到一个空的位置就把元素插进去,循环完了都找不到说明容器满了。就像你去一条街上的店里吃饭,问了第一家被告知满座,然后挨着一家家去问是否有位置一样。

2)线性补偿探测法:di=Q 下一个位置满足 Hi=(H(key) + Q) mod m i=1,2,…k(k<=m-1) ,要求 Q 与 m 是互质的,以便能探测到哈希表中的所有单元。
继续用上面的例子,现在你不是挨着一家家去问了,拿出计算器算了一下,然后隔Q家问一次有没有位置。

3)伪随机探测再散列:di=伪随机数序列。还是那个例子,这是完全根据心情去选一家店来问了

缺点:

  • 这种方法建立起来的hash表当冲突多的时候数据容易堆聚在一起,这时候对查找不友好;

  • 删除结点不能简单地将被删结 点的空间置为空,否则将截断在它之后填人散列表的同义词结点的查找路径。因此在 用开放地址法处理冲突的散列表上执行删除操作,只能在被删结点上做删除标记,而不能真正删除结点

  • 当空间满了,还要建立一个溢出表来存多出来的元素。

二、再哈希法

Hi = RHi(key),i=1,2,…k
RHi均是不同的哈希函数,即在同义词产生地址冲突时计算另一个哈希函数地址,直到不发生冲突为止。这种方法不易产生聚集,但是增加了计算时间。

缺点:增加了计算时间。

三、建立一个公共溢出区

假设哈希函数的值域为[0,m-1],则设向量HashTable[0…m-1]为基本表,每个分量存放一个记录,另设立向量OverTable[0….v]为溢出表。所有关键字和基本表中关键字为同义词的记录,不管他们由哈希函数得到的哈希地址是什么,一旦发生冲突,都填入溢出表。

简单地说就是搞个新表存冲突的元素。

四、链地址法(拉链法)

将所有关键字为同义词的记录存储在同一线性链表中,也就是把冲突位置的元素构造成链表。

拉链法的优点:

  • 拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;

  • 由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;

  • 在用拉链法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。

拉链法的缺点:

  • 指针需要额外的空间,故当结点规模较小时,开放定址法较为节省空间,而若将节省的指针空间用来扩大散列表的规模,可使装填因子变小,这又减少了开放定址法中的冲突,从而提高平均查找速度

Java中HashMap的数据结构

HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。

看图就可以知道Java中的hashMap使用了拉链法处理冲突。
HashMap有一个初始容量大小,默认是16

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

为了减少冲突的概率,当hashMap的数组长度到了一个临界值就会触发扩容,把所有元素rehash再放到扩容后的容器中,这是一个非常耗时的操作。

而这个临界值由【加载因子】和当前容器的容量大小来确定:DEFAULT_INITIAL_CAPACITY*DEFAULT_LOAD_FACTOR ,即默认情况下是16x0.75=12时,就会触发扩容操作。

所以使用hash容器时尽量预估自己的数据量来设置初始值。具体代码实现自行去研究HashMap的源码。

基础知识补充完毕,回到正题,为什么加载因子要默认是0.75?
从hashmap源码注释里找到了这一段

Ideally, under random hashCodes, the frequency of

  • nodes in bins follows a Poisson distribution

  • (http://en.wikipedia.org/wiki/Poisson_distribution) with a

  • parameter of about 0.5 on average for the default resizing

  • threshold of 0.75, although with a large variance because of

  • resizing granularity. Ignoring variance, the expected

  • occurrences of list size k are (exp(-0.5) * pow(0.5, k) /

  • factorial(k)). The first values are:

  • 0: 0.60653066

  • 1: 0.30326533

  • 2: 0.07581633

  • 3: 0.01263606

  • 4: 0.00157952

  • 5: 0.00015795

  • 6: 0.00001316

  • 7: 0.00000094

  • 8: 0.00000006

  • more: less than 1 in ten million

注意wiki链接中的关键字:Poisson_distribution
泊淞分布啊

简单翻译一下就是在理想情况下,使用随机哈希码,节点出现的频率在hash桶中遵循泊松分布,同时给出了桶中元素个数和概率的对照表。

从上面的表中可以看到当桶中元素到达8个的时候,概率已经变得非常小,也就是说用0.75作为加载因子,每个碰撞位置的链表长度超过8个是几乎不可能的。

好了,再深挖就要挖到统计学那边去了,就此打住,重申一下使用hash容器请尽量指定初始容量,且是2的幂次方。

关于泊淞分布的知识请看

http://www.ruanyifeng.com/blog/2015/06/poisson-distribution.html#comment-356111

作者:Eric新之助
链接:https://www.jianshu.com/p/dff8f4641814
来源:简书

补充

为什么部位是1或者是0.5

首先说写一下hash的额数据结构,jdk1.8以前是数组+链表,jdk1.8以后是数组+链表+红黑色。

当负载因子是1.0的时候,也就意味着,只有当数组的8个值(这个图表示了8个)全部填充了,才会发生扩容。这就带来了很大的问题,因为Hash冲突时避免不了的。当负载因子是1.0的时候,意味着会出现大量的Hash的冲突,底层的红黑树变得异常复杂。对于查询效率极其不利。这种情况就是牺牲了时间来保证空间的利用率。

负载因子是0.5的时候,这也就意味着,当数组中的元素达到了一半就开始扩容,既然填充的元素少了,Hash冲突也会减少,那么底层的链表长度或者是红黑树的高度就会降低。查询效率就会增加。

负载因子是0.75的时候,空间利用率比较高,而且避免了相当多的Hash冲突,使得底层的链表或者是红黑树的高度比较低,提升了空间效率。

BAT等大厂Java面试经验总结 想获取 Java大厂面试题学习资料扫下方二维码回复「BAT」就好了回复 【加群】获取github掘金交流群回复 【电子书】获取2020电子书教程回复 【C】获取全套C语言学习知识手册回复 【Java】获取java相关的视频教程和资料回复 【爬虫】获取SpringCloud相关多的学习资料回复 【Python】即可获得Python基础到进阶的学习教程回复 【idea破解】即可获得intellij idea相关的破解教程关注我gitHub掘金,每天发掘一篇好项目,学习技术不迷路!



回复 【idea激活】即可获得idea的激活方式
回复 【Java】获取java相关的视频教程和资料
回复 【SpringCloud】获取SpringCloud相关多的学习资料
回复 【python】获取全套0基础Python知识手册
回复 【2020】获取2020java相关面试题教程
回复 【加群】即可加入终端研发部相关的技术交流群
阅读更多
为什么HTTPS是安全的
因为BitMap,白白搭进去8台服务器...
《某厂内部SQL大全 》.PDF
字节跳动一面:i++ 是线程安全的吗?
大家好,欢迎加我微信,很高兴认识你!
在华为鸿蒙 OS 上尝鲜,我的第一个“hello world”,起飞!

相信自己,没有做不到的,只有想不到的在这里获得的不仅仅是技术!



就给个“在看”
<think>好的,我现在要回答用户关于HashMap默认负载因子为什么0.75的问题。首先,我需要回忆一下HashMap的基本原理,负载因子的作用,以及为什么选择这个数值。 首先,负载因子HashMap在扩容之前允许的填充程度。当元素数量超过容量乘以负载因子时,就会触发扩容。比如默认容量16,负载因子0.75时,当元素超过12个就会扩容到32,依此类推。 接下来,用户想知道为什么选择0.75这个特定值。这可能涉及到时间和空间成本的权衡。负载因子高意味着更少的扩容操作,节省空间,但可能导致哈希冲突增加,查找时间变长。负载因子低则相反,空间占用多,但查找效率高。 我记得Java官方文档中有提到,0.75是在时间和空间成本之间的一种折中选择。可能还有数学上的依据,比如泊松分布或者概率论中的最优值。比如,在理想情况下,哈希冲突的概率较低时,0.75可能是一个平衡点,使得链表长度在大部分情况下保持较短,同时减少频繁的扩容。 另外,可能需要考虑哈希表的性能。比如,当负载因子0.75时,哈希表的平均探测次数在开放地址法中可能达到一个较好的平衡。但JavaHashMap使用的是链表和红黑树(当链表过长时),所以可能这个数值经过测试验证,在大多数场景下性能最优。 此外,可能需要解释负载因子哈希冲突的关系。较高的负载因子导致更多冲突,而较低的则减少冲突。但扩容是有成本的,所以需要找到一个合适的阈值。 可能需要引用一些资料或官方说明。例如,在JavaHashMap文档中提到,默认负载因子0.75提供了时间和空间效率的良好平衡。较高的值减少了空间开销,但增加了查找成本。而0.75是两者之间的妥协。 总结起来,答案应该包括负载因子的定义,为什么0.75被选中的原因(时间和空间的平衡,数学或经验依据),可能还提到哈希冲突的影响以及扩容的开销。</think><< HashMap负载因子默认设置为0.75是时间和空间成本之间的折中结果。负载因子(Load Factor)表示哈希表在扩容前允许的填充程度,计算公式为:`元素数量 / 容量`。当元素数量超过`容量 × 负载因子`时,哈希表会触发扩容(容量翻倍,重新哈希)。以下是具体原因: 1. **空间效率与时间效率的平衡** - 负载因子过高(如1.0):虽然减少了内存占用,但会导致哈希冲突概率显著增加,链表或红黑树的查询时间复杂度可能退化到O(n)或O(log n)。 - 负载因子过低(如0.5):虽然减少了哈希冲突,提高了查询效率,但会导致频繁扩容,增加时间和内存的开销。 2. **统计学和数学依据** - 在理想哈希函数下,0.75负载因子对应的哈希冲突概率较低。根据泊松分布,当负载因子0.75时,哈希表中每个桶(Bucket)的链表长度超过8的概率极小(约千万分之一),适合实际应用场景。 3. **经验验证** - 实际测试表明,0.75在大多数场景下能平衡扩容频率和哈希冲突。例如,默认容量16时,扩容阈值是12(16 × 0.75),在插入第13个元素时触发扩容。 **示例代码**:默认负载因子HashMap构造函数中的体现 ```java HashMap<String, Integer> map = new HashMap<>(); // 默认负载因子0.75 HashMap<String, Integer> customLoadMap = new HashMap<>(16, 0.5f); // 自定义负载因子0.5 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值