热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

HashMap的构造函数,hash,tableSizeFor的源码分析

一,静态默认参数默认的初始容量16,且实际容量是2的整数幂,00000001 向左移动4位00010000staticfinalintDEFAULT_INITIAL_CAPAC

一,静态默认参数


//默认的初始容量16,且实际容量是2的整数幂,0000 0001 =>向左移动4位 => 0001 0000

static final int DEFAULT_INITIAL_CAPACITY = 1 <<4;

//最大容量(传入容量过大将被这个值替换) 

//00000000 0000000 00000000 00000001 =>向左移动30位 => 01000000 00000000 00000000 00000000

static final int MAXIMUM_CAPACITY = 1 <<30;

// 默认加载因子为0.75(当表达到3/4满时,才会再散列),这个因子在时间和空间代价之间达到了平衡.更高的因子可以降低表所需的空间,但是会增加查找代价,而查找是最频繁操作

static final float DEFAULT_LOAD_FACTOR = 0.75f;

//桶的树化阈值:即 链表转成红黑树的阈值,在存储数据时,当链表长度 >= 8时,则将链表转换成红黑树

static final int TREEIFY_THRESHOLD = 8;

// 桶的链表还原阈值:即 红黑树转为链表的阈值,当在扩容(resize())时(HashMap的数据存储位置会重新计算),在重新计算存储位置后,当原有的红黑树内数量 <= 6时,则将 红黑树转换成链表

//最小树形化容量阈值:即 当哈希表中的容量 > 该值时,才允许树形化链表 (即 将链表 转换成红黑树)

static final int UNTREEIFY_THRESHOLD = 6;

 



二,构造函数

1. 无参构造函数


2. 传入初始容量,调用this


初始化capacity和扩容的阈值


tableSizeFor()的主要功能是返回一个比给定整数大且最接近的2的幂次方整数,如给定16,返回2的4次方16.


逐行分析



  • int n = cap - 1

    给定的cap 减 1,为了避免参数cap本来就是2的幂次方,这样一来,经过后续操作,cap将会变成2 * cap,是不符合我们预期的



  • n |= n >>> 1

    n >>> 1 : n无符号右移1位,即n二进制最高位的1右移一位

    n | (n >>> 1) 导致 n二进制的高2位值为1

    目前n的高1~2位均为1



  • n |= n >>> 2

    n继续无符号右移2位

    n | (n >>> 2) 导致n二进制表示的高34位经过运算值均为1

    目前n的高14位均为1



  • n |= n >>> 4

    n继续无符号右移4位

    n | (n >>> 4) 导致n二进制表示的高58位经过运算值均为1

    目前n的高18位均为1



  • n |= n >>> 8

    n继续无符号右移8位

    n | (n >>> 8) 导致n二进制表示的高916位经过运算值均为1

    目前n的高116位均为1



