要点归纳
- 数据结构
- 1.7 HashMap 的数据结构 为 数组 + 链表 ,[1.8引用数组+链表+红黑树]
- 初始化
- 构造方法
- 调用初始化
- put 方法
- 主要过程
- 判断分配空间
- key为空处理
- 计算索引位
- key冲突 处理过程
- addEntry 、是否扩容
- 扩容
- 扩容过程
- 环形链表问题
之前了解过的可按照上面提到的回想下,是否都掌握。
定义
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
{
... }
可看到 HashMap 继承了 AbstractMap ,实现了 Map、Cloneable、Serializable
Cloneable:标记为colone、Serializable:标记为可被序列^化
来看下 定义的属性
/**
* 定义默认长度 16 2^4
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* 最大长度 2^30
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* 默认加载因子 [size >= 阈值(容量*加载因子) 则会进行扩容]
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* 空的Entry 数组对象
*/
static final Entry<?,?>[] EMPTY_TABLE = {
};
/**
* 用于存放key value的对象 [还存储了当前对象的hash值 以及 next Entry *hash冲突时会形成链表结构]
*/
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
/**
* 当前元素个数
*/
transient int size;
/**
* 当前数组的阈值
*/
int threshold;
/**
* 当前加载因子
*/
final float loadFactor;
/**
* 记录当前集合被修改的次数
*/
transient int modCount;
构造方法
/**
* 无参构造 则会取 默认长度 及 默认加载因子 调用下面的构造方法
*/
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
/**
* 手动定义长度 及 默认加载因子 调用下面的构造方法
*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* map类型的参数 构造方法
*/
public HashMap(Map<? extends K, ? extends V> m) {
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
inflateTable(threshold);
putAllForCreate(m);
}
/**
* 指定长度 及 加载因子
*/
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;
init();
}
以上则会发现,调用构造方法,虽然定义了 长度 及 加载因子,但并未进行初始化。
此时 table 对象 仍然为 EMPTY_TABLE
put方法
public V put(K key, V value) {
//校验 当前 数组为空 则进行初始化 [✨此过程为要点]
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
//key 为 null 时,进行针对 null key 的赋值逻辑 [✨此过程为要点]
if (key == null)
return putForNullKey(value);
//计算 key 对应的 hash 值
int hash = hash(key);
//根据 hash 值 计算出 索引值 i [该key 对应的value 应放在 数组的索引位置] [✨此过程为要点]
int i = indexFor(hash, table.length);
//遍历 i 索引对应的 entry对象,如果hash 及 key 相等[引用 或 值 相等]
//则将 value 替换 oldValue 并将 oldValue返回 [✨此过程为要点]
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
//记录修改次数
modCount++;
//若上方没有匹配到相同的key 添加 Entry 对象
addEntry(hash, key, value, i);
return null;
}
inflateTable 初始化方法
private void inflateTable(int toSize) {
// Find a power of 2 >= toSize
//返回 第一个 大于或等于 参数 2 的 幂值 16 -> 16 、 17 -> 32
//为什么一定要取 2的幂 后续会讲解 [✨此过程为要点]
int capacity =