原文链接:
通俗易懂Hashmap源码解析_java阳开发之路的博客-CSDN博客_hashmap源码
https://www.csdn.net/tags/MtTaEgxsNTYxMDAyLWJsb2cO0O0O.html
·
浅析HashMap的结构
哈希表是一种以键 - 值(key-value)存储数据的结构,我们只要输入待查找的值的key,就可以找到其对应的值即 Value。
HashMap的设计十分精巧,本文不作深入分析,只简要描述HashMap的原理。
HashMap的结构是一个entry数组,每一个entry节点在数组中的位置由entry节点中的key来决定,即用一个哈希函数把 key 换算成一个确定的位置,然后把 value 放在数组的这个位置。
了解哈希数组的同学都知道,【不同元素的哈希映射有可能是相同的,意味着两个完全不同的元素,是有可能会映射到相同的位置上】。
HashMap的做法是:将新元素放在已有元素的尾端。因此,entry节点的类型实际上是一个链表结构:
如图所示:
结论:
- 当 链表的长度 >= 7 ,且entry数组长度 < 64 时,此时会优先选择扩容,(不转红黑树)。
- 只有当 链表的长度 >= 7,且entry数组的长度 >= 64 时,才会将链表转为红黑树。
- 当该entry节点的长度小于等于6了,则将它的红黑树转回为链表结构;
·
·
Hashmap源码-3个初始化方法:
1)链表与红黑树互相转化的阀值:
//将链表转化为红黑树的阀值
static final int TREEIFY_THRESHOLD = 8;
//将红黑树转回为链表的阀值
static final int UNTREEIFY_THRESHOLD = 6;
//将链表转为红黑树的最小entry数组的长度为 64
//当链表的长度 >= 7,且entry数组的长度>= 64时,才会将链表转为红黑树。
static final int MIN_TREEIFY_CAPACITY = 64;
2) Map的默认初始化容量以及容量极限:
//HashMap默认的初始容量大小--16,容量必须是2的幂
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//HashMap的容量极限,为2的30次幂;
static final int MAXIMUM_CAPACITY = 1 << 30;
3) 默认负载因子、实际元素数量:
//负载因子的默认大小,
//元素的数量/容量得到的实际负载数值与负载因子进行对比,来决定容量的大小以及是否扩容;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//HashMap的实际元素数量
transient int size;
4)table——Entry数组;
//Node是Map.Entry接口的实现类,可以将这个table理解为是一个entry数组;
//每一个Node即entry,本质都是一个单向链表
transient Node<K,V>[] table;
5)Map已经修改的次数、下一次扩容的大小、存储负载因子的常量:
//HashMap已在结构上修改的次数 结构修改是指更改 HashMap 中的映射数量或以其他方式修改其内部结构(例如,重新散列)的那些
transient int modCount;
//下一次HashMap扩容的阀值大小,如果尚未扩容,则该字段保存初始entry数组的容量,或用零表示
int threshold;
//存储负载因子的常量,初始化的时候将默认的负载因子赋值给它;
final float loadFactor;
什么是Map已经修改的次数?
6)构造函数1——默认的无参构造函数:
将默认的负载因子0.75f 传给 存储负载因子的常量;
//默认的构造函数
public HashMap() {
//将默认的负载因子0.75f传给存储负载因子的常量
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
7)构造函数2——带指定容量参数的构造函数
将默认的负载因子0.75f当做负载因子常量,连同用户传入的容量参数,一起传给两个参数都带的构造函数,并调用之。
//带指定容量参数的构造函数
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
8)构造函数3——带指定容量大小和负载因子大小参数的构造函数
//带指定容量大小和负载因子大小参数的构造函数
public HashMap(int initialCapacity, float loadFactor) {
//指定的容量大小不可以小于0,否则将抛出IllegalArgumentException异常
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
//判定指定的容量大小是否大于HashMap的容量极限
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//指定的负载因子不可以小于0或为Null,否则将抛出IllegalArgumentException异常
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
//传入用户指定的负载因子
this.loadFactor = loadFactor;
//Map扩容的阀值大小
//tableSizeFor方法用于查找到距离容量initialCapacity最近的2次幂值;
this.threshold = tableSizeFor(initialCapacity);
}
9)tableSizeFor()方法:
在刚才的构造函数中,我们看到了tableSizeFor()这一个方法,将容量参数传进去,返回结果赋值给threshold 。
看源码:
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
可以看出,其实代码很简单,就是对容量cap进行了几次无符号右移的操作,最后返回。
那么这个方法的目的和作用是什么呢?
这个方法的作用是:用于查找距离容量cap最近的2次幂值,比如:
- cap是5~7,那么该方法的返回结果就是8;
- cap是9~15,那么该方法的返回结果就是16;
- cap是17~31,那么该方法的返回结果就是32;
- 以此类推...
10)构造函数4——传入一个Map集合的构造函数:
//传入一个Map集合,将Map集合中元素Map.Entry全部添加进HashMap实例中
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
//此构造方法主要实现了Map.putAll()
putMapEntries(m, false);
}
·