1.常用属性介绍
HashMap类属性
// 默认容量为16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 最大容量为2^30
static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认加载因子0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 空数组常量
static final Entry<?,?>[] EMPTY_TABLE = {};
HashMap实例属性
// 哈希桶数组
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
// HashMap有效元素长度
transient int size;
// 阈值
int threshold;
// 加载因子
final float loadFactor;
// HashMap结构被修改次数
transient int modCount;
2.构造函数
2.1 无参构造
使用无参构造,默认会调用参数为容量和加载因子的有参构造,并传入默认容量16以及默认加载因子0.75
public HashMap() {
//调用参数为容量、加载因子的有参构造,传入默认容量和默认加载因子
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
2.2 有参构造
需要传入容量和加载因子
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;
threshold = initialCapacity;
//HashMap中无意义,LinkedHashMap才实现
init();
}
3.put()
public V put(K key, V value) {
// 判断HashMap是否初始化
if (table == EMPTY_TABLE) {
// 详见3.1 inflateTable方法(初始化)
inflateTable(threshold);
}
if (key == null)
// 详见3.3 putForNullKey()
return putForNullKey(value);
// 详见3.9 hash()
int hash = hash(key);
// 根据hash值和hash桶数组长度计算index
int i = indexFor(hash, table.length);
// 遍历index=i的哈希桶
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
// 先判断hash值是否相等,在判断key值是否相等
// 如果相等则覆盖原value,并将原value值返回
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
// 新增元素,详见3.4
addEntry(hash, key, value, i);
return null;
}
3.1 inflateTable()
初始化哈希桶数组
// 初始化
private void inflateTable(int toSize) {
// 找到一个大于等于toSize的2的次方,详见3.2 roundUpToPowerOf2()
int capacity = roundUpToPowerOf2(toSize);
// 设置阈值
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
// 重新设置table
table = new Entry[capacity];
initHashSeedAsNeeded(capacity);
}
3.2 roundUpToPowerOf2()
对用户初始化的容量进行处理
private static int roundUpToPowerOf2(int number) {
// 当number大于等于最大容量,取最大容量;反之小于最大容量时,判断是否大于1,
// 如果大于1则取大于等于number的最小2次方数,小于1则取1
// highestOneBit()方法的作用是将传入参数只保留最高位为1,其他全置为0
return number >= MAXIMUM_CAPACITY
? MAXIMUM_CAPACITY
: (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}
3.3 putForNullKey()
key为null时的元素存放
private V putForNullKey(V value) {
// 将key为null的元素,默认是存入table[0]的哈希桶中
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
// 循环遍历table[0],如果找到桶中某元素key为null,则将value覆盖,并返回原来的value值
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
// 如果没有找到key为null的元素,则新增一个元素到index=0的哈希桶中
// 详见3.4 addEntry()
addEntry(0, null, value, 0);
return null;
}
3.4 addEntry()
新增元素到对应bucketIndex的哈希桶中
void addEntry(int hash, K key, V value, int bucketIndex) {
// 判断当前长度是否大于等于阈值
if ((size >= threshold) && (null != table[bucketIndex])) {
// 扩容,详见3.5 resize()
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
// 详见3.8 createEntry()
createEntry(hash, key, value, bucketIndex);
}
3.5 resize()
哈希桶数组扩容
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
// 将原数组的元素全部转移到新数组中,详见3.6 transfer()
transfer(newTable, initHashSeedAsNeeded(newCapacity));
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
3.6 transfer()
将原哈希桶数组转移到新哈希桶数组中
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
// 循环遍历哈希桶数组,判断每个哈希桶中不为null的元素,将其一一转移至新数组中
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
// 判断是否需要重新计算hash值
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
// 详见3.7 indexFor()
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}
3.7 indexFor()
根据hash值和哈希桶数组长度计算对应的index值
static int indexFor(int h, int length) {
// 等价于 h % length
return h & (length-1);
}
3.8 createEntry()
创建新entry插入到对应index的哈希桶中
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
// 使用头插法将新元素插入到table[bucketIndex]哈希桶的头部
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
3.9 hash()
计算key的hash值
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
// 扰动函数,减少hash值碰撞概率
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
4.get()
根据key获取对应value值
public V get(Object key) {
if (key == null)
// 详见4.1 getForNullKey()
return getForNullKey();
// 详见4.2 getEntry()
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
4.1 getForNullKey()
获取key为null时的值
private V getForNullKey() {
if (size == 0) {
return null;
}
// 因为key=null的元素默认就是存储在table[0]哈希桶中,直接循环遍历它
// 如果有key为null则返回对应value,反之则返回null
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}
4.2 getEntry()
根据key获取对应value值的具体操作
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
// 计算hash值
int hash = (key == null) ? 0 : hash(key);
// 根据hash值,以及哈希桶数组长度找到对应的哈希桶,并遍历该哈希桶
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
// 判断hash和key是否相等
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
5.常见面试题
5.1 容量为什么是2的幂?
- 这就是要说到indexFor()方法,该方法就是使用hash值与上哈希桶数组长度-1(hash & (length - 1)),它在哈希桶数组长度为2的幂的时候是等价于hash值取余哈希桶数组长度(hash % length),但是与操作的运行速度远远高于取余操作,因此容量为2的幂。
5.2 HashMap的扩容方式是什么?
- 判断原哈希桶数组长度是否为最大容量,如果是最大容量则将阈值修改为int的最大值,反之则创建一个新的哈希桶数组长度为原来的两倍,循环遍历原哈希桶数组,判断是否需要rehash,在根据hash值以及新哈希桶数组长度计算新的index值,并根据index存入新哈希桶数组中对应的哈希桶中。最后根据新哈希桶长度乘以加载因子与最大默认长度+1比较,取较小值赋值给阈值属性。