HashMap源码学习第一天

目录

1. 简介

2,类图

3,属性

1,table Node 数组

4. 构造方法

1,HashMap()方法

2,HashMap(int initialCapacity)方法

3,HashMap(int initialCapacity, float loadFactor)方法

4 ,HashMap(Map m)方法


1. 简介

HashMap ,是一种散列表,用于存储 key-value 键值对的数据结构,一般翻译为“哈希表基于 key 级别的 get/put 等操作。

在日常的业务开发中,HashMap 可以说是和 ArrayList 一样常用的集合类,特别是考虑到数据库的性能,又或者服务的拆分后,我们把关联数据的拼接,放到了内存中,这就需要使用到 HashMap 了。

2,类图

HashMap 实现的接口、继承的抽象类,如下图所示:

HashMap 其实是在数组的基础上实现的,一个“加强版”的数组。

通过 hash(key) 的过程,我们可以将 key 成功的转成一个整数。但是,hash(key) 可能会超过数组的容量,所以我们需要 hash(key) % size 作为下标放入数组的对应位置。至此,我们是不是已经可以通过 O(1) 的方式,快速的从 HashMap 中进行 get 读取操作了。

1、hash(key) 计算出来的哈希值,并不能保证唯一;

2、hash(key) % size 的操作后,即使不同的哈希值,也可能变成相同的结果。 就导致我们常说的“哈希冲突”

解决方法

1、开放寻址法

2、链表法

在 JDK8 开始的版本,HashMap 采用“数组 + 链表 + 红黑树”的形式实现,在空间和时间复杂度中做取舍。

3,属性

//底层存储的数组
transient Node<K,V>[] table;

    /**
     * Holds cached entrySet(). Note that AbstractMap fields are used
     * for keySet() and values().调用 `#entrySet()` 方法后的缓存
     */
    transient Set<Map.Entry<K,V>> entrySet;

    /**
     * The number of key-value mappings contained in this map. key-value 的键值对数量
     */
    transient int size;

    /*
     *HashMap 的修改次数
     * The number of times this HashMap has been structurally modified
     * Structural modifications are those that change the number of mappings in
     * the HashMap or otherwise modify its internal structure (e.g.,
     * rehash).  This field is used to make iterators on Collection-views of
     * the HashMap fail-fast.  (See ConcurrentModificationException).
     */
    transient int modCount;

    /**
     *   阀值,当 {@link #size} 超过 {@link #threshold} 时,会进行扩容
     * The next size value at which to resize (capacity * load factor).
     *
     * @serial
     */
    // (The javadoc description is true upon serialization.
    // Additionally, if the table array has not been allocated, this
    // field holds the initial array capacity, or zero signifying
    // DEFAULT_INITIAL_CAPACITY.)
    int threshold;

    /**
     * The load factor for the hash table.
     *
     * @serial 扩容因子
     */
    final float loadFactor;

重点看下 tablesizethresholdloadFactor 四个属性。

1,table Node 数组

table Node 数组。代码如下:

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;  //哈希值
        final K key;        // KEY 键
        V value;      //VALUE 值
        Node<K,V> next;  //下一个节点
        
        // ... 省略实现方法

}
  • hash + key + value 属性,定义了 Node 节点的 3 个重要属性。
  • 实现了 Map.Entry 接口,该接口定义在 Map 接口中。
  • next 属性,指向下一个节点。通过它可以实现 table 数组的每一个位置可以形成链表。

Node 子类如下图:

 TreeNode ,定义在 HashMap 中,红黑树节点。通过它可以实现 table 数组的每一个位置可以形成红黑树,可自行了解

4. 构造方法

HashMap 一共有四个构造方法,我们分别来看看。

1,HashMap()方法

HashMap() 构造方法代码如下:

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


 static final float DEFAULT_LOAD_FACTOR = 0.75f;


public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

2,HashMap(int initialCapacity)方法

HashMap(int initialCapacity) 方法,初始化容量为 initialCapacity 的 HashMap 对象。代码如下:

public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

3,HashMap(int initialCapacity, float loadFactor)方法

HashMap(int initialCapacity, float loadFactor) 构造方法,初始化容量为 initialCapacity 、加载因子为 loadFactor 的 HashMap 对象代码如下:

//最大的容量为 2^30 。
static final int MAXIMUM_CAPACITY = 1 << 30;

public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)  // 校验 initialCapacity 参数
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY) // 避免 initialCapacity 超过 MAXIMUM_CAPACITY
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))   // 校验 loadFactor 参数
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor; // 设置 loadFactor 属性
        this.threshold = tableSizeFor(initialCapacity); //计算 threshold 阀值
    }

我们重点来看 计算 threshold 阀值处,调用 #tableSizeFor(int cap) 方法,返回大于 cap 的最小 2 的 N 次方。例如说,cap = 10 时返回 16 ,cap = 28 时返回 32 。代码如下:

 static final int tableSizeFor(int cap) {
        // 将 cap 从最高位(最左边)第一个为 1 开始的位开始,全部设置为 1 。
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

4 ,HashMap(Map<? extends K, ? extends V> m)方法

HashMap(Map<? extends K, ? extends V> m) 构造方法,创建 HashMap 对象,并将 c 集合添加到其中。代码如下:

public HashMap(Map<? extends K, ? extends V> m) {
        // 设置加载因子
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        //批量添加到 table 中
        putMapEntries(m, false);
    }

putMapEntries(Map<? extends K, ? extends V> m, boolean evict) 方法,批量添加到 table 中。代码如下:

final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
        int s = m.size();
        if (s > 0) {
            if (table == null) { // pre-size  如果 table 为空,说明还没初始化,适合在构造方法的情况
                float ft = ((float)s / loadFactor) + 1.0F; // 根据 s 的大小 + loadFactor 负载因子,计算需要最小的 tables 大小+ 1.0F 的目的,是因为下面 (int) 直接取整,避免不够。
                int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                         (int)ft : MAXIMUM_CAPACITY);
                if (t > threshold)  // 如果计算出来的 t 大于阀值,则计算新的阀值
                    threshold = tableSizeFor(t);
            }    // 如果 table 非空,说明已经初始化,需要不断扩容到阀值超过 s 的数量,避免扩容
            else if (s > threshold)
                resize();  // 扩容
            for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
                K key = e.getKey();
                V value = e.getValue();
                putVal(hash(key), key, value, false, evict);
            }
        }
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ehdjsbs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值