目录
一、HashMap简介
1.1 简述
在api文档中,其大致定义为
基于哈希表实现的Map接口。 此实现提供了所有可选的map操作,并允许key和value为null。(HashMap类大致相当于Hashtable ,除了它是不同步的,并允许null)。这个类不能保证map中元素的顺序; 特别是,它不能保证顺序在一段时间内保持不变。
1.2、继承结构
- public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
- public abstract class AbstractMap<K,V> implements Map<K,V>
- public interface Map<K,V>
其中,抽象类AbstractMap是为了提供Map接口的基本实现,以减少实现该接口所需的工作量。
1.3、数据结构
在jdk1.8以前,HashMap的数据结构采用的是数组加链表的形式。而在jdk1.8 后,为了提升HashMap的效率,引入了红黑树。图大致如下:
二、源码分析
2.1、常量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
DEFAULT_INITIAL_CAPACITY:初始容量,初始的数组大小,默认为16。
static final int MAXIMUM_CAPACITY = 1 << 30;
MAXIMUM_CAPACITY:数组的最大容量,当数组扩容扩至该值时就无法再扩大了。
static final float DEFAULT_LOAD_FACTOR = 0.75f;
DEFAULT_LOAD_FACTOR:默认的负载因子,当数组中数据的数量超过负载因子和当前容量的乘积时,数组将会进行扩容至原来的2倍。即默认情况下,数据量达到数组大小的3/4时,数组就会扩容。
static final int TREEIFY_THRESHOLD = 8;
TREEIFY_THRESHOLD:链表转红黑树的阈值。即当链表长度大于等于8时,数据结构会转变成红黑树。
static final int UNTREEIFY_THRESHOLD = 6;
UNTREEIFY_THRESHOLD:红黑树转链表的阈值。即当红黑树节点小于等于6树,数据结构转为链表。
static final int MIN_TREEIFY_CAPACITY = 64;
MIN_TREEIFY_CAPACITY:链表树化的最小值。在调用HashMap中生成红黑树的treeifyBin(Node<K,V>[] tab, int hash)方法中,会先进行一次判断,如果元素的数量少于MIN_TREEIFY_CAPACITY,会进行数组扩容,而不是树化。
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) {
//……
}
}
2.2、属性
transient Node<K,V>[] table;
table:用来保存key-value的散列数组。其中,值得一提的是,table数组能引用的对象有两种:Node和TreeNode,分别用来保存链表和红黑树的。他们都是HashMap的静态内部类,继承结构如下:
Node
Hash$Node -- > Map$Entry,详情如下
- static class Node<K,V> implements Map.Entry<K,V>
- Map接口中: interface Entry<K,V>
TreeNode
HashMap$TreeNode -- > LinkedHashMap$Entry -- > Hash$Node -- > Map$Entry,详情如下
- static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V>
- LinkedHashMap类中: static class Entry<K,V> extends HashMap.Node<K,V>
- static class Node<K,V> implements Map.Entry<K,V>
- Map接口中: interface Entry<K,V>
显然,Node是TreeNode的父类,所以tables数组才能保存TreeNode的引用。
transient Set<Map.Entry<K,V>> entrySet;
entrySet:保存entrySet()的缓存。在调用entrySet()时,会进行判断,若entrySet不为空,则返回entrySet;若为空,则为entrySet创一个EntrySet
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}
transient int size;
size:map中元素的个数
transient int modCount;
modCount:保存该HashMap对像被修改的次数。每当该HashMap对象的key-value被修改时(put,remove,clear等),modCount ++;modCount是实现HashMap快速失败(fast-fail)的关键元素。
abstract class HashIterator {
Node<K,V> next; // next entry to return
Node<K,V> current; // current entry
int expectedModCount; // for fast-fail
int index; // current slotHashIterator() {
expectedModCount = modCount;
Node<K,V>[] t = table;
current = next = null;
index = 0;
if (t != null && size > 0) { // advance to first entry
do {} while (index < t.length && (next = t[index++]) == null);
}
}final Node<K,V> nextNode() {
Node<K,V>[] t;
Node<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
if ((next = (current = e).next) == null && (t = table) != null) {
do {} while (index < t.length && (next = t[index++]) == null);
}
return e;
}//......
}
当使用Iterator对HashMap进行迭代时,迭代器会先将modCount的值保存为expectedModCount,在后续使用中,如果预期值与modeCount不等,说明HashMap进行了修改,抛出ConcurrentModificationException异常。要注意的是,只是更改value的值是不会引起快速失败的,迭代时也能正确读取值。
int threshold;
threshold:衡量数组是否需要扩增的一个阈值。threshold = capacity * loadFactor,当size>=threshold的时候,对数组进行扩容。
final float loadFactor;
负载因子,不设的话为默认值(DEFAULT_LOAD_FACTOR)0.75。计算HashMap的实时装载因子的公式为:size/capacity,即默认数据量达到数组的3/4大小时数组扩容。
2.3、方法
在上述内容中,也多少讲到了一些方法。考虑到个人精力跟篇幅等问题,在这里只简单用注释的方式讲下put方法及涉及到的putVal
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
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)
n = (tab = resize()).length;
// 若该桶为空,则创建Node
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else { // 存在节点的情况
Node<K,V> e; K k;
// key为null或key相等,e引用该节点,在后面代码中直接修改value值
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为null或已存在,e引用该节点,在后面代码中直接修改value值
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // key已存在(null也可作key),修改value值,并返回旧的value值
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
// 达到扩容阈值,进行扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}