HashMap 源码分析

基于哈希表的实现的Map接口。 此实现提供了所有可选的map操作,并允许null的值和null键。 ( HashMap类大致相当于Hashtable ,除了它是不同步的,并允许null)。这个类不能保证map的顺序; 特别是,它不能保证订单在一段时间内保持不变。 假设哈希函数在这些存储桶之间正确分散元素,这个实现为基本操作( get和put )提供了恒定的时间性能。 收集视图的迭代需要与HashMap实例(桶数)加上其大小(键值映射数)的“容量” 成正比 。 因此,如果迭代性能很重要,不要将初始容量设置得太高(或负载因子太低)是非常重要的。

HashMap的一个实例有两个影响其性能的参数: 初始容量和负载因子 。 容量是哈希表中的桶数,初始容量只是创建哈希表时的容量。 负载因子是在容量自动增加之前允许哈希表得到满足的度量。 当在散列表中的条目的数量超过了负载因数和电流容量的乘积,哈希表被重新散列 (即,内部数据结构被重建),使得哈希表具有桶的大约两倍。

作为一般规则,默认负载因子(0.75)提供了时间和空间成本之间的良好折中。 更高的值会降低空间开销,但会增加查找成本(反映在HashMap类的大部分操作中,包括get和put )。 在设置其初始容量时,应考虑map中预期的条目数及其负载因子,以便最小化重新组播操作的数量。 如果初始容量大于最大条目数除以负载因子,则不会发生重新排列操作。

如果许多映射要存储在HashMap实例中,则以足够大的容量创建映射将允许映射的存储效率高于使其根据需要执行自动重新排序以增长表。 请注意,使用同一个hashCode()多个密钥是降低任何哈希表的hashCode()的一种方法。 为了改善影响,当按键是Comparable时,这个类可以使用键之间的比较顺序来帮助打破关系。

请注意,此实现不同步。 如果多个线程同时访问哈希映射,并且至少有一个线程在结构上修改了映射,那么它必须在外部进行同步。 (结构修改是添加或删除一个或多个映射的任何操作;仅改变与实例已经包含的密钥相关联的值不是结构修改。)这通常通过对自然地封装映射的一些对象进行同步来实现。 如果没有这样的对象存在,map应该使用Collections.synchronizedMap方法“包装”。 这最好在创建时完成,以防止意外的不同步访问map:

Map m = Collections.synchronizedMap(new HashMap(...)); 所有这个类的“集合视图方法”返回的迭代器都是故障快速的 :如果映射在迭代器创建之后的任何时间被结构地修改,除了通过迭代器自己的remove方法之外,迭代器将抛出一个ConcurrentModificationException 。 因此,面对并发修改,迭代器将快速而干净地失败,而不是在未来未确定的时间冒着任意的非确定性行为。

请注意,迭代器的故障快速行为无法保证,因为一般来说,在不同步并发修改的情况下,无法做出任何硬性保证。 失败快速迭代器尽力扔掉ConcurrentModificationException 。 因此,编写依赖于此异常的程序的正确性将是错误的:迭代器的故障快速行为应仅用于检测错误。

源码分析

内部变量

  • 默认长度

     static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
  • 最大长度

     static final int MAXIMUM_CAPACITY = 1 << 30;
  • 默认负载因子

     static final float DEFAULT_LOAD_FACTOR = 0.75f;
  • 链表转换成树的长度

由于HashMap在JDK8及其以后的数据结构进行了调整,结构为链表加红黑树,当链表的长度大于8时将其转变为红黑树,以减小时间复杂度 java static final int TREEIFY_THRESHOLD = 8;

构造方法

这里介绍一种,看懂了它,其他的构造一看自然就通了

当我创建一个Map时可以指指定它的初始化长度以及负载因子的大小,如:Map<String,String> map = new HashMap<>(16, 0.75f); (1) (2) (3)是对我们传入参数的校验, (4)对负载因子进行赋值, (5)threshold 如果尚未分配表数组,则字段保留初始阵列容量,或零表示默认初始容量

常用方法

put

当我执行put的时候,最终执行的时putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) 这个方法,

  • (1)当我们初次添加时或满足条件时,或当 ++size > threshold = (initialCapacity * loadFactor) 会执行resize()来 初始化或加倍表大小

  • 当满足 (2) 中的计算条件时会进行 new Node<>(hash, key, value, next);

  • 当满足(3)时,说明key以及存在,这时我们替换器value值

  • 如果满足(5) p instanceof TreeNode 则会进行tree的存储

  • 当不满足 以上条件,则会进行(6)

Last updated

Was this helpful?