首页 > 编程语言 >HashMap源码随笔

HashMap源码随笔

时间:2024-01-15 23:22:42浏览次数:40  
标签:bin return HashMap 映射 哈希 hashCode 源码 key 随笔

源码第一块:
概述:
Map 接口的基于哈希表的实现。此实现提供所有可选的映射操作,并允许 null 值和 null 键。(HashMap 类大致等同于 Hashtable,只不过它是不同步的,并且允许 null。此类不保证地图的顺序;特别是,它不保证订单会随着时间的推移保持不变。此实现为基本操作(get 和 put)提供恒定时间性能,假设哈希函数将元素正确地分散在存储桶中。对集合视图进行迭代所需的时间与 HashMap 实例的“容量”(存储桶数量)及其大小(键值映射数量)成正比。因此,如果迭代性能很重要,则不要将初始容量设置得太高(或负载系数太低),这一点非常重要。HashMap 实例有两个影响其性能的参数:初始容量和负载因子。容量是哈希表中的存储桶数量,初始容量只是创建哈希表时的容量。负载因子是衡量哈希表在容量自动增加之前允许的满量。当哈希表中的条目数超过负载因子和当前容量的乘积时,将重新哈希表(即重新构建内部数据结构),以便哈希表的桶数大约是其两倍。作为一般规则,默认负载系数 (0.75) 在时间和空间成本之间提供了良好的权衡。较高的值会减少空间开销,但会增加查找成本(反映在 HashMap 类的大多数操作中,包括 get 和 put)。在设置映射的初始容量时,应考虑映射中的预期条目数及其负载系数,以最大程度地减少重新散列操作的次数。如果初始容量大于最大条目数除以负载因子,则不会发生重新散列操作。如果要在 HashMap 实例中存储许多映射,则创建具有足够大容量的映射将允许更有效地存储映射,而不是让它根据需要执行自动重新散列以增加表。请注意,使用具有相同 hashCode() 的多个键肯定会降低任何哈希表的性能。为了改善影响,当键具有可比性时,此类可以使用键之间的比较顺序来帮助打破联系。请注意,此实现不是同步的。如果多个线程同时访问哈希映射,并且至少有一个线程在结构上修改了映射,则必须在外部同步该映射。(结构修改是添加或删除一个或多个映射的任何操作;仅更改与实例已包含的键关联的值不是结构修改。这通常是通过在自然封装地图的某个对象上进行同步来实现的。如果不存在此类对象,则应使用 Collections.synchronizedMap 方法“包装”映射。这最好在创建时完成,以防止意外的不同步访问地图: Map m = Collections.synchronizedMap(new HashMap(...));此类的所有“集合视图方法”返回的迭代器都是快速失败的:如果在创建迭代器后的任何时间对映射进行结构修改,则迭代器将抛出 ConcurrentModificationException。因此,在面对并发修改时,迭代器会快速而干净地失败,而不是冒着在未来不确定的时间出现任意的、非确定性行为的风险。请注意,无法保证迭代器的快速失效行为,因为一般来说,在存在不同步的并发修改的情况下,不可能做出任何硬性保证。快速失败迭代器会尽力而为地抛出 ConcurrentModificationException。因此,编写一个依赖于此异常的程序的正确性是错误的:迭代器的快速失败行为应该只用于检测错误。
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {}’

此映射通常充当 bined(桶)哈希表,但是当 bin 变得太大时,它们会转换为 TreeNodes 的 bin,每个 bin 的结构与 java.util.TreeMap 中的结构类似。大多数方法尝试使用普通的 bin,但在适用时中继到 TreeNode 方法(只需检查节点的实例)。TreeNodes 的 bin 可以像任何其他 bin 一样被遍历和使用,但在过度填充时还支持更快的查找。但是,由于正常使用的绝大多数箱不会过度填充,因此在表方法的过程中可能会延迟检查树箱是否存在。树 bin(即元素都是 TreeNodes 的 bin)主要由 hashCode 排序,但在平局的情况下,如果两个元素属于相同的“类 C 实现 Comparable”,则使用它们的 compareTo 方法进行排序。(我们保守地通过反射检查泛型类型来验证这一点——参见 comparableClassFor 方法)。当键具有不同的哈希值或可排序时,树箱的增加复杂性在提供最坏情况下的 O(log n) 操作是值得的,因此,在意外或恶意使用下,性能会优雅地下降,其中 hashCode() 方法返回分布不良的值,以及许多键共享 hashCode 的值,只要它们也具有可比性。(如果这些都不适用,与不采取预防措施相比,我们可能会在时间和空间上浪费大约两倍。但唯一已知的情况源于糟糕的用户编程实践,这些实践已经非常缓慢,以至于这几乎没有区别。由于 TreeNodes 的大小大约是常规节点的两倍,因此我们仅在 bin 包含足够多的节点来保证使用时才使用它们(参见 TREEIFY_THRESHOLD)。当它们变得太小时(由于移除或调整大小),它们会被转换回普通垃圾箱。在具有良好分布的用户 hashCode 的用法中,很少使用树 bin。理想情况下,在随机哈希码下,bin 中节点的频率服从泊松分布 (http:en.wikipedia.orgwikiPoisson_distribution),对于默认调整大小阈值 0.75,参数平均约为 0.5,尽管由于调整大小粒度而存在很大差异。忽略方差,列表大小 k 的预期出现次数为 (exp(-0.5) pow(0.5, k) factorial(k))。树箱的根通常是它的第一个节点。但是,有时(目前仅在 Iterator.remove 上),根目录可能在其他地方,但可以通过父链接(方法 TreeNode.root())恢复)。所有适用的内部方法都接受哈希码作为参数(通常由公共方法提供),允许它们在不重新计算用户哈希码的情况下相互调用。大多数内部方法还接受“tab”参数,该参数通常是当前表,但在调整大小或转换时可能是新表或旧表。当 bin 列表被树化、拆分或未树化时,我们将它们保持在相同的相对访问遍历顺序(即字段 Node.next)中,以更好地保留局部性,并略微简化对调用 iterator.remove 的拆分和遍历的处理。在插入时使用比较器时,为了在重新平衡之间保持总排序(或此处要求的接近),我们将类和 identityHashCodes 作为决胜局进行比较。由于子类 LinkedHashMap 的存在,普通模式与树模式之间的使用和转换变得复杂。请参阅下面的钩子方法,这些方法定义为在插入、删除和访问时调用,这些方法允许 LinkedHashMap 内部以其他方式保持独立于这些机制。(这还要求将映射实例传递给一些可能创建新节点的实用工具方法。类似并发编程的基于 SSA 的编码风格有助于避免在所有曲折的指针操作中出现锯齿错误。

`/**
 * The default initial capacity - MUST be a power of two.
 *  默认容量大小为16,其必须为2的整数次幂
 */
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16`
 `/**
 * The maximum capacity, used if a higher value is implicitly specified
 * by either of the constructors with arguments.
 * MUST be a power of two <= 1<<30.
 *最大容量,如果使用参数的任一构造函数隐式指定了较高的值,则使用该容量。必须是 2 的幂<= 1<<30。
 * 规定了HashMap的最大容量大小为2的29次方,如果手动输入了其他值,则采用改值,但是该值必须为2的幂``   
 */
 static final int MAXIMUM_CAPACITY = 1 << 30;
/**
 * The load factor used when none specified in constructor.
 * 负载因子,没什么好说的,网络上的文章已经写的很明白了
 */
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
 * The bin count threshold for using a tree rather than list for a
 * bin.  Bins are converted to trees when adding an element to a
 * bin with at least this many nodes. The value must be greater
 * than 2 and should be at least 8 to mesh with assumptions in
 * tree removal about conversion back to plain bins upon
 * shrinkage.
 * 树化的阈值为8,当HashMap的容量大于64并且在一个bin上发生冲突,链的长度为8的时候,进行树化
 * 该值必须大于 2,并且应至少为 8
 */
static final int TREEIFY_THRESHOLD = 8;
/**
 * The bin count threshold for untreeifying a (split) bin during a
 * resize operation. Should be less than TREEIFY_THRESHOLD, and at
 * most 6 to mesh with shrinkage detection under removal.
 * 解除树化的阈值为6,与8之间空着一个7,防止单个元素增减的时候频繁进行树化与解除
 * 该值必须小于树化阈值,并且最多为6
 * 
 */
static final int UNTREEIFY_THRESHOLD = 6;

/**
 * The smallest table capacity for which bins may be treeified.
 * (Otherwise the table is resized if too many nodes in a bin.)
 * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
 * between resizing and treeification thresholds.
 * 树化前置容量条件,当HashMap中的容量小于这个值的时候,达到了树化阈值,那么将会进行一次检定,如果容量
 * 大于这个值,则进行树化,否则就进行扩容来减少Hash冲突
 * 该值至少应该是4倍的树化阈值
 */
static final int MIN_TREEIFY_CAPACITY = 64;`

接下来是一个内部类Node,是HashMap进行元素存储的基本单位之一,这里就不过多赘述。

`static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}

    public final K getKey()        { return key; }
    public final V getValue()      { return value; }
    public final String toString() { return key + "=" + value; }

    public final int hashCode() {
        return Objects.hashCode(key) ^ Objects.hashCode(value);
    }

    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }

    public final boolean equals(Object o) {
        if (o == this)
            return true;
        if (o instanceof Map.Entry) {
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;
            if (Objects.equals(key, e.getKey()) &&
                Objects.equals(value, e.getValue()))
                return true;
        }
        return false;
    }
}

接下来是一些对外的方法:

/**
 * 计算 key.hashCode() 并将 (XOR) 较高的哈希位传播到更低的位。由于该表使用 2 的幂掩码,因此仅以高于当前掩码的位数变化的哈希集将始终发生冲突。
 * (在已知的例子中,有一组浮点键,它们在小表中保存连续的整数。因此,我们应用了一个转换
 * ,将较高位的影响向下分散。在速度、实用性和比特扩展质量之间需要权衡。由于许多常 
 * 见的哈希集已经合理分布(因此不会从扩展中受益),并且由于我们使用树来处理 bin 中的大量冲突,因此我们只是以最便宜的方式对一些移位进行异或,
 * 以减少系统损失,并合并最高位的影响,否则由于表边界,这些位永远不会用于索引计算。
 * 这里计算hash是对key的hashCode进行高16位与低16位的异或,从而使得hash值同时具有高16位与低16位的特征,从而减少Hash冲突
 */
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

/**
 * Returns x's Class if it is of the form "class C implements
 * Comparable<C>", else null.
 * 返回x的Class对象,当且仅当 X 是 X implement Comparable<X>,否则返回nul
 */
static Class<?> comparableClassFor(Object x) {
    // 首先 x 必须是 Comparable的实现类
    if (x instanceof Comparable) {
    // 初始化 Class对象,type对象,参数化类型对象
        Class<?> c; Type[] ts, as; Type t; ParameterizedType p;
        if ((c = x.getClass()) == String.class) // bypass checks
            return c;
        if ((ts = c.getGenericInterfaces()) != null) {
            for (int i = 0; i < ts.length; ++i) {
                if (((t = ts[i]) instanceof ParameterizedType) &&
                    ((p = (ParameterizedType)t).getRawType() ==
                     Comparable.class) &&
                    (as = p.getActualTypeArguments()) != null &&
                    as.length == 1 && as[0] == c) // type arg is c
                    return c;
            }
        }
    }
    return null;
}

标签:bin,return,HashMap,映射,哈希,hashCode,源码,key,随笔
From: https://www.cnblogs.com/blog4cloudboy/p/17966460

相关文章

  • C语言学习随笔-07 auto关键字
    1、在C中auto是一个存储类的关键字。     -auto存储类:auto存储类是所有局部变量默认的存储类。     -auto可以在声明变量的时候根据变量的初始值的类型自动为此变量选择匹配的类型。2、注意事项     -auto声明的变量必须要初始化,否则编译器不能判断变量......
  • PriorityQueue源码阅读
    目录简介模型代码分析成员变量方法总结参考链接本人的源码阅读主要聚焦于类的使用场景,一般只在java层面进行分析,没有深入到一些native方法的实现。并且由于知识储备不完整,很可能出现疏漏甚至是谬误,欢迎指出共同学习本文基于corretto-17.0.9源码,参考本文时请打开相应的源码对照,......
  • 学习spring源码(一)
    学习文档来自小傅哥,详情可以去原文章了解,这边只是简单记录一下学习体会《Spring手撸专栏》第3章:初显身手,运用设计模式,实现Bean的定义、注册、获取工程结构:类似是这样,我这边稍微有点区别,仅做参考small-spring-step-02└──src├──main│└──java......
  • PDF.js实现按需分片加载pdf文件-包含前后端开发源码和详细开发教程
    PDF.js实现按需分片加载pdf文件-包含前后端开发源码和详细开发教程:https://blog.csdn.net/qq_29864051/article/details/130742657?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522170529842016800186594900%2522%252C%2522scm%2522%253A%252220140713.130102334..%252......
  • [源码分析] - flex 标准文档导读与 一个rust实现解析
    本文是w3中css-flexbox[标准文档](CSSFlexibleBoxLayoutModuleLevel1(w3.org)解读.(2023.1),并对一些开源实现进行调研分析.文档导读csslayoutmodecsslayout模式用于确定在盒模型中的元素如何排布(大小与位置),在css2.1中有如下几种方式.blocklayout,块级别......
  • QR二维码生成器源码(中间可插入小图片)
    QR二维码生成器源码(中间可插入小图片) 二维码终于火了,现在大街小巷大小商品广告上的二维码标签都随处可见,而且大都不是简单的纯二维码,而是中间有个性图标的二维码。我之前做了一个使用google开源项目zxing实现二维码、一维码编码解码的程序并开放了源码(用C#实现的条形码和二......
  • Django 源码分析(一):命令分析
    Django源码分析(一):命令分析说明:本部分主要介绍Django程序在开发中常用的命令是如何控制生成的进行解析;1.分析入口启动命令:pythonmanage.pyrunserver127.0.0.1:8000项目启动的时候执行的manage.py脚本,相关代码如下:"""Django'scommand-lineutilityforadministrat......
  • Django 源码分析(二):wsgi & asgi
    Django源码分析(二):wsgi&asgi说明:上一节主要讲述了django项目的启动,后期主要会根据django请求的生命周期进行分析;参考文章:https://zhuanlan.zhihu.com/p/95942024参考文章:https://zhuanlan.zhihu.com/p/269456318附:生命周期参考图;第一步:浏览器发起请求补充:第一步和第......
  • Django 源码(三)-应用 & 中间件 & 配置文件
    Django源码(三)-应用&中间件&配置文件本部分主要是在为程序启动时候加载应用以及中间件的信息;1.应用的加载在程序启动的部分,我们分析到程序执行的时候都会执行一个setup()函数,相关的内容可以看之前的章节的部分;defsetup(set_prefix=True):"""Configurethes......
  • Promise超详细源码解读
    说到promise,相信大家在日常开发中都经常使用到,它是我们异步操作中必不可少的一部分,可以让代码看起来变得更好理解;我曾在技术社区看过许多关于promise底层原理的文章,大概原理明白,这次,我准备系统的分析实现源码并记录下来,本文将一行行代码去分析最后附加流程图和总结,希望这能对你......