HashMap在JDK1.8中的存放数据结构的示意图,就是HashMap本质上是一个Node<K, V>数组table,table中每个index对应一个bucket,这个bucket可能是链表,可能是红黑树。
-
类定义:
类HashMap<K,V>继承了AbstractMap,实现了Map<K,V>、Cloneable和Serializable;
-
类中静态常量:
DEFAULT_INITIAL_CAPACITY:默认的初始化容积为16,如果在使用之前就知道具体会多大,建议直接在创建的时候就定义到那个大小,可以避免多次膨胀;
MAXIMUM_CAPACITY:HashMap最大的容积,大小为1 << 30,即为2^31;
DEFAULT_LOAD_FACTOR:客座率,也就是超过现有的容积的多少比例后直接膨胀;
TREEIFY_THRESHOLD:bucket中的节点数到了多少之后变成红黑树,一般建议是2~8之间的数值,越小,则链表变成红黑树越频繁,越大,则查找速度越慢,需要均衡。
UNTREEIFY_THRESHOLD:一个树的链表还原阈值,当扩容的时候,如果bucket中的元素的个数小于这个值的时候,就还原成链表
MIN_TREEIFY_CAPACITY:哈希表的最小树形化容量,当table数组的长度大于这个值的时候,表中的桶才能树形化,否则桶内元素太多时会扩容,而不是树形化,为了避免进行扩容、树形化选择的冲突,这个值不能小于 4 * TREEIFY_THRESHOLD
-
HashMap中实现添加键值对的方法java.util.HashMap#put调用的是java.util.HashMap#putVal,详细查看其中的代码:
详情参考的URL为:
http://www.cnblogs.com/huaizuo/p/5371099.html
-
HashMap中的java.util.HashMap#get,其实现为先计算key的hash,再从table中获取对应的bucket,再从bucket中的链表或者红黑树中遍历获取对应的value,因此,耗时和链表长度有关,也和红黑树的大小有关。调用的函数是java.util.HashMap#getNode;
-
HashMap中的java.util.HashMap#containsKey函数,其实现原理和上get一样的,一样的调用的java.util.HashMap#getNode,根据返回结果是否为空来进行判断的。
详细源码和注释如下:
先查看HashMap的put函数,发现他调用的是另一个函数java.util.HashMap#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为空,则直接调用resize()函数,初始化存储空间
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//计算插入位置,计算方式为下标为(n - 1) & hash
if ((p = tab[i = (n - 1) & hash]) == null)//如果插入位置没有其他元素,直接插入,p为插入位置的Node
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
//如果插入位置的Node的key和要插入的key一致,则两者相等
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)//如果插入处的Node即p为TreeNode,也就是说是红黑树,则调用putTreeVal
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {//当前插入处的Node仍然为链表,则进行以下操作
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {//判断是否到了bucket的最后一个Node
p.next = newNode(hash, key, value, null);
//超过了链表设置的长度,就开始扩容,调用treeifyBin函数
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;
}
}
//e如果不为空,即为HashMap中存在要插入的键值对的key,更新值
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//如果再插入一个对象后,size超过容积*容积率则扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
从上面代码可以发现,HashMap中的table是一个Node<K, V>数组,而且,table[index]可能是一个链表,也可能是一个树(TreeNode),这个树其实是一颗红黑树。也从上述代码可以知道,一开始存放的是链表,那么什么时候变成的红黑树的呢?我们注意到了treeifyBin()函数,这个函数如下:
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
//如果table为空,或者table的长度小于MIN_TREEIFY_CAPACITY,则进行resize()
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);
}
}