可以看出,无论给定cap(cap
当然如果经过运算值大于MAXIMUM_CAPACITY,直接选用MAXIMUM_CAPACITY.


 

所以最终tableSizeFor(20)的结果是 32(2的5次方)


三,hash算法


在Java8中,HashMap中key的Hash值由Hash(key)方法计得,HashMap中存储数据table的index是由key的Hash值决定的. 在HashMap存储数据时,我们期望数据能均匀分布,以防止哈希冲突.
为什么cap要保持为2的幂次方?


自然而然我们就会想到去用%取余操作来实现我们这一构想


取余(%)操作 : 如果除数是2的幂次则等价于与其除数减一的与(&)操作.


这也就解释了为什么一定要求cap要为2的幂次方.再来看看table的index的计算规则:

等价于:









1


index = e.hash % newCap


采用二进制位操作&,相对于%,能够提高运算效率,这就是cap的值被要求为2幂次的原因。


(数组长度-1)正好相当于一个“低位掩码”

“与”操作的结果就是散列值的高位全部归零,只保留低位值,用来做数组下标访问

以初始长度16为例,16-1=15

2进制表示是00000000 00000000 00001111

和某散列值做“与”操作如下,结果就是截取了最低的四位值

但这时候问题就来了,这样就算我的散列值分布再松散,要是只取最后几位的话,碰撞也会很严重

这时候“扰动函数”的价值就体现出来了

右位移16位,正好是32位一半,自己的高半区和低半区做异或,就是为了混合原始hashCode的高位和低位,以此来加大低位的随机性

而且混合后的低位掺杂了高位的部分特征,这样高位的信息也被变相保留下来。

index的运算规则是









1


e.hash & (newCap - 1)


newCap是2的幂,所以newCap - 1的高位全0

若e.hash值只用自身的hashcode,index只会和e.hash的低位做&操作.这样一来,index的值就只有低位参与运算,高位毫无存在感,从而会带来哈希冲突的风险

所以在计算key的hashCode时,用其自身hashCode与其低16位做异或操作

这也就让高位参与到index的计算中来了,即降低了哈希冲突的风险又不会带来太大的性能问题

参考自:

https://www.nowcoder.com/discuss/151172

https://www.jianshu.com/p/ee0de4c99f87



推荐阅读
  • Java集合详解5:深入理解LinkedHashMap和LRU缓存
    Java集合详解5:深入理解LinkedHashMap和LRU缓存今天我们来深入探索一下LinkedHashMap的底层原理,并且使用linkedhashmap来实现LRU缓存。具体代码在我的 ... [详细]
  • 转载自:http:www.blogjava.netCarpenterLeearchive20160427430268.html总体介绍之所以把HashSet和HashMa ... [详细]
  • 缓存这个东西就是为了提高运行速度的,由于缓存是在寸土寸金的内存里面,不是在硬盘里面,所以容量是很有限的。LRU这个算法就是把最近一次使用时间离现在时间最远的数据删除掉。先说说List:每 ... [详细]
  • 01Map集合概述A:Map集合概述:我们通过查看Map接口描述,发现Map接口下的集合与Collection接口下的集合,它们存储数据的形式不同a:Collection中的集合 ... [详细]
  • HashTable与ConcurrentHashMap均可实现HashMap的功能,对外提供了键值对存储的数据结构。但是在内部结构及实现上有何区别,性能上的差异到底在哪里又是如何导致的 ... [详细]
  • 手写HashMap,快手面试官直呼内行
    手写HashMap,快手面试官直呼内行-手写HashMap?这么狠,面试都卷到这种程度了?第一次见到这个面试题,是在某个不方便透露姓名的Offer收割机大佬的文章:这……我当 ... [详细]
  • 本指南从零开始介绍Scala编程语言的基础知识,重点讲解了Scala解释器REPL(读取-求值-打印-循环)的使用方法。REPL是Scala开发中的重要工具,能够帮助初学者快速理解和实践Scala的基本语法和特性。通过详细的示例和练习,读者将能够熟练掌握Scala的基础概念和编程技巧。 ... [详细]
  • 如何利用Java 5 Executor框架高效构建和管理线程池
    Java 5 引入了 Executor 框架,为开发人员提供了一种高效管理和构建线程池的方法。该框架通过将任务提交与任务执行分离,简化了多线程编程的复杂性。利用 Executor 框架,开发人员可以更灵活地控制线程的创建、分配和管理,从而提高服务器端应用的性能和响应能力。此外,该框架还提供了多种线程池实现,如固定线程池、缓存线程池和单线程池,以适应不同的应用场景和需求。 ... [详细]
  • 本文探讨了 Java 中 Pair 类的历史与现状。虽然 Java 标准库中没有内置的 Pair 类,但社区和第三方库提供了多种实现方式,如 Apache Commons 的 Pair 类和 JavaFX 的 javafx.util.Pair 类。这些实现为需要处理成对数据的开发者提供了便利。此外,文章还讨论了为何标准库未包含 Pair 类的原因,以及在现代 Java 开发中使用 Pair 类的最佳实践。 ... [详细]
  • 在前文探讨了Spring如何为特定的bean选择合适的通知器后,本文将进一步深入分析Spring AOP框架中代理对象的生成机制。具体而言,我们将详细解析如何通过代理技术将通知器(Advisor)中包含的通知(Advice)应用到目标bean上,以实现切面编程的核心功能。 ... [详细]
  • 哈希表(Hash Table)是一种高效的查找算法,与传统的链表和树结构相比,其在查找过程中无需进行逐个元素的比较。本文将深入探讨哈希表的基本原理、应用场景以及优化策略,帮助读者全面理解其在实际开发中的优势和局限性。通过实例分析和代码示例,我们将展示如何有效利用哈希表提高数据处理效率,并解决常见的冲突问题。 ... [详细]
  • HashMap:键值对(key-value):通过对象来对对象进行索引,用来索引的对象叫做key,其对应的对象叫做value.默认是1:1关系:存在则覆盖,当key已经存在,则利用新的va ... [详细]
  • 类Hashtable<K,V>所有已实现的接口:Serializable,Cloneable,Map<K,V>此类实现一个哈希表,该哈希表将键映 ... [详细]
  • 本篇文章给大家分享的是有关Java中怎么对HashMap按键值排序,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话 ... [详细]
  • 在Java中有多种遍历HashMap的方法,注意Java中所有的Map类型都实现了共有的Map接口,所以接下来方法适用于所有Map(如:HaspMap,TreeMap,Linked ... [详细]
author-avatar
没搜摸索摸索_685
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有