栈 · java se -> 集合框架 · HashMap -> HashMap 源码解析

目录

  • 实现继承关系

  • 构造方法

  • 存放元素 put() 

  • 获取元素 get()

本文以 jdk 1.8 为例

实现继承关系

首先,我们先来了解一下 HashMap 的实现继承关系,类声明相关代码如下:

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
    
    //other thing
}

HashMap 继承了 AbstractMap 抽象类,实现了 Map、Cloneable、Serializable 这三个接口,在实例化时可以传入 <k,v> 泛型 。

由此可得:

  • HashMap 是 Map 接口的实现类,实现了该接口的所有方法;

  • HashMap 实现了 Cloneable、Serializable 接口,支持可克隆和可序列化;

  • HashMap 是泛型的居停应用和实现;

构造方法

HashMap 提供了三个构造方法用来生成实例,代码如下:

/**
* 传入初始化容器大小 initialCapacity 和装载因子 loadFactor 
* 例如:new HashMap(2,0.75f); 
*/
public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
}

/**
* 传入初始化容器大小 initialCapacity ,使用默认的装载因子 DEFAULT_LOAD_FACTOR
*/
public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

/**
* 无参构造函数,所有的属性值度采用默认值
*/
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

/**
* 初始化时传入一个已经存在的 Map 实例对象,相关的泛型需要符合要求。
* 目的是合并两个 HashMap 对象的元素。
*/
public HashMap(Map<? extends K, ? extends V> m) {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    putMapEntries(m, false);
}

我们需要对 initialCapacity 、loadFactor、threshold 这两个参数进行解释

initialCapacity 

initialCapacity 指的是桶初始化容量的大小。值的限定条件来源于 DEFAULT_INITIAL_CAPACITY、MAXIMUM_CAPACITY 两个属性

/**
* 默认的初始化容量,必须为 2 的幂
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

/**
* 最大的 “桶” 容量,必须是 2 的幂
*/
static final int MAXIMUM_CAPACITY = 1 << 30;

由构造函数可知,如果实例化 HashMap 对象的时候指定的容量 initialCapacity 超过  MAXIMUM_CAPACITY ,则使用最大容量。

这里我们需要了解一下“容量”的概念。

我们知道 HashMap 的数据结构是 哈希表,哈希表的主体结构是一个数组,每个数组元素可以衍生成为其他的数据结构。而我们之前所说的容量就是指的 主体数组的长度 ,默认为 16 。

有时候,我们也会把主体数组的每个内存位置空间称作为“桶”,数组就是一组的桶,容量也就是通的数量!!

它跟 size 是不等价的,size 表示 HashMap 中存放 KV 的数量(为链表和树中的KV的总和),需要注意一下。

loadFactor

loadFactor 译为装载因子。装载因子用来衡量 HashMap 满的程度。

loadFactor的默认值为 0.75f 。计算 HashMap 的实时装载因子的方法为:size/capacity,而不是用桶的数量去除以 capacity。

/**
* 默认装载因子的大小
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;

threshold 

threshold 主要是用来帮助 HashMap 扩容用的,表示当 HashMap 的 size 大于 threshold 时会执行 resize 操作。

threshold = capacity * loadFactor,即:扩容警戒线 = 桶数量 * 装载因子 ;

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

这里 hash(key) 方法是通过传进来的 key 去计算 hashCode,从而得到 bucket ( 桶 ) 位置来储存值对象。

扩容 resize() 

当hashmap中的元素个数超过 数组大小*loadFactor 时,就会进行数组扩容,loadFactor的默认值为0.75,也就是说,默认情况下,数组大小为16,那么当hashmap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知hashmap中元素的个数,那么预设元素的个数能够有效的提高hashmap的性能。

比如说,我们有1000个元素new HashMap(1000), 但是理论上来讲new HashMap(1024)更合适,不过上面annegu已经说过,即使是1000,hashmap也自动会将其设置为1024。 但是new HashMap(1024)还不是更合适的,因为0.75*1000 < 1000, 也就是说为了让0.75 * size > 1000, 我们必须这样new HashMap(2048)才最合适,既考虑了&的问题,也避免了resize的问题。

获取元素 get()

HashMap 通过调用 get() 方法获取数据。

当获取对象时,通过键对象的 hashCode() 方法 计算出bucket ( 桶 ) 位置。

因为存放数据的时候可能会出现“碰撞”,所以在通过 key 获取元素的时候,还需要调用键对象的 equals() 方法找到正确的键值对,然后返回值对象。

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值