构造函数
构造函数
HashMap 一共提供了四个构造函数,其中 默认无参的构造函数 和 参数为 Map 的构造函数 为 Java Collection Framework 规范的推荐实现,其余两个构造函数则是 HashMap 专门提供的。
HashMap()
该构造函数意在构造一个具有> 默认初始容量 (16) 和 默认负载因子(0.75) 的空 HashMap,是 Java Collection Framework 规范推荐提供的,其源码如下:
/**
* Constructs an empty HashMap with the default initial capacity
* (16) and the default load factor (0.75).
*/
public HashMap() {
//负载因子:用于衡量的是一个哈希表的空间的使用程度
this.loadFactor = DEFAULT_LOAD_FACTOR;
//HashMap进行扩容的阈值,它的值等于 HashMap 的容量乘以负载因子
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
// HashMap的底层实现仍是数组,只是数组的每一项都是一条链
table = new Entry[DEFAULT_INITIAL_CAPACITY];
init();
}
在构造函数中都会涉及初始容量
和负载因子
,这两个参数是影响 HashMap 性能的重要参数。其中,容量表示哈希表中桶的数量 (table 数组的大小),初始容量是创建哈希表时桶的数量;负载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度,它衡量的是一个哈希表的空间的使用程度,负载因子越大表示哈希表的装填程度越高,反之愈小。对于使用拉链法的哈希表来说,查找一个元素的平均时间是 O(1+a),a 指的是链的长度,是一个常数。特别地,若负载因子越大,那么对空间的利用更充分,但查找效率的也就越低;若负载因子越小,那么哈希表的数据将越稀疏,对空间造成的浪费也就越严重。系统默认负载因子为 0.75,这是时间和空间成本上一种折衷,一般情况下我们是无需修改的。
此外,每次新建一个 HashMap 时,都会初始化一个 Entry 类型的 table 数组,其中 Entry 类型的定义如下:
static class Entry<K,V> implements Map.Entry<K,V> {
final K key; // 键值对的键
V value; // 键值对的值
Entry<K,V> next; // 下一个节点
final int hash; // hash(key.hashCode())方法的返回值
/**
* Creates new entry.
*/
Entry(int h, K k, V v, Entry<K,V> n) { // Entry 的构造函数
value = v;
next = n;
key = k;
hash = h;
}
......
}
其中,Entry 为 HashMap 的内部类,实现了 Map.Entry 接口,其包含了键 key、值 value、下一个节点 next,以及 hash 值四个属性。事实上,Entry 是构成哈希表的基石,是哈希表所存储的元素的具体形式。
HashMap(int initialCapacity)
该构造函数意在构造一个指定初始容量和默认负载因子 (0.75)的空 HashMap,其源码如下:
// Constructs an empty HashMap with the specified initial capacity and the default load factor (0.75)
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR); // 直接调用上述构造函数
}
HashMap(int initialCapacity, float loadFactor)
该构造函数意在构造一个 指定初始容量 和 指定负载因子的空 HashMap,其源码如下:
/**
* Constructs an empty HashMap with the specified initial capacity and load factor.
*/
public HashMap(int initialCapacity, float loadFactor) {
//初始容量不能小于 0
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
//初始容量不能超过 2^30
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//负载因子不能小于 0
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// HashMap 的容量必须是2的幂次方,超过 initialCapacity 的最小 2^n
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
//负载因子
this.loadFactor = loadFactor;
//设置HashMap的容量极限,当HashMap的容量达到该极限时就会进行自动扩容操作
threshold = (int)(capacity * loadFactor);
// HashMap的底层实现仍是数组,只是数组的每一项都是一条链
table = new Entry[capacity];
init();
}
HashMap(Map<? extends K, ? extends V> m)
该构造函数意在构造一个与指定 Map 具有相同映射的 HashMap,其 初始容量不小于 16 (具体依赖于指定 Map 的大小),负载因子是 0.75,是 Java Collection Framework 规范推荐提供的,其源码如下:
// Constructs a new HashMap with the same mappings as the specified Map.
// The HashMap is created with default load factor (0.75) and an initial capacity
// sufficient to hold the mappings in the specified Map.
public HashMap(Map<? extends K, ? extends V> m) {
// 初始容量不小于 16
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
putAllForCreate(m);
}
HashMap 的底层数组长度为何总是 2 的 n 次方?
我们知道,HashMap 的底层数组长度总是 2 的 n 次方,原因是 HashMap 在其构造函数 HashMap(int initialCapacity, float loadFactor) 中作了特别的处理,如下面的代码所示。当底层数组的 length 为 2 的 n 次方时, h&(length - 1)
就相当于对 length 取模,其效率要比直接取模高得多,这是 HashMap 在效率上的一个优化。
// HashMap 的容量必须是2的幂次方,超过 initialCapacity 的最小 2^n
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
在上文已经提到过,HashMap 中的数据结构是一个数组链表,我们希望的是元素存放的越均匀越好。最理想的效果是,Entry 数组中每个位置都只有一个元素,这样,查询的时候效率最高,不需要遍历单链表,也不需要通过 equals 去比较 Key,而且空间利用率最大。
HashMap 采用了一个分两步走的哈希策略来保证分布的均匀:
- 使用 hash() 方法用于对 Key 的 hashCode 进行重新计算,以防止质量低下的 hashCode()函数实现。由于 hashMap 的支撑数组长度总是 2 的倍数,通过右移可以使低位的数据尽量的不同,从而使 Key 的 hash 值的分布尽量均匀;
- 使用 indexFor() 方法进行取余运算,以使 Entry 对象的插入位置尽量分布均匀。
因此,总的来说,HashMap 的底层数组长度总是 2 的 n 次方的原因有两个,即当 length=2^n 时:
- 不同的 hash 值发生碰撞的概率比较小,这样就会使得数据在 table 数组中分布较均匀,空间利用率较高,查询速度也较快;
- h&(length - 1) 就相当于对 length 取模,而且在速度、效率上比直接取模要快得多,即二者是等价不等效的,这是 HashMap 在速度和效率上的一个优化。