本篇目录:
HashMap底层数据结构 HashMap中的常量 HashMap中的变量 HashMap的初始化 初始化后第一次调用put
HashMap底层数据结构
JDK1.8之前HashMap由数组 链表
组成。数组是HashMap的主体,链表主要是为了解决哈希冲突而存在。哈希冲突是两个对象调用hashCode
方法计算的哈希值相同导致计算的数组索引值相同。
JDK1.8之后HashMap由数组 链表 红黑树
组成,当链表长度大于8,且数组长度大于64时,该索引位置上的所有数据改使用红黑树存储。当链表长度大于8,但数组长度不大于64时,链表也不会转换成红黑树,而是会扩容数组。
详见源码:
代码语言:javascript复制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();
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
图片来自网络
HashMap中的常量
代码语言:javascript复制static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 默认初始大小
static final int MAXIMUM_CAPACITY = 1 << 30; //最大容量 2的30次方
static final float DEFAULT_LOAD_FACTOR = 0.75f; //默认加载因子(负载因子)
static final int TREEIFY_THRESHOLD = 8; //当前链表长度转红黑树的阈值
static final int UNTREEIFY_THRESHOLD = 6; //红黑树转链表的阈值
static final int MIN_TREEIFY_CAPACITY = 64; //链表转红黑树的数组长度阈值
HashMap中的变量
代码语言:javascript复制/* EntrySet实体,定义成变量可以保证不被重复创建,是一个Map.Entry集合
* Map.Entry是一个接口,HashMap的内部类Node就实现了该接口
*/
transient Set<Map.Entry<K,V>> entrySet;
/* 用来存储HashMap中的键值对,是一个Node数组
* Node是一个内部类,实现了Map.Entry接口,就是一个单向链表
*/
transient Node<K,V>[] table;
/* 记录HashMap中键值对的数量,put和remove都会对其造成改变*/
transient int size;
/* 当HashMap内部结构发生变化时才会修改
* 当HashMap内部结构发生改变时时则会 modCount
*/
transient int modCount;
/* threshold表示一个临界值,当HashMap的size大于这个值的时候进行resize */
int threshold;
HashMap的初始化
HashMap的四个构造方法:
代码语言:javascript复制// 指定初始大小和加载因子
public HashMap(int initialCapacity, float loadFactor) {...}
// 指定初始大小
public HashMap(int initialCapacity) {...}
// 无参构造,全部使用默认常量
public HashMap() {...}
// 使用现有的Map来构造HashMap
public HashMap(Map<? extends K, ? extends V> m) {...}
与ArrayList不同,new ArrayList()
时,ArrayList的底层数组大小是0,在添加第一个元素后大小会被扩容为10。
HashMap在JDK1.8之前,在构造方法中就会初始化一个长度16的Entry[]
数组用来存储键值对。
在JDK1.8以后,在第一次调用put方法时创建一个Node[]
数组用来存储键值对数据,Node实现了Map.Entry接口。
Node源码:该类是一个内部类
代码语言:javascript复制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) {...}
public final K getKey() {...}
public final V getValue() {...}
public final String toString() {...}
public final int hashCode() {...}
public final V setValue(V newValue) {...}
public final boolean equals(Object o) {...}
}
初始化后第一次put
这里是代码执行流程:
① put()
方法:
// 调用put方法
public V put(K key, V value) {
// 这里调用了putVal方法
return putVal(hash(key), key, value, false, true);
}
② 进入putVal()
方法:
// 判断当前表中是否为空(是不是第一次添加元素)
if ((tab = table) == null || (n = tab.length) == 0)
// 进入resize方法
n = (tab = resize()).length;
这里的变量table
是HashMap中用来存储键值对的Node<K,V>[]
数组。
如下:transient Node<K,V>[] table;
③ 进入resize()
方法:
由于该方法内容比较多,所以这里以程序运行流程列明,其余代码请自行查看HashMap源码。
代码语言:javascript复制// 以下内容以程序运行流程
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
newCap = DEFAULT_INITIAL_CAPACITY; //16
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //16 * 0.75
threshold = newThr; //将新的临界值赋值给threshold
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; //创建一个16长度的Node数组
table = newTab; //将长度16的数组赋值给变量table
return newTab; //将Node数组返回
④ 回到putVal
方法
if ((tab = table) == null || (n = tab.length) == 0)
// 刚刚进入如下方法,得到n为初始化的数组长度16
n = (tab = resize()).length;
// 通过&位运算 得到i=1,判断Node数组索引1的元素是否为空
if ((p = tab[i = (n - 1) & hash]) == null)
// 进入newNode方法,该方法会根据参数创建一个单向链表
tab[i] = newNode(hash, key, value, null);
//这里省略不执行的代码
...
// 由于进行了初始化,所以HashMap内部结构发生了变化,所以 modCount
modCount;
// 这里判断了HashMap的键值对数量是否大于扩容阈值
if ( size > threshold)
resize();
// 该方法是为了继承HashMap的LinkedHashMap类服务,这里没有实际作用
afterNodeInsertion(evict);
// 到这里第一次调用put就结束了
return null;
以上内容调用的newNode
方法:
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
// 返回Node链表
return new Node<>(hash, key, value, next);
}
由于篇幅已经很长了,所以“HashMap扩容机制解析”另写一篇。