概述
相比较Java7中的链表组合存储,Java8中的HashMap有了大量改进,最为明显的就是Java8中采用数组+链表+红黑树的方式对元素进行存储,这样安全和功能性完备的情况下让其速度更快,同时减少了哈希冲突的情况。
HashMap的主结构类似于一个数组,添加值时通过key确定储存位置.每个位置是一个Node(图中黑点)的数据结构,该结构可组成链表.当发生冲突时,相同hash值的键值对会组成链表.
这种数组+链表的组合形式大部分情况下都能有不错的性能效果,java6、7就是这样设计的.然而,在极端情况下,一组(比如经过精心设计的)键值对都发生了冲突,这时的哈希结构就会退化成一个链表,使HashMap性能急剧下降.
所以在java8中,HashMap的结构实现变为数组+链表+红黑树.如图:
如果您对于红黑树不是特别了解,建议您花一点时间认真研读本文:
如果您对于哈希冲突不是特别了解,建议您花一点时间认真研读本文:
Node节点
就如同上文所述,因为数据结构的不同,HashMap中对数据的存储方式也分为普通的链表节点和红黑树节点
1、链表节点Node:
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) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
很普通,和上文中的linkedList中的节点没什么区别,只是多了一个成员变量hash,用来存储节点数据的hash值
2、树节点 TreeNode:
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent; // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // needed to unlink next upon deletion
boolean red; //是否为红色节点
TreeNode(int hash, K key, V val, Node<K,V> next) {
super(hash, key, val, next);
}
//以下省略其他方法
本文主题为HashMap解析,对红黑树有兴趣的同学可以看概述中的红黑树相关文章,相信会有所收获。
成员变量
private static final long serialVersionUID = 362498820763181265L;
static final int DEFAULT_INITIAL_CAPACITY = 16; // 默认的初始容量16
static final int MAXIMUM_CAPACITY = 1073741824; // 最大容量
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; // 红黑树结构的最小容量
transient HashMap.Node<K, V>[] table;
transient Set<Entry<K, V>> entrySet;
transient int size;
transient int modCount;//修改次数
int threshold;// HashMap的阈值,用于判断是否需要调整HashMap的容量(threshold = 容量*加载因子)
final float loadFactor;
static final int hash(Object var0) {
int var1;
return var0 == null?0:(var1 = var0.hashCode()) ^ var1 >>> 16;
}
值得一提的是HashMap的hash()方法,我们可以看到,HashMap并未直接使用对象的hashCode(),而是经过
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
我们看看网上对此的解释:
而之前我们提到index的运算规则是e.hash & (newCap - 1).由于newCap是2的幂次,那么newCap - 1的高位应该全部为0.如果e.hash值只用自身的hashcode的话,那么index只会和e.hash的低位做&操作.这样一来,index的值就只有低位参与运算,高位毫无存在感,从而会带来哈希冲突的风险.所以在计算key的哈希值的时候,用其自身hashCode值与其低16位做异或操作.这也就让高位参与到index的计算中来了,即降低了哈希冲突的风险又不会带来太大的性能问题.
PUT方法分析
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;
// tab为空则调用resize()初始化创建
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 计算index,并对null做处理
if ((p = tab[i = (n - 1) & hash]) == null)// 无哈希冲突,创建新的Node
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
// 节点key存在,直接覆盖value,保证key的唯一性
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 { //是链表
// index 相同的情况下
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null); //如果e后的Node为空,将value赋予下一个Node
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);//链表长度达到8,改变链表结构为红黑树
break;
}
// key相同则跳出循环
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
//就是移动指针方便继续取 p.next
p = e;
}
}
if (e != null) {
V oldValue = e.value;
//根据规则选择是否覆盖value
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
// 扩容检测
if (++size > threshold)
// size大于加载因子,扩容
resize();
afterNodeInsertion(evict);
return null;
}
Remove方法分析
//两种方法实际都是执行removeNode()方法
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
public boolean remove(Object key, Object value) {
return removeNode(hash(key), key, value, true, true) != null;
}
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
else if ((e = p.next) != null) {//节点类型判断
if (p instanceof TreeNode)
// 红黑树节点
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {
// 链表节点
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
p = e;
} while ((e = e.next) != null);
}
}
// 获取到node后,分情形删除节点
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode)
//这里我们只需要知道我们只是删除对应的红黑树节点即可
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
else if (node == p)
tab[index] = node.next;
else
p.next = node.next;
++modCount;
--size;
afterNodeRemoval(node);
return node;
}
}
return null;
}