一、概述
1.1 jdk_1.8之前HashMap都是基于数组+链表实现 非线程安全的。
1.2 缺点:如果出现hash碰撞(桶entry碰撞),就会退化成单链表!查找时间从O(1)到O(n)。最好不要出现hash碰撞。
2.1 jdk_1.8之前后HashMap都是基于数组+链表+红黑树实现的 非线程安全的。
因为jdk_1.8之前出现桶碰撞, 在链表中查找数据会出现很大的性能损失。所以jdk_1.8之后当出现hash值冲突时, 如果链表node节点大于8时不再采用链表,转而使用红黑树代替链表!提高查找效率。
关于性能提升参考:http://blog.csdn.net/lc0817/article/details/48213435/
二、源码解析
1 字段解析(注释来自百度翻译 勿怪)
/**
*表中,第一次使用初始化,并调整其大小为
*必要。分配时,长度总是两个幂。
*我们也允许在某些操作中允许长度为零
*目前不需要的自举机构。)
*/
transient Node[]table; 这个就是上图中的 hash数组。
/ * *
*此映射中包含的键值映射的数量。
* /
transient int size; 个人觉得就是Node<k,v>节点数量
/ * *
*调整大小的下一个尺寸值(容量*负载因子)。
*
* @系列
* /
/ /(javadoc的描述是真实的在序列化。此外,如果尚未分配表数组,则字段保留初始数组容量,或零表示 default_initial_capacity。)
int threshold; 个人理解为扩容阀值 如果 size(node节点) 大于这个值是 则对table数组进行扩容。
float loadFactor; 装载因子
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 默认初始化数组大小为16
static final float DEFAULT_LOAD_FACTOR = 0.75f; 默认装载因
/ * *
*使用树的bin计数阈值,而不是列表
*仓。当添加一个元素到一个
* bin至少有这么多节点。值必须更大。
*大于2,至少应符合假设的8
*树搬迁转换回平原垃圾桶后
*收缩。
* /
static final int TREEIFY_THRESHOLD = 8; 链表最大长度 大于这个长度,链表转化为红黑树
2. 构造函数
相关自己可以看下源码
知道存储的数据大小时最好指定大小
3. 关于Node类型
Node<k,v>[] table;
Node<K,V> 是一个HashMap的一个静态内部类。他是实现于Map.Entry<k,v>
重要参数:
final int hash; hash值
final K key; 存储的key
V value; 存储的值
Node<k,v> next; 指向的下一个节点的地址
4 存储方法
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
//通过这里我们也就可以发现key是可以为null的,如果为空返回0也就是table[0]的位置。
return (key ==null) ?0 : (h = key.hashCode()) ^ (h >>>16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node[] tab; Node p; int n, i;
//将table赋值给 tab 如果tab为null或者长度为0 则重新去初始化table(注意在resize里面 由于 现在tab和table 指向的是同一个地址 所以也是初始化tab)
if ((tab =table) ==null || (n = tab.length) ==0)
n = (tab = resize()).length;
//tab[i = (n -1) & hash] 这样写还是第一次见, 反正意思通过 (n -1) & hash 得到数组下标的值 为空的时候 就去创建一个新的节点并保存到那个下标((n -1) & hash)上去
if ((p = tab[i = (n -1) & hash]) ==null)
tab[i] = newNode(hash, key, value, null);
else {
//一旦不为空 所以就hash碰撞了。需要组成 链表或者树。
Node e; K k;
//如果发现hash值(下标) 和 将要存储的key 都是一致的, 注意:p是通过(p = tab[i = (n -1) & hash]) 得到的 将这个值赋给e, 后面会对e执行是否覆盖value的操作。
if (p.hash == hash &&((k = p.key) == key || (key !=null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
//判断 节点是否是红黑树类型 如果是执行红黑插入操作。
e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
else {
//到这里说明链表存储的,则对链表进行循环依次向后查找
for (int binCount =0; ; ++binCount) {
//将p指向的下一个节点赋值给 e 如果为null这是链表的最后一个节点了 则将创建一个新节点赋值给p的下一个节点即(next)
if ((e = p.next) ==null) {
p.next = newNode(hash, key, value, null);
//如果冲突节点达到8个,调用treeifyBin(tab, hash),这个treeifyBin首先回去判断当前hash表的长度,如果不足64的话,实际上就只进行resize,扩容table,如果已经达到64,那么才会将冲突项存储结构改为红黑树。
if (binCount >=TREEIFY_THRESHOLD -1)// -1 for 1st
treeifyBin(tab, hash);
break;
}
//当e 不等于空 且他的hash值和key等于要存储的hash值 和key时则结束循环 后面会判断是否对value覆盖
if (e.hash == hash && ((k = e.key) == key || (key !=null && key.equals(k))))
break;
//把e赋给p继续循环
p = e;
}
}
//如果e不等于空说明数或者链表存在或者插入 hash值和key一样的节点了, 这里就是进行对value判断是否用新的值覆盖以前的值!
if (e !=null) {// existing mapping for key
V oldValue = e.value;
//判断是否修改已插入节点的value
if (!onlyIfAbsent || oldValue ==null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
//插入新节点后,hashmap的结构调整次数+1
++modCount;
if (++size >threshold) //判断节点大小是否大于扩容阀值大于就执行扩容
resize();
afterNodeInsertion(evict);
return null;
}
作为第一次写文章,大量参考其他文章只是对部分做了点个人理解和 详细解释!
不到之处欢迎指正。
发现对代码支持很差呢!