类的描述
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
基于哈希表的 Map接口的实现。这种实现提供了所有可选的Map操作,并允许 null值和 null关键。(这 HashMap类大致相当于 Hashtable,除了它是不同步的,允许空值。)这类做任何保证Map的秩序;特别是,它并不能保证订单将随时间保持恒定。
这个实现的基本操作提供了稳定的性能(get和put),假设哈希函数的分散元素之间的正确的桶。在集合视图迭代需要时间成正比的“能力”的HashMap实例(桶的数量)加上其尺寸(键值对映射的数量)。因此,它是非常重要的,不设置的初始容量太高(或负载因子太低),如果迭代性能是重要的。
对HashMap实例有影响其性能的两个参数:初始容量和负载因子。是的容量哈希表中的存储桶的数目,和初始容量仅仅是当时的哈希表的创建能力。的负载因子是如何全面衡量哈希表是可以在其容量自动增加。当哈希表中的条目数超过负荷的因素和当前容量的乘积,哈希表进行扩容(即内部数据结构重建),哈希表有大约两倍的存储桶的数目。
作为一个一般规则,默认负载因子(。75)提供了一个很好的时间和空间成本之间的权衡。较高的值会降低空间开销,但提高查找成本(体现在大多数的HashMap类的操作,包括get和put)。预计参赛人数在Map及其负载系数应考虑设置其初始容量时,以尽量减少重复操作的次数。如果初始容量大于最大条目数除以负载因子,没有rehash操作将不会发生。
如果许多映射将被存储在一个HashMap实例,创建了一个足够大的容量将允许映射可以存储比让它执行自动改写为增长所需要的表格更有效。注意使用多键同hashCode()
是一个肯定的方式来减缓任何哈希表的性能。改善的影响,Comparable键时,这类可以帮助打破关系键比较的顺序使用。
请注意,此实现不同步。如果多个线程访问一个哈希映射的同时,并至少有一个线程修改Map的结构,它必须同步外部。(结构上的修改是任何操作,添加或删除一个或多个映射;仅仅改变一个关键实例已经包含了不是一个结构上的修改。相关的价值)这通常是由一些对象同步自然封装图完成。如果该对象不存在,Map应该是“包裹”使用Collections.synchronizedMap方法。最好的做法是在创建时,防止意外的非同步访问的Map:
MapM =集合。synchronizedmap(新HashMap(…));
所有这一类的“集合视图的方法”快速失败返回的迭代器:如果Map的结构修改,迭代器创建后的任何时间,以任何方式除了通过迭代器的remove方法,迭代器将抛出一个ConcurrentModificationException。因此,在并发修改的面前,迭代器失败迅速和干净,而不是冒着任意的,非确定性的行为在未来的一个不确定的时间。
注意迭代器不能快速失败行为得到保证的话,一般来说,不可能在不同步的并发修改的存在作出难以保证。快速失败迭代器扔ConcurrentModificationException尽最大努力的基础上。因此,要写一个程序,依靠这一例外的正确性错误:快速失败迭代器的行为只能用来检测错误。
hashMap哈希表的存储结构
数组+链表+红黑树的数据结构
常量、变量、内部类
//默认初始map初始化容量, 16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认扩展因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//链表变为红黑树的阈值
//当当前链表长度>8 链表转转红黑树
static final int TREEIFY_THRESHOLD = 8;
//红黑树变为链表的阈值
//当红黑树节点数<6 时 红黑树转为链表
static final int UNTREEIFY_THRESHOLD = 6;
/**
* 最小树形化容量阈值:即 当哈希表中的容量 > 该值时,才允许树形化链表 (即 将链表 转换成红黑
* 树)否则,若桶内元素太多时,则直接扩容,而不是树形化 为了避免进行扩容、树形化选择的冲突,
* 这个值不能小于 4 * TREEIFY_THRESHOLD
*/
static final int 、、、、、、、
= 64;
//节点
static class Node<K,V> implements Map.Entry<K,V> {
final int hash; //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;
}
}
//上面结构图中的数组
transient Node<K,V>[] table;
/**
* 保存缓存的entrySet()。请注意,AbstractMap字段用于keySet()和values()。
*/
transient Set<Map.Entry<K,V>> entrySet;
/**
* 节点的数量
*/
transient int size;
//hashMap被修改的次数
transient int modCount;
// 要调整大小的下一个大小值(容量*负载系数)。
int threshold;
//扩展因子
final float loadFactor;
构造函数
// 指定初始化容量,和扩容因子
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
//得到大于等于他的第一个2的n次幂
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
//指定容量, 扩展因子为默认扩展银因子
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//未指定初始容量, 扩展因子时默认
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false); //在put里面讲
}
常用方法
put
//这里是使用key的hash值的高16位和自己的低16位进行里与操作, 为了更大程度上增加离散度
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
在put的时候会计算key的hash值
//讲当前高16位和低16位进行与运算, 其实这是为了key位置的离散度
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
/**
* hash: key的hash值
* key: node的key值
* value: value的value值
* onlyIfAbsent: 如果为true不修改旧值
* evict: 如果为false,则表处于创建模式(这个不是很明白,往下看)
*/
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是空 || 或者表的长度为 0, 进行扩容resize(), 在下面讲
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//用当前key的hash与map中table的length-1进行与运算,得到key在table中的位置,如果当前位置是 null, 则当前数组位置的插入当前node
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
}
。。。
}
因为length一直是2的n次幂,length-1, 的二进制的每个位置都是1,其他位置都是0, 这样有两个好处
1. 因为1前面的位置都是0 ,与运算 都是1时才返回1, 所以与运算后得到值不会大于length -1
2. 每个位置都1进行与运算时。可以更大的保证数据的离散度
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
。。。
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
//让如果当前数组的key等于新元素的key, 直接讲p赋值e
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//如果p是一个树,则
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
。。。
}
操作红黑树
final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
int h, K k, V v) {
Class<?> kc = null;
boolean searched = false;
TreeNode<K,V> root = (parent != null) ? root() : this;
for (TreeNode<K,V> p = root;;) {
int dir, ph; K pk;
if ((ph = p.hash) > h) //当前节点的左节点
dir = -1;
else if (ph < h) //当前节点的右节点
dir = 1;
//如果当前接的key = key, 且key != null 返回当前节点
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
return p;
else if ((kc == null && //当前kc == null. 且 (当前元素没有比较方法或者 当前的class和 p节点key的class 不是同一个)
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0) {
if (!searched) {
TreeNode<K,V> q, ch;
searched = true;
//遍历树获取与等于当前key的节点
if (((ch = p.left) != null &&
(q = ch.find(h, k, kc)) != null) ||
((ch = p.right) != null &&
(q = ch.find(h, k, kc)) != null))
return q;
}
dir = tieBreakOrder(k, pk);
}
TreeNode<K,V> xp = p;
//如果我们遍历到了nul节点
if ((p = (dir <= 0) ? p.left : p.right) == null) {
//根据计算,讲put的值插入到当前节点的 left或者right
Node<K,V> xpn = xp.next;
TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
if (dir <= 0)
xp.left = x;
else
xp.right = x;
xp.next = x;
x.parent = x.prev = xp;
if (xpn != null)
((TreeNode<K,V>)xpn).prev = x;
moveRootToFront(tab, balanceInsertion(root, x)); //红黑树的操作
return null;
}
}
}
//当哈希代码相等且不可比较时,用于排序插入的断开连接实用程序。我们不需要一个总订单,只需
//要一个一致的插入规则来保持再平衡的等价性。断开领带比必要的更进一步简化了测试。
static int tieBreakOrder(Object a, Object b) {
int d;
if (a == null || b == null ||
(d = a.getClass().getName().
compareTo(b.getClass().getName())) == 0)
d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
-1 : 1);
return d;
}
操作链表
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
.....
else {
//遍历链表
for (int binCount = 0; ; ++binCount) {
//如果链表p.next == null 说明链表中没有key与put的key相等,说明是新插入
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // 当前链表长度 <= 8 链表转树
treeifyBin(tab, hash);
break;
}
//如果匹配到了,跳出循环
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
...
}
最终返回值:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
。。。。
if (e != null) { // 在map中匹配到了节点
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null) //onlyIfAbsent为fasle 或者 oldVlue 为 //null 将节点的value替换为新的value
e.value = value;
afterNodeAccess(e);//这个是提供给HashMap子类使用的, 如LinkedHashMap
return oldValue; //返回oldValue
}
}
++modCount;
//新插入元素,所以如果当前size > 容量 * 扩展因子, 进行扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict); //这个是提供给HashMap子类使用的, 如LinkedHashMap
return null;
}
上面大致可以这样理解
1. 如果当前hashMap数组。table是null 或者table.length ==0 进行扩容
2. 用key的hash与 length-1进行与运算,计算出key在数组中的位置
2.1 如果数组当前位置的没有节点, 直接将新节点放到该位置
2. 2 如果数组当前位置的有节点, 且当前节点的key与put的key相同,替换当前位置的value, 返回旧value(onlyIfAbsent || oldValue == null)
2.3 如果当前节点是红黑树,通过树的查找算法,找到put的 key匹配的节点,
2.3.1 匹配成功 替换当前位置的value, 返回旧value(onlyIfAbsent || oldValue == null)
2.3.2 匹配失败,插入树,然后进行红黑树自己的树的操作,
2.4 当前节点是链表, 循环链表, 找到put的 key匹配的节点,
2.4.1 匹配成功 替换当前位置的value, 返回旧value(onlyIfAbsent || oldValue == null)
2.4.2 匹配失败,插入链表,如果当前链表的长度>=8 ,链表转红黑树
3. 新插入值,size +1 如果新的size > 容量 * 扩展因子扩容, 然后返回null
扩容
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) { //oldTab.length > 0
if (oldCap >= MAXIMUM_CAPACITY) {//oldTab.length > MAXIMUM_CAPACITY
threshold = Integer.MAX_VALUE; .//扩容容量的阈值变为Integer.MAX_VALUE
return oldTab; //不扩容
}
//如果当前长度的2倍 < MAXIMUM_CAPACITY 且 oldCap >= DEFAULT_INITIAL_CAPACITY
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; //旧的扩容容量阈值 扩展2倍
}
else if (oldThr > 0) // 旧的扩容容量阈值 > 0
newCap = oldThr; // newCap = 旧的扩容容量阈值
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY; //新容量为 DEFAULT_INITIAL_CAPACITY
//扩容容量阈值变为 DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; //新建数组
table = newTab;
if (oldTab != null) {
//遍历table
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null) //如果当前位置只有一个节点, 计算新的位置并赋值
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode) //如果树,最后他
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order //如果是链表,遍历
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
//根据与运算,将链表分为两个链表
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
//然后将loTail放到原位置
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
//将hitail放到j+oldCap的位置,讲完树我们一起讲这个
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
红黑树节点的扩容操作
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
TreeNode<K,V> b = this;
// Relink into lo and hi lists, preserving order
TreeNode<K,V> loHead = null, loTail = null;
TreeNode<K,V> hiHead = null, hiTail = null;
int lc = 0, hc = 0;
//遍历树, 讲树根据(e.hash & bit) == 0 分为两个Tree
for (TreeNode<K,V> e = b, next; e != null; e = next) {
next = (TreeNode<K,V>)e.next;
e.next = null;
if ((e.hash & bit) == 0) {
if ((e.prev = loTail) == null)
loHead = e;
else
loTail.next = e;
loTail = e;
++lc;
}
else {
if ((e.prev = hiTail) == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
++hc;
}
}
//如果将lo树放到原位置,
if (loHead != null) {
//如果lo树的节点数 <= 6, 树变链表
if (lc <= UNTREEIFY_THRESHOLD)
tab[index] = loHead.untreeify(map);
else {
tab[index] = loHead;
if (hiHead != null) // (else is already treeified)
loHead.treeify(tab);
}
}
//如果将hi树放到原位置 + oldCab的位置,
if (hiHead != null) {
/如果hi树的节点数 <= 6, 树变链表
if (hc <= UNTREEIFY_THRESHOLD)
tab[index + bit] = hiHead.untreeify(map);
else {
tab[index + bit] = hiHead;
if (loHead != null)
hiHead.treeify(tab);
}
}
}
上面关于树和链表的操作中有很大的相同处,都是原本的分为两个,一个放到原位置上,一个放到了 原位置+oldCab上,为什么这样呢,请看下图
当容量扩容二倍的时候
e.hash & (newCap - 1) 计算就是标红多了标红位置的与运算,所以新节点的位置,要不是 当前位置,要不就是当前位置 + oldCap
上述中的图片是我copy其他大牛的, 请看图片水印,相关文档