Java容器之HashMap源码分析(妈妈再也不用担心我不懂HashMap了)

  最近面试被问HashMap容器的实现原理,答的一塌糊涂。。。虽说一直念叨着说要看看Java容器的源码,但总是被耽搁了,今天终于静下心来看了🤦‍♂️。

  注明:以下源码分析都是基于jdk 1.8.0_221版本
在这里插入图片描述

一、HashMap概述(一图以蔽之

  HashMap的类声明如下

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

  HashMap是一个<key,value>(或称键值对)容器,其底层实现是使用一个hash数组指向多个不同的链表。每次我们放入一个<key,value>,它会自动计算key对应的hash值,然后根据hash值插入到不同的链表中。
在这里插入图片描述

  注明:可能会有人对为啥要用hash数组链表产生疑问,这是因为实际插入过程中会出现多个<key,value>的key计算出的hash值相同(哈希冲突),如上图的table[1]。但是当链表太长时,在容器中查找<key,value>,每次都要遍历耗时长,降低了查找效率,所以在Java 8中,引入了红黑树。默认当某个hash值下超过了8个<key,value>,此时就需要转化成红黑树,如果上图中的table[14]

二、HashMap类的属性

1、HashMap类静态属性

/**
 * 序列化的版本号
 */
private static final long serialVersionUID = 362498820763181265L;
/**
 * 默认的初始化容量大小,并且必须是2的幂(主要是考虑效率,后面有介绍)
 */
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

/**
 * 最大的容量(容器中存放<key, value>的最大数量)
 */
static final int MAXIMUM_CAPACITY = 1 << 30;

/**
 * 负载因子
 * 当容器中<key, value>的数量超过capacity * DEFAULT_LOAD_FACTOR时,需要扩容
 */
static final float DEFAULT_LOAD_FACTOR = 0.75f;

/**
 * 链表转红黑树阈值
 * 当某个hash值下<key, value>用链表存储,并且链表长度不小于该值,就需要转成红黑树
 */
static final int TREEIFY_THRESHOLD = 8;

/**
 * 红黑树转链表阈值
 * 当某个hash值下<key, value>是用红黑树存储,并且树中的节点数小于该值,就需要转成链表
 */
static final int UNTREEIFY_THRESHOLD = 6;

/**
 * 最小树形化容量阈值
 * 当哈希表中的容量 > 该值时,才允许将链表转成红黑树操作,否则直接扩容。
 * 为了避免进行扩容、链表转红黑树选择的冲突,并且这个值不能小于 4 * TREEIFY_THRESHOLD(链表转红黑树阈值)
 */
static final int MIN_TREEIFY_CAPACITY = 64;

2、HashMap非静态属性

  transient关键字的作用是在序列化的时候排除该属性,比如写入硬盘持久化,用这个关键字修饰的属性在对象保存时不会写入。(不过HashMap类在尾端重写了序列化方法,手动指定了需要序列化的属性)

/**
 * table数组,也称hash桶数组
 */
transient Node<K,V>[] table;

/**
 * entrySet属性,把<K,V>存放到Set容器中(一般hashmap的遍历用此属性)
 */
transient Set<Map.Entry<K,V>> entrySet;

/**
 * 容器key-value数量(注意与容器的容量(容器可存放的数量)不同)
 */
transient int size;

/**
 * 容器进行结构性调整(增加或者删除键值对等操作,不包括修改value值)的次数
 */
transient int modCount;

/**
 * 容器中能容纳的key-value极限,capacity * loadFactor,超过就需要扩容
 */
int threshold;

/**
 * 负载因子,默认是0.75(前面类的静态属性已经定义过了)
 */
final float loadFactor;

注 意 : \color{red}注意: 上面提到的容量就是table数组的长度,size是容器中存放的key-value数量,threshold = 容量 * 负载因子,表示的该容器最多可以放置多少个key-value

三、HashMap类的构造器

  查看HashMap类文件,可以发现一共有4个构造器。

/**
 * @param  initialCapacity 初始化容量大小
 * @param  loadFactor      负载因子
 * @throws IllegalArgumentException if the initial capacity is negative
 *         or the load factor is nonpositive
 */
public HashMap(int initialCapacity, float loadFactor) {
   
	// 检查initialCapacity的合法性
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
    // 检查initialCapacity是否超过了可设置的最大容量(类静态属性)
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    // 检查loadFactor负载因子的合法性
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    this.loadFactor = loadFactor;
    //初始化threshold稍微复杂一点,tableSizeFor方法解析见本博客尾端
    this.threshold = tableSizeFor(initialCapacity);
}

/**
 * @param  initialCapacity 初始化容量大小
 * @throws IllegalArgumentException if the initial capacity is negative.
 */
public HashMap(int initialCapacity) {
   
	//默认负载因子为0.75
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

/**
 * 只设置负载因子为0.75,其它值全部默认
 */
public HashMap() {
   
    this.loadFactor = DEFAULT_LOAD_FACTOR;
}

/**
 * 复制构造函数,将另外一个map初始化构造
 *
 * @param   m 其它map容器
 * @throws  NullPointerException
 */
public HashMap(Map<? extends K, ? extends V> m) {
   
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    //将m容器中的所有entry放入新建的容器对象中
    putMapEntries(m, false);
}

四、增加key-value相关方法

1、put方法

  put方法,往容器中添加key-value,允许key = null,也允许value = null

/**
 * 往容器中添加`key-value`,允许`key = null`,也允许`value = null`
 */
public V put(K key, V value) {
   
    return putVal(hash(key), key, value, false, true);
}

2、putVal方法

  putVal方法的作用是往map容器中插入一个key-value

/**
 * Implements Map.put and related methods.
 *
 * @param hash key的hash值(调用hash()方法)
 * @param key 插入键值对key
 * @param value 插入键值对value
 * @param onlyIfAbsent 设为true时,表示如果容器已经存在这个key就不进行修改
 * @param evict 为 false时,表示容器正处于创建(其它map传入初始化)
 * @return previous value, or null if none
 * 
 */
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
   
    // tab指向 对象的table数组(hash桶数组),p 指向hash对应的桶
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    // 如果容器为空,则需要调用resize方法,初始化table数组
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 将p指向hash对应的hash桶
    if ((p = tab[i = (n - 1) & hash]) == null)
    	// (n - 1) & hash求出hash对应的table数组下标,如果这个位置为空,说明这个桶为空
    	// 直接放入table中,不需要生成链表、红黑树等
        tab[i] = 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值