HashMap
底层机制及源码剖析
HashMap
类继承关系和构造器
构造器
Constructor and Description |
---|
HashMap() 构造一个空的 HashMap ,默认初始容量(16)和默认负载系数(0.75)。 |
HashMap(int initialCapacity) 构造一个空的 HashMap 具有指定的初始容量和默认负载因子(0.75)。 |
HashMap(int initialCapacity, float loadFactor) 构造一个空的 HashMap 具有指定的初始容量和负载因子。 |
HashMap(Map<? extends K,? extends V> m) 构造一个新的 HashMap 与指定的相同的映射 Map 。 |
常用方法
Modifier and Type | Method and Description |
---|---|
void | clear() 从这张地图中删除所有的映射。 |
Object | clone() 返回此 HashMap 实例的浅拷贝:键和值本身不被克隆。 |
V | compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) 尝试计算用于指定键和其当前映射的值的映射(或 null 如果没有当前映射)。 |
V | computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction) 如果指定的键尚未与值相关联(或映射到 null ),则尝试使用给定的映射函数计算其值,并将其输入到此映射中,除非 null 。 |
V | computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) 如果指定的密钥的值存在且非空,则尝试计算给定密钥及其当前映射值的新映射。 |
boolean | containsKey(Object key) 如果此映射包含指定键的映射,则返回 true 。 |
boolean | containsValue(Object value) 如果此地图将一个或多个键映射到指定值,则返回 true 。 |
Set<Map.Entry<K,V>> | entrySet() 返回此地图中包含的映射的Set 视图。 |
void | forEach(BiConsumer<? super K,? super V> action) 对此映射中的每个条目执行给定的操作,直到所有条目都被处理或操作引发异常。 |
V | get(Object key) 返回到指定键所映射的值,或 null 如果此映射包含该键的映射。 |
V | getOrDefault(Object key, V defaultValue) 返回到指定键所映射的值,或 defaultValue 如果此映射包含该键的映射。 |
boolean | isEmpty() 如果此地图不包含键值映射,则返回 true 。 |
Set<K> | keySet() 返回此地图中包含的键的Set 视图。 |
V | merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction) 如果指定的键尚未与值相关联或与null相关联,则将其与给定的非空值相关联。 |
V | put(K key, V value) 将指定的值与此映射中的指定键相关联。 |
void | putAll(Map<? extends K,? extends V> m) 将指定地图的所有映射复制到此地图。 |
V | putIfAbsent(K key, V value) 如果指定的键尚未与某个值相关联(或映射到 null ),则将其与给定值相关联并返回 null ,否则返回当前值。 |
V | remove(Object key) 从该地图中删除指定键的映射(如果存在)。 |
boolean | remove(Object key, Object value) 仅当指定的密钥当前映射到指定的值时删除该条目。 |
V | replace(K key, V value) 只有当目标映射到某个值时,才能替换指定键的条目。 |
boolean | replace(K key, V oldValue, V newValue) 仅当当前映射到指定的值时,才能替换指定键的条目。 |
void | replaceAll(BiFunction<? super K,? super V,? extends V> function) 将每个条目的值替换为对该条目调用给定函数的结果,直到所有条目都被处理或该函数抛出异常。 |
int | size() 返回此地图中键值映射的数量。 |
Collection<V> | values() 返回此地图中包含的值的Collection 视图。 |
HashMap
结构图
源码分析
HashMap<Integer, String> maps = new HashMap<>();
maps.put(1,"湛江");
maps.put(2,"海康");
maps.put(3,"西安");
maps.put(4,"南宁");
步骤1:使用无参构造创建一个 HashMap集合
final float loadFactor;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
public HashMap() {
对加载因子 loadFactor 赋值为 0.75
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
步骤2:调用 HashMap 中 put 方法
public V put(K key, V value) {
先调用 一个hash方法得到一个 hash 值
return putVal(hash(key), key, value, false, true);
}
调用 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;
如果表为空并长度为0时,就调用 resize 方法进行扩容
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
如果根据 hash 和 表长度-1 计算结果得到一个索引位置为null时
if ((p = tab[i = (n - 1) & hash]) == null)
直接创建一个 newNode 对象 存放在该索引位置
tab[i] = newNode(hash, key, value, null);
else {
如果存在进行如下判断
Node<K,V> e; K k;
如果存在,并且两者key相等或equals值相同时,则不添加
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) {
如果在链表中没有该元素直接创建一个 newNode对象插入,跳出循环
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
当前链表是否达到 8,达到进行树化
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
如果存在则不添加
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;记录修改的次数
if (++size > threshold)判断当前集合长度是否能添加一个元素,不能则进行扩容
resize();
afterNodeInsertion(evict);
return null;
}
调用 newNode 方法进行创建一个 Node 对象
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
return new Node<>(hash, key, value, next);
}
调用 HashMap 本类中 一个静态内部类 Node 进行相关初始化
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
扩容算法
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
记住结论:
扩容机制【和HashSet
相同】到达临界值都是以2倍的方式进行扩容
HashMap
底层维护了Node
类型的数组table
,默认为null
- 当创建对象时,将加载因为
loadfactor
初始化为0.75
- 当添加
key-value
时,通过key
的哈希值得到在table
的索引位置,然后判断该索引处是否有元素,如果没有元素直接添加。如果该索引处有元素,继续判断该元素的key
和准备加入的key
是否相等,如果相等,则直接替换掉value
值,如果不相等需要判断,是树结构还是链表结构,做出相应处理。如果添加时发现容量不够,则需要扩容 - 第1次添加,则需要扩容
table
容量为16,临界值threshole为12(16*0.75)
- 以后再扩容,则需要扩容
table
容量为原来的2倍(32)
,临界值为原来的2倍,即24,依次类推 - 在
java8
中如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8)
,并且table
的大小>=MIN_TREEIFY_CAPACITY(默认64)
,就会进行树化【红黑树】
代码案例:
package com.hspedu.map_;
import java.util.HashMap;
@SuppressWarnings({"all"})
public class HashMapSource1 {
public static void main(String[] args) {
HashMap map = new HashMap();
map.put("java", 10);//ok
map.put("php", 10);//ok
map.put("java", 20);//替换value
System.out.println("map=" + map);//
/*老韩解读HashMap的源码+图解
1. 执行构造器 new HashMap()
初始化加载因子 loadfactor = 0.75
HashMap$Node[] table = null
2. 执行put 调用 hash方法,计算 key的 hash值 (h = key.hashCode()) ^ (h >>> 16)
public V put(K key, V value) {//K = "java" value = 10
return putVal(hash(key), key, value, false, true);
}
3. 执行 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 数组为null, 或者 length =0 , 就扩容到16
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//取出hash值对应的table的索引位置的Node, 如果为null, 就直接把加入的k-v
//, 创建成一个 Node ,加入该位置即可
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;//辅助变量
// 如果table的索引位置的key的hash相同和新的key的hash值相同,
// 并 满足(table现有的结点的key和准备添加的key是同一个对象 || equals返回真)
// 就认为不能加入新的k-v
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)//如果当前的table的已有的Node 是红黑树,就按照红黑树的方式处理
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);
//加入后,判断当前链表的个数,是否已经到8个,到8个,后
//就调用 treeifyBin 方法进行红黑树的转换
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash && //如果在循环比较过程中,发现有相同,就break,就只是替换value
((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; //替换,key对应value
afterNodeAccess(e);
return oldValue;
}
}
++modCount;//每增加一个Node ,就size++
if (++size > threshold[12-24-48])//如size > 临界值,就扩容
resize();
afterNodeInsertion(evict);
return null;
}
5. 关于树化(转成红黑树)
//如果table 为null ,或者大小还没有到 64,暂时不树化,而是进行扩容.
//否则才会真正的树化 -> 剪枝
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
}
*/
}
}
package collection_.collectionP.map_;
import java.util.HashMap;
/**
* @author: 海康
* @version: 1.0
*/
public class HasHMapSource01 {
public static void main(String[] args) {
HashMap hashMap = new HashMap();
hashMap.put("java",10);
hashMap.put("php",20);
hashMap.put("java",20);
System.out.println(hashMap);
}
/**
* 第一步:执行构造器,完成加载因子初始化
* public HashMap() {
* this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
* }
*
* 第二步:执行put方法
* public V put(K key, V value) {
* return putVal(hash(key), key, value, false, true);
* }
* 2.1:执行hash(key)方法获取hash值
* 2.1.1
* static final int hash(Object key) {
* int h;
* return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
* }
* 2.1.2
* public int hashCode() {
* int h = hash;
* if (h == 0 && value.length > 0) {
* char val[] = value;
*
* for (int i = 0; i < value.length; i++) {
* h = 31 * h + val[i];
* }
* hash = h;
* }
* return h;
* }
* 2.2执行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;//铺助变量
* if ((tab = table) == null || (n = tab.length) == 0)//如果table数组为null
* n = (tab = resize()).length;//则调用resize()方法完成扩容,第一次扩容为16
* if ((p = tab[i = (n - 1) & hash]) == null)//如果在table表中该索引位置为null
* //则直接将数据添加在该索引位置
* tab[i] = newNode(hash, key, value, null);
* else {//如果不为空则进行以下判断
* Node<K,V> e; K k;//铺助变量
* //如果p指定table表中的索引位置中的key值和准备添加到该索引位置的key值相同,
* //并两者指定相同对象引用或内容相同,则将p指定table表中索引位置数据赋给e
* 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指定table表中索引的下一个位置为null,
* //则直接添加到table表中的p指定table表中索引的下一个位置
* p.next = newNode(hash, key, value, null);
* //如果该链表大于8,则进行树化
* if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
* treeifyBin(tab, hash);
* break;
* }
* //如果p指定table表中的索引位置中的key值和准备添加到该索引位置的key值相同,
* //并两者指定相同对象引用或内容相同,则将p指定table表中索引位置数据赋给e
* if (e.hash == hash &&
* ((k = e.key) == key || (key != null && key.equals(k))))
* break;
* p = e;//重新将e赋给P,使用得P继续指定下一个位置
* }
* }
* if (e != null) { // existing mapping for key
* V oldValue = e.value;
* if (!onlyIfAbsent || oldValue == null)
* e.value = value;
* afterNodeAccess(e);
* return oldValue;
* }
* }
* ++modCount;
* if (++size > threshold)
* resize();
* afterNodeInsertion(evict);
* return null;
* }
*/
}
5. 关于树化(转成红黑树)
//如果table 为null ,或者大小还没有到 64,暂时不树化,而是进行扩容.
//否则才会真正的树化 -> 剪枝
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
}
注意是:不是一直扩容数组,当数组达到下面代码时,则不再扩容数组容量了
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
模拟HashMap
集合触发树机制代码
package collection_.collectionP.map_;
import java.util.HashMap;
import java.util.Objects;
/**
* @author: 海康
* @version: 1.0
*/
public class HashMapSource02 {
public static void main(String[] args) {
HashMap hashMap = new HashMap();
for (int i = 0; i < 12; i++) {
hashMap.put(new A(i),"hello");
}
}
}
class A {
public int num;
public A(int num) {
this.num = num;
}
@Override
public String toString() {
return "A{" +
"num=" + num +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
A a = (A) o;
return num == a.num;
}
@Override
public int hashCode() {
// return Objects.hash(num);
return 168;//指定返回一个hashCode值
}
}