基本原理
Map接口的基于哈希表的实现。
HashMap的实例有两个影响其性能的参数:初始容量和负载因子。容量是哈希表中的桶数,初始容量只是哈希表创建时的容量。负载因子是哈希表在其容量自动增加之前允许达到的程度的度量。当哈希表中的条目数超过负载因子和当前容量的乘积时,对哈希表进行重新哈希(即重建内部数据结构),使哈希表具有大约两倍的桶数。
作为一般规则,默认负载因子 (.75) 在时间和空间成本之间提供了良好的折衷。较高的值会减少空间开销,但会增加查找成本(反映在HashMap类的大多数操作中,包括get和put )。在设置其初始容量时,应考虑映射中的预期条目数及其负载因子,以尽量减少重新哈希操作的次数。如果初始容量大于最大条目数除以负载因子,则不会发生重新哈希操作。
核心知识点
1. 初始容量
容量是哈希表中的桶数,初始容量只是哈希表创建时的容量。
//默认初始容量 - 必须是 2 的幂
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16`
2. 最大容量
//最大容量,如果一个更高的值由任何一个带参数的构造函数隐式指定时使用。 必须是 2 <= 1<<30 的幂。
static final int MAXIMUM_CAPACITY = 1 << 30;
3. 负载因子
//负载因子是哈希表在其容量自动增加之前允许达到的程度的度量。
static final float DEFAULT_LOAD_FACTOR = 0.75f;
4. 数据结构
数组+链表
5. 扩容机制
当哈希表中的条目数超过负载因子和当前容量的乘积时,对哈希表进行重新哈希(即重建内部数据结构),使哈希表具有大约两倍的桶数。
6. 构造函数
/** *构造一个具有指定初始容量和负载因子的空HashMap 。
参数:
initialCapacity -
初始容量 loadFactor – 负载因子
抛出: IllegalArgumentException –
如果初始容量为负或负载因子为非
**/
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0) //判断传入的容量
throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
//出入的容量不能超过最大容量30,如果超过则设置位最大容量
if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY;
//负载因子不能为负数
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
7. put元素
计算hash值
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//对key进行16进制的位运算获取hash值
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
添加元素
/**
*hash 元素key的hash值
*key 元素key
*value – 要放置的值
*onlyIfAbsent – 如果为真,则不更改现有值
*evict – 如果为 false,则表处于创建模式。
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K,V>[] tab;//定义节点数组
Node<K,V> p;
int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
//如果当前map为空则定义默认容量16
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
//如果根据hash计算的下标,查找数组中的元素不存在则创建一个node放置元素
tab[i] = newNode(hash, key, value, null);
else {
//如果元素在数组中已经存在
Node<K,V> e;
K k;
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//直接覆盖元素
else if (p instanceof TreeNode)
//如果为红黑树树结构,插入树中
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//链表结构,插入链表后面
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break; }
//如果已经存在相同的key则覆盖
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
} if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount; //如果当前链表节点超过8个,则进行扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
8. 指针碰撞
如果当前元素的hash值位运算后所在的位置已经有元素,则将元素打包成node节点挂在当前节点后面
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
//如果当前节点的下一个节点为null,则将插入的元素打包成newNode插入当前节点后面
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash); break;
}
}
9. 查找原理
//如果通过key能获取到value则返回,否则返回null
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
/**
* Implements Map.get and related methods
* @param hash hash for key
* @param key the key
* @return the node, or null if none
*/
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab;
Node<K,V> first, e;
int n; K k;
//先根据数组查找中当前被查找的元素位于哪一个桶中,然后遍历桶中的链表
if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) {
//如果当前node数组中的第一个node的hash相同则返回,
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
//否则开始遍历当前链表
if ((e = first.next) != null) {
if (first instanceof TreeNode)
//红黑树查找
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
//在do-while循环中获取hash值和key值相同的node节点返回
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
本次分析就结束了,收工! "本文正在参加「金石计划」"