参考:https://www.jianshu.com/p/9ca74bdfdb6b
1.HashMap底层实现原理
2.HashMap工作原理
3.HashMap冲突产生及解决
4.HashMap不同步原因分析及解决办法
5.HashMap扩容
首先,我学习时JDK版本为1.8(在网上搜的,因为我学的时候已经是1.8了,自行百度了解,有错误指出谢谢)
JDK1.8中对Hashmap做了以下改动。
- 引入红黑树,优化数据结构
- 使用一个Node数组来存储数据,但这个Node可能是链表结构,也可能是红黑树结构。
- 将链表头插法改为尾插法
- 优化hash算法
- 出现哈希冲突时,1.7把数据存放在链表,1.8是先放在链表,链表长度超过8就转成红黑树
源码
public class HashMap<K,V> extends AbstractMap<K,V>implements Map<K,V>, Cloneable, Serializable {}
基本属性
private static final long serialVersionUID = 362498820763181265L; // 序列号
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // (默认数组长度)初始容量为16
static final int MAXIMUM_CAPACITY = 1 << 30; // 最大数组容量2^30
static final float DEFAULT_LOAD_FACTOR = 0.75f; // 在构造函数中未指定时使用的负载因子。默认负载因子0.75
static final int TREEIFY_THRESHOLD = 8; // (链表转红黑树的阈值) 桶的树化阈值:即 链表转成红黑树的阈值,在存储数据时,当链表长度 > 该值时,则将链表转换成红黑树
static final int UNTREEIFY_THRESHOLD = 6; // (扩容时红黑树转链表的阈值)桶的链表还原阈值:即 红黑树转为链表的阈值,当在扩容(resize())时(此时HashMap的数据存储位置会重新计算),在重新计算存储位置后,当原有的红黑树内数量 < 6时,则将 红黑树转换成链表
static final int MIN_TREEIFY_CAPACITY = 64; // 最小树形化容量阈值:即 当哈希表中的容量 > 该值时,才允许树形化链表 (即 将链表 转换成红黑树)
// 否则,若桶内元素太多时,则直接扩容,而不是树形化
// 为了避免进行扩容、树形化选择的冲突,这个值不能小于 4 * TREEIFY_THRESHOLD
transient Node<K,V>[] table; // 存储元素的数组,允许长度为0,总是2的幂次倍
transient Set<Map.Entry<K,V>> entrySet; // key-value集合
transient int size; // 映射中包含的键-值映射的数目(存放元素的数组,但是不等于数组的长度!!!)
transient int modCount; // 每次扩容或更改map结构的计数器
int threshold; // 临界值,当实际大小(容量*装载系数)超过临界值时,会进行扩容。
构造函数
1.初始容量(未定义)负载因子(未定义)
public HashMap(int initialCapacity, float loadFactor) {
// 初始容量不能小于0,否则会报错
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
// 初始容量大于最大值,只能为最大值,不能再扩容了
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
// 负载因子不能小于0或者不为数字,抛出异常
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor; // 初始化负载因子
this.threshold = tableSizeFor(initialCapacity); // 初始化临界值
}
2.初始容量(未定义)负载因子(0.75默认)
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
3.初始容量(16默认)负载因子(0.75默认)
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // 所有其他字段为默认值
}
HashMap是数组+链表/红黑树(JDK1.8增加了红黑树部分)实现的
HashMap是基于hash算法实现的,通过put(key,value)存储,get(key)获取。当传入key时,HashMap会根据key.hashcode()计算出hash值,根据hash值将value保存在bucket里。当计算出的hash值相同时,我们称之为hash冲突,HashMap的做法是用链表或红黑树存储相同hash值的value。
例如:同一hash值的元素已经被放在数组同一位置了,这时就添加到同一hash值的元素的后面,他们在数组的同一位置,但是形成了链表,同一各链表上的Hash值是相同的,所以说数组存放的是链表。而当链表长度太长时,链表就转换为红黑树,提高查找的效率。
bucket桶:桶是每个所指数组的元素。在较早的Java版本中,每个存储桶都包含一个Map条目的链接列表。在新的Java版本(1.8版本)中,每个存储桶都包含条目的树结构或条目链接列表。
工作原理
1.put(key,value)存储对象
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
HashMap并没有直接提供putVal接口给用户调用,而是提供的put函数,而put函数就是通过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)
n = (tab = resize()).length; // 扩容
// 情况A:桶为空,直接将元素放入当前位置
// 根据 hash 值确定节点在数组中的插入位置,若此位置没有元素则进行插入,注意确定插入位置所用的计算