重要的成员
1、哈希表:
transient Node<K,V>[] table;
2、节点
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
...
}
3、哈希表的初始大小,默认16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
4、哈希表的size
transient int size;
5、负载因子,以及默认值
final float loadFactor;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
一般使用默认值即可
6、扩容阈值
int threshold;
等于数组容量*负载因子
JDK 8 优化的 Hash 算法
JDK 1.7
计算 key
的哈希值时,直接调用 hashCode()
方法。
JDK1.8
:hash()
方法也叫 “扰动函数”
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
int 类型有 32 位,相当于将 hashCode()
方法的返回值与它的高十六位进行一次异或操作。
优点:降低 Hash 冲突的概率。
面试记得提一下:
因为在后面进行哈希寻址的时候,JDK 1.7 直接使用取模来得到数组下标,JDK 1.8 为了提高效率,变成与 数组长度-1 的值进行与操作得到。而大部分情况下,数组长度都不会超过
2^16
,所以需要在计算哈希值时进行一次高低十六位的异或操作,使得计算后的低十六位也包含高十六位的信息,从而避免二进制位的浪费。
即,混合原始哈希码的高位和低位,以此来加大低位的随机性
构造器
默认:没有对 table
数组进行初始化,而是在添加元素时才创建
public HashMap() {
// 0.75f
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
带参数的构造器:可以指定数组的初始化容量和负载因子
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
// 如果指定的初始化容量大于容量的上限,就直接设置为上限 1<<30
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
// 注意:数组未被初始化时,初始化容量使用字段 threshold 记录
// 但不是任意一个正整数的值都可以作为数组的容量
// HashMap 规定数组的容量为 2^n,
this.threshold = tableSizeFor(initialCapacity);
}
对传入的值做一系列的按位或操作:返回一个刚好大于等于 cap 且为 2^n 的数
static final int tableSizeFor(int cap) {
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;
}
算法的大致流程:
1、抓出 n 最左边的 1
2、让这个 1 右边的数字全部变成 1
最终就得到一个 11…111 的数字。如 n = 0001xxxx
第一次:
n>>>1 = 00001xxx
n = 00001xxx | 0001xxxx = 00011xxx
第二次:
n>>>2 = 0000011x
n = 0000011x | 00011xxx = 0001111x
第三次:
n>>>4 = 00000001
n = 00000001 | 0001111x = 00011111
最终返回 n+1 = 100000
put() 方法的寻址操作
核心方法:HashMap#putVal()
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 当发现 table 为空或者长度为 0,创建
if ((tab = table) == null || (n = tab.length) == 0)
// resize() 方法可以用于进行初始化或者扩容
n = (tab = resize()).length;
// 寻址:计算到数组的下标 i = (n - 1) & hash
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] =