目录
2,HashMap(int initialCapacity)方法
3,HashMap(int initialCapacity, float loadFactor)方法
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;
重点看下 table
、size
、threshold
、loadFactor
四个属性。
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);
}
}
}