HashMap源码----JDK1.8
类注释:
-
基于hash表结构实现,hash表结构: 大小为16的数组内元素为一条链表
-
可以放入空值,还可以放入空键(之前还以为只能是空值),空键怎么得到hoshcode值?(在hash方法中进行了key为null的处理,返回值为0) 放到hash表的哪里?放到数组索引为0的地方 第一个桶
Hashtable这个是一个线程安全的HashMap,现在不常用或者不用,不允许空值,更多的使用JUC包下的CurrentHashMap,或者Collections.synchronized在每个方法上面加上锁。
-
不保证映射的顺序?
-
初始容量和负载因子(默认0.75),容量是哈希表中的桶数,初始容量就是哈希表创建时的容量。负载因子是衡量哈希表在其容量自动增加之前允许达到多满的指标。当哈希表中的条目数超过负载因子和当前容量的乘积时,重新哈希表(即重建内部数据结构),使哈希表具有大约两倍的桶数。
-
快速失败Fail-fast,一般是用于检测并发修改,迭代器可以快速失败。因为HashMap是非线程安全的,也就是说在多线程情况下,未来会不确定的发生失败。快速失败让你不用担心这个风险。
结构
-
继承关系
AbstractMap:此类提供Map接口的骨架实现,以最大限度地减少实现此接口所需的工作(人家的描述就是清晰)
Map接口,规定将键映射到值。
Cloneable,是一个空的接口,只有实现了该接口的类调用Object 的 clone 方法不会抛出异常CloneNotSupportedException
Serializable,空接口,标志此类可以序列化
成员变量
序列化用的,ArrayList,LinkedList也有,但是值不一样,treeMap值也不一样
serialVersionUID 是 Java 为每个序列化类产生的版本标识,可用来保证在反序列时,发送方发送的和接受方接收的是可兼容的对象
private static final long serialVersionUID = 362498820763181265L;
默认初始HashMap容量,必须为2的幂?为什么? 值为1<<4 二进制左移4位 16 也就是说元素个数大于12(16 * 0.75)时会扩容
目的:保证计算槽点位置tab[i = (n - 1) & hash] 的结果在 [0,Cap-1] 之间
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
最大容量,在两个带参数的构造函数隐式指定更高值时使用(?)。 必须是 2 的幂 <= 1<<30
static final int MAXIMUM_CAPACITY = 1 << 30;
默认负载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
JDk1.8在这里与JDK1.7有区别,就是当一条链表上的结点数>=8时会转为红黑树【此时不算当前插入的节点数】
static final int TREEIFY_THRESHOLD = 8;
//链表结点树,从0开始计数,到>=7 也就是说结点数为8个和8个之上就会转红黑树,有前提
当一个桶内元素<=6时,会由红黑树转为链表
static final int UNTREEIFY_THRESHOLD = 6;
只有桶的总数>=64时才会有转红黑树。【此时不算当前插入的节点数】
static final int MIN_TREEIFY_CAPACITY = 64;
存放数据的数组,长度必须为2的幂
transient Node<K,V>[] table;
元素个数,整个HashMap元素个数,具体哪个桶内元素个数是由binCount作为局部变量计数
transient int size;
用于fail-fast
transient int modCount;
阈值,如果表数组尚未分配,此字段保存初始数组容量,或零表示 DEFAULT_INITIAL_CAPACITY
- 在resize方法中用于初始化数组容量
- 之后就是阈值,容量*负载因子
int threshold;
哈希表的负载因子
final float loadFactor;
链表结点
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;//当前key进过HashCode之后的哈希值,用于决定数据放到哪个桶
final K key;//自定义类需要重写equals和hashcode方法,可以为null值
V value;//存放的数据,可null
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;
}
}
红黑树结点
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);
}
/**
* Returns root of tree containing this node.
*/
final TreeNode<K,V> root() {
for (TreeNode<K,V> r = this, p;;) {
if ((p = r.parent) == null)
return r;
r = p;
}
}
构造方法
//我最常用的,全部都是默认,默认大小16,负载因子0.75
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;//0.75f // all other fields defaulted
}
//指定大小
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//指定大小和负载因子
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的幂,扩容也是必须容量为2的幂
}
//返回给定目标容量的最近二次幂,记得Leekcode有道题是这个算法来着。
//比如cap=3 a+=b 等于 a=a+b a|=b 等于 a=a|b a等于a或b,二进制或运算
//对于位运算,可参考学习https://blog.csdn.net/javazejian/article/details/51181320
//System.out.println(-4>>1);-2 有符号右移
static final int tableSizeFor(int cap) {// cap=3
int n = cap - 1;//n = 2
n |= n >>> 1;// n = 10 | 01 11 3
n |= n >>> 2;// n = 11 | 00 11 3
n |= n >>> 4;// n = 11 | 00 11 3
n |= n >>> 8;// n = 11 | 00 11 3
n |= n >>> 16;// n = 11 | 00 11 3
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;// n=4
}
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
## 方法
### hash方法
根据key生成一个hashcode值来决定将数据放入哪个桶中
(h = key.hashCode()):说明自定义类必须实现hashcode方法,object中hashcode方法是空的。
将hashCode值再右移16位是为了让算出来的hash更分散,以减少哈希冲突
key为null时 返回hashcode值为0
得到hash值后怎么计算槽点,(length-1) & hash = hash % (length
//hash值换算桶下标
//假设key是Integer值,则hashcode返回hash值就是int值 当前只有一个值 get(4)
计算 (length-1) & hash = (1-1) & 4 = 0 桶下标为0
计算 hash%(length-1) 4 % 0 = 0 桶下标为0
//上面是一种错误的计算
只有一个值那么数组长度length也不会是1 无参构造出的对象length为16 指定则为最接近的2次幂
计算 15 & 4 01111 & 00100 = 4
计算 4 % 15 = 4
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//比如说String类中hashCode方法
public int hashCode() {
int h = hash;//默认hash值为0
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
从无参构造器开始
//当我们调用HashMap map = new HashMap()时候并未完成初始化,当第一次put时才进行的初始化
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;//0.75f // all other fields defaulted
}
//1.无参构造出的对象第一次put值时执行初始化操作 resize方法帮助我们进行第一次初始化
ode<K,V>[] oldTab = table; oldTab = null
int oldCap = 0;
int oldThr = 0;
newCap = DEFAULT_INITIAL_CAPACITY; = 初始化为默认容量16
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); = 12 0.75 * 16
threshold = 12 阈值超过则扩容
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; 初始化
table = newTab; 给到类属性
返回大小为16的数组
常用方法
get方法
- hashmap中有值为null则返回null
- 如果通过k找不到元素则返回null
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
//hash值传进来是为了找到是哪个桶,key是为了找到桶内的具体位置
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
//1.判断table不为null,且长度大于0。2.经过hash索引换算出来的下标对应的桶不为null
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
//当到了这里 first已经取出了桶中的第一个结点,判断下要取的是不是就是这个
//1.first的key的hash值和当前hash值相等
//2.first结点的key和当前key相等(可以取出值为null) 或者 值不为null时使用equals进行判断
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first; //如果上面的条件都符合则直接返回first 没有hash冲突就只执行到这里了相当于数组取值
if ((e = first.next) != null) {//桶内第一个值不为null,且桶内还有其它结点
if (first instanceof TreeNode)//如果是红黑树结点
return ((TreeNode<K,V>)first).getTreeNode(hash, key);//逻辑复杂了再封装个方法
do {
//如果是链表则一个一个找,找到则返回结点,找不到返回null
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
//找不到返回
return null;
}
put方法
//放入当前键值对,如果之前有和key关联的值则替换,并返回之前值,如果没有返回null
public V put(K key, V value) {
// int hash, K key, V value, boolean onlyIfAbsent,boolean evict
// onlyIfAbsent=false,默认会执行覆盖操作,会更改现有值,如果旧值是null则必然会替换
return putVal(hash(key), key, value, false, true);
}
//简略:resize()方法是初始化或者加倍表大小
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;//p就是一个指针 用来遍历链表
if ((tab = table) == null || (n = tab.length) == 0) //表示是第一次put值
n = (tab = resize()).length; //进行初始化,如果是无参构造new出的对象,则返回默认大小为16的数组
//无hash冲突,相当于数组放值
if ((p = tab[i = (n - 1) & hash]) == null) //如果经过hash计算出的下标对应的桶为null,则直接加入进去
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;//e就是一个临时存放指针用来存储p.next
//如果桶内第一个结点和当前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 {
//hash冲突了,刚开始往链表添加元素
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {//如果进入此if则说明e为null(也就是说是链表的末尾)
//放到最后一个结点后面,尾插
p.next = newNode(hash, key, value, null);
//链表结点数>=8时转为红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//如果遍历中途有和新增结点一样的则break
//这个时候不执行p=e 则不会往后移动,每次for进来都会进入这个if条件然后break
//这样可以保证e就是我们想要修改的结点,且binCount统计的也是当前链表结点数
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key 不等于null则说明当前key之前就有了
V oldValue = e.value;//用于返回当前key对应映射的旧value
if (!onlyIfAbsent || oldValue == null)//put()调用 onlyIfAbsent为false 则默认会替换
e.value = value;
afterNodeAccess(e);//对于hashmap是个空方法,用于linkedHashMap的LRU策略,最近使用的Node,放在链表的最末尾
//LRU 最近最少使用
return oldValue;
}
}
++modCount;//记录hashmap的修改
if (++size > threshold) //如果增加当前元素后总元素个数大于阈值,也就是说默认16时大于12 就是13就会扩容
resize();
afterNodeInsertion(evict);//在节点插入之后做些什么,HashMap此方法为空
return null;
}
resize方法,初始化&扩容方法
//1.无参构造出的对象第一次put值时执行初始化操作
Node<K,V>[] oldTab = table; oldTab = null
int oldCap = 0;
int oldThr = 0;
newCap = DEFAULT_INITIAL_CAPACITY; = 初始化为默认容量16
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); = 12 0.75 * 16
threshold = 12 阈值超过则扩容
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; 初始化
table = newTab; 给到类属性
返回大小为16的数组
//2.假设现在要扩容,指定容量4,现在加第4个
Node<K,V>[] oldTab = table;//现在oldTab应该有4个值了
int oldCap = (oldTab == null) ? 0 : oldTab.length;//4
int oldThr = threshold;//3
newCap = oldCap << 1 //容量扩大2倍
float ft = (float)newCap * loadFactor;//6
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE); //6 保证不会超出最大值
threshold = newThr;//重新设置阈值
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;//要返回table
//接下来就是怎么扩容了 新的数组和旧的数组,数据重新散列
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) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = 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) {
for (int j = 0; j < oldCap; ++j) {//遍历全部桶
Node<K,V> e;
if ((e = oldTab[j]) != null) {//判断旧桶中有值则重新散列到新桶
oldTab[j] = null;//释放内存,GC回收
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;//桶下标改变
//扩容前的下标计算是 hash & (len-1) || hash % length
//扩容后 hash & (newLen - 1) 就是 hash & (2*len-1)
//这种情况下,举例
//旧容量为4 扩容后为8
//之前的put(1,1) 和 put(5,1)是都在下标为1的位置
//现在重新计算下标就是put(1,1)还是下标1桶,但put(5,1)就成了下标5桶
//这个下标5 = 旧下标1 + 旧容量4
//就是newTab[j + oldCap] = hiHead;
Node<K,V> next;
do {
next = e.next;
//put(1,1) 001 & 100 == 0 是旧下标不需要改变下标
//put(4,1) 100 & 100 != 0 是需要改变桶的元素
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);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
treeifyBin方法,链表转红黑树&向树添加元素
链表一般不会轻易转化成红黑树,看源码是有一段描述。默认8是通过泊松分布算出来的,有8个元素的概率是0.00000006
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)//如果hashmap中数据数量不够64只会扩容
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);
}
}
remove方法
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
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;
//和get 方法一样的开头
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);
}
}
//要删了
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;
}
遍历hashMap
有可以遍历key的keySet(),有values.还有entrySet()返回键值对
public Set<K> keySet() {
Set<K> ks = keySet;
if (ks == null) {
ks = new KeySet();
keySet = ks;
}
return ks;
}
public Collection<V> values() {
Collection<V> vs = values;
if (vs == null) {
vs = new Values();
values = vs;
}
return vs;
}
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}
final class KeySet extends AbstractSet<K>
final class Values extends AbstractCollection<V>
final class EntrySet extends AbstractSet<Map.Entry<K,V>>
final class KeyIterator extends HashIterator
implements Iterator<K> {
public final K next() { return nextNode().key; }
}
final class ValueIterator extends HashIterator
implements Iterator<V> {
public final V next() { return nextNode().value; }
}
final class EntryIterator extends HashIterator
implements Iterator<Map.Entry<K,V>> {
public final Map.Entry<K,V> next() { return nextNode(); }
}
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 slot
HashIterator() {
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);
}
}
public final boolean hasNext() {
return next != 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 iterator = map.keySet().iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
//1.返回一个ks
public Set<K> keySet() {
Set<K> ks = keySet; //多态的体现
if (ks == null) {
ks = new KeySet();
keySet = ks;
}
return ks;
}
//2.调用这个内部类的方法
public final Iterator<K> iterator() { return new KeyIterator(); }
//3.继承了HashIterator 当前方法只有着遍历当前map的key的next()方法
final class KeyIterator extends HashIterator
implements Iterator<K> {
public final K next() { return nextNode().key; }//nextNode()方法
}
//4.走了这么多,我们都没接触到元素在哪里 我们哪什么去遍历
//元素在父类的构造方法中被初始化
HashIterator() {
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);//找到第一个不为null的桶
}
}
//遍历在这里,获取当前node的下一个结点
final Node<K,V> nextNode() {
Node<K,V>[] t;
Node<K,V> e = next;//第一次调用该方法则从第一个不为null的桶开始
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
//每次调用该方法都会进行判断
//(next = (current = e).next) 还在移动结点,保证每次返回的e都是下一个结点
//如果下一个结点为null&&table!=null,顺带又重新初始化了t
if ((next = (current = e).next) == null && (t = table) != null) {
//换桶,找个不为null的桶.和构造器中一样但是index值会改变这就可以找到之后不为null的桶
do {} while (index < t.length && (next = t[index++]) == null);
}
return e;
}
之前的描述有问题,不是总的元素个数>64 而是桶的总树>64
static final int MIN_TREEIFY_CAPACITY = 64;
修改下,hashmap 链表转红黑树,是第9个元素的时候转。也就是不算当前节点,此刻的结点数>=8,源码的bincount = 0, bincount >= 7