目录
-
实现继承关系
-
构造方法
-
存放元素 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;
}