针对HashMap源码阅读做一个记录,关于HashMap的结构图网上有很多,就懒得画了,想看的时候直接网上搜索。
hashmap类图
hashmap的部分公共方法
hashMap源码的可读性相比其他框架代码来说不是很好,读起来有一些不舒服,本文没有对红黑树进行讲解,因为具体对红黑树也不是很了解,只知道其大体结构不知道怎么去实现,等有时间需要补补红黑树的实现。
属性
//默认大小
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;
//链表大小这个变成红黑树
static final int TREEIFY_THRESHOLD = 8;
//红黑树小于这个变成链表
static final int UNTREEIFY_THRESHOLD = 6;
static final int MIN_TREEIFY_CAPACITY = 64;
//存储Node的数组
transient Node[] table;
//一种存储map 中key value的数据结构,执行entrySet会返回这个
transient Set> entrySet;
transient int size;
//存储结点数操作
transient int modCount;
//当size大于threshold执行扩容
int threshold;
final float loadFactor;
基本数据类型
HashMap中用于存储key-value的数据结构是静态内部类Node,下面是Node代码,比较简单
static class Node implements Map.Entry {
//hash
final int hash;
//key
final K key;
//value
V value;
Node next;
Node(int hash, K key, V value, Node 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;
}
}
因为HashMap新增了红黑树来进行存储,下面是红黑树的基本属性(方法已经省略),这里不对红黑树进行探索(因为不会),//todo 等后面会了在进行补充
static final class TreeNode extends LinkedHashMap.Entry {
TreeNode parent; // red-black tree links
TreeNode left;
TreeNode right;
TreeNode prev; // needed to unlink next upon deletion
boolean red;
TreeNode(int hash, K key, V val, Node next) {
super(hash, key, val, next);
}
}
构造函数
//返回输入数最近的2的次幂,比如 输入30返回32,返回的数>=输入的数
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, 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);
}
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);
}
//这里相当于向HashMap中存储Map,
final void putMapEntries(Map extends K, ? extends V> m, boolean evict) {
int s = m.size();
if (s > 0) {
if (table == null) { // pre-size
//计算扩容size,+1.0是因为计算是float,转化为int会自动丢失小数点后数字
float ft = ((float)s / loadFactor) + 1.0F;
//大小为t
int t = ((ft < (float)MAXIMUM_CAPACITY) ?
(int)ft : MAXIMUM_CAPACITY);
//设置最大大小threshold
if (t > threshold)
threshold = tableSizeFor(t);
}
//因为这里不止初始化的时候使用,还有可能出现putAll中也会调用这个方法,当传入数据的map值大于当前最大threshold直接扩容后添加,关于当前数组容量+s的值大于threshold的问题这里没处理,是因为putVal的时候也会扩容
else if (s > threshold)
//扩容,后面讲
resize();
for (Map.Entry extends K, ? extends V> e : m.entrySet()) {
K key = e.getKey();
V value = e.getValue();
//添加值,后面讲解
putVal(hash(key), key, value, false, evict);
}
}
}
tableSizeFor中算法解析
获取离该数最近的2的幂次方数有规律,例如
110, 11 , 1011, 1000这四个数,距离他们最大的2的幂次方数分别为
1000,100,10000, 1000这四个数,即在前面补1,其余位全部置0,本身就是2的幂次方数除外。
换而言之我们只需要得出111, 11, 1111, 111这四个数,然后将他们+1即可。
而如何得到最高位只需要将原来的数-1,然后依次向右移动 与原本自身‘|’就可以了,因为只需要最高位的数字,只要最高位为1,向右移动然后相或后面所有位置都会为1,结果最高位后必然全是1,最终结果+1即为所得答案。
put
static final int hash(Object key) {
int h;
//key等于null ,hash为0, 不为null取hash值和自身的hash向右移动16为进行异或
//>>>表示无符号右移,高位补0,^表示异或,同值取0,异值取1,所以h的高16位不变,至于为什么这样用是因为这样可以让key更加均匀的分布,在后面代码中会进行分析。
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
/**
* Implements Map.put and related methods
*
* 这里是核心代码
* @param hash key的hash
* @param key the key
* @param value the value to put
* @param onlyIfAbsent 如果存在就不更改值
* @param evict 这个参数留给子类扩展的,参考LinkedHashMap
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//tab为table[], n为table[]长度,i表示key对应的table下标索引,p表示对应下标的第一个数据
Node[] tab; Node p; int n, i;
//当map中没有数据执行这里
if ((tab = table) == null || (n = tab.length) == 0)
//返回新存储数据数组的大小
n = (tab = resize()).length;
//(n - 1) & hash 数组长度和key的hash相或,用来获得数据应该存入数组的下标,这里就是为什么hashMap中的size大小为2的幂的原因。
if ((p = tab[i = (n - 1) & hash]) == null)
//当该下标上没有值时,直接赋值新结点
tab[i] = newNode(hash, key, value, null);
//当下标上已经有值时,需要进行特殊处理.
else {
Node e; K k;
//这里判断该数组下标的第一个数据的key是不是等于当前要插入的key,判断顺序=》先判断hash,然后key == p.key 和 key.equals(k)任意满足一个即可(重点),任意满足一个即可!! 一定要满足的条件是hash相同!!
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//这里可以理解为当结点是TreeNode时,向该树添加值
else if (p instanceof TreeNode)
e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
else {
//开启循环,如果binCount >= 7将会转为红黑树 binCount为table中对应Node的个数
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
//将结点添加到最后一个
p.next = newNode(hash, key, value, null);
//转为红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//结点中找到了对应相等的key,停止循环
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//e不为null需要处理,即添加的数据已经存在,需要对其进行value覆盖
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
//将value赋值为新值
e.value = value;
//hashMap没实现
afterNodeAccess(e);
//返回oldValue
return oldValue;
}
}
//结点数操作++
++modCount;
//当添加后的size>threshold ,例如默认没没设置初始值,第一次扩容是size = 13
if (++size > threshold)
resize();
//hashMap没实现
afterNodeInsertion(evict);
//因为没有oldValue,返回null
return null;
}
一些个人见解
- key进行hash时,如果为null,hash为0,否则hash为key.hashCode() ^ (key.hashCode() >>> 16)值,使用>>>16的原因是要充分的利用高16位和低16位来得到hash,让key分布更均匀,因为key的落点分布是和map的size-1(size是2的倍数)相与,而size一般不会达到1<<16,所以直接用hash相与基本用不到高16位,在hash处理了过后可以充分利用高16位和低16位。(个人见解)
扩容
final Node[] resize() {
Node[] 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;
}
//新Map的size设置为old * 2, 如果size没有达到16则在if(newThr == 0)为newThr里面赋值
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
//map中的数据为null, 但是设置了threshold值,就会将map的大小设置为threshold,初始化中如果设置了threshold则其存储的是map最开始应有的size
newCap = oldThr;
else { // zero initial threshold signifies using defaults
//如果初始化没设置threshold,或者扩容前的size没有16,扩容后大小为16
newCap = DEFAULT_INITIAL_CAPACITY;
//设置下一次需要扩容的size, 12
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
//当map中没有数据,或者扩容后size < 16
if (newThr == 0) {
//设置下一次需要扩容的size
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node[] newTab = (Node[])new Node[newCap];
table = newTab;
//扩容开始
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node e;
if ((e = oldTab[j]) != null) {
//置null, 方便gc回收
oldTab[j] = null;
//当该位置下只有一个结点,直接写入新数组
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
//树结点移植到新的table中,如果移植后的数结点小于等于6则将转化为链表
else if (e instanceof TreeNode)
((TreeNode)e).split(this, newTab, j, oldCap);
//链表转移
else { // preserve order
Node loHead = null, loTail = null;
Node hiHead = null, hiTail = null;
Node next;
//循环,当下一个结点不为null
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);
//赋值
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
- 通过扩容可以看到Map中存储Node用的数组的大小就是当前Map的容量,Node数组一定有下标的值为null
链表赋值算法讲解,因为map扩容是table.length<<1, table.length的值是2的幂次,确定node存储下标index是hash&oldTable.length - 1来进行确定的;
当oldTable的length为16时候,即下标的确定是 1111 & hash ,根据这个可以得出下标index为1的hash可能为:0001、1_0001、10_0001、11_0001、100_0001、101_0001、110_0001(加_是为了方便观看).......
在扩容后的length等于32,length-1 = 1_1111,此时下标index为1的hash为0001、10_0001、11_0001、100_0001、101_0001、110_0001....相比上面只少了1_0001,明显可以看出1_0001和上面其他数相比是因为'1'_0001,所以通过oldCap&e.hash == 0可以过滤掉类似'1'_0001这种数据,1_0001的新下标为1_0001,增加了1_0000。所以原下标的数组会分成两个链表,一个链表所在索引不动,另一个链表所在索引+oldTable的length。
通过oldCap&e.hash == 0将原本的链表拆成两根链表,然后分别赋值即可。
根据这个例子大致可以明白该思想。
注:map扩容采用的尾插法
remove
public V remove(Object key) {
Node e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
/**
* Implements Map.remove and related methods
*
* @param hash hash for key
* @param key the key
* @param value the value to match if matchValue, else ignored
* @param matchValue if true only remove if value is equal
* @param movable if false do not move other nodes while removing
* @return the node, or null if none
*/
final Node removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node[] tab; Node p; int n, index;
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node 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)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)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;
}
remove
public V remove(Object key) {
Node e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
/**
* Implements Map.remove and related methods
*
* @param hash hash for key
* @param key the key
* @param value the value to match if matchValue, else ignored
* @param matchValue if true only remove if value is equal
* @param movable if false do not move other nodes while removing
* @return the node, or null if none
*/
final Node removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node[] tab; Node p; int n, index;
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node node = null, e; K k; V v;
//第一个就是要查找的值
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
//链表或者红黑树上查找对应的key
else if ((e = p.next) != null) {
if (p instanceof TreeNode)
node = ((TreeNode)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)node).removeTreeNode(this, tab, movable);
else if (node == p)
//第一个结点就是查找到的结点
tab[index] = node.next;
else
//p是node的前一个结点
p.next = node.next;
++modCount;
--size;
afterNodeRemoval(node);
return node;
}
}
return null;
}
迭代器
在HashMap中有一些的迭代器,通过这三个迭代器可以对key的集合,value的集合,key,value的集合进行遍历
final class KeyIterator extends HashIterator
implements Iterator {
public final K next() { return nextNode().key; }
}
final class ValueIterator extends HashIterator
implements Iterator {
public final V next() { return nextNode().value; }
}
final class EntryIterator extends HashIterator
implements Iterator> {
public final Map.Entry next() { return nextNode(); }
}
他们的父类
abstract class HashIterator {
Node next; // next entry to return
Node current; // current entry
int expectedModCount; // for fast-fail
int index; // current slot
HashIterator() {
expectedModCount = modCount;
Node[] 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 nextNode() {
Node[] t;
Node 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;
}
public final void remove() {
Node p = current;
if (p == null)
throw new IllegalStateException();
//在这里不对应将会报错,因为迭代器遍历的时候会改变这两个数字,如果在使用迭代器的时候对容器进行了更改就会报错,所以迭代器使用期间不能进行增删处理.
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
current = null;
K key = p.key;
removeNode(hash(key), key, null, false, false);
expectedModCount = modCount;
}
}
针对HashMap从上面已经知道,只有结点的增删才会对modCount进行添加操作,当对原有结点进行修改时,不会进行modCount操作,所以在迭代器遍历时可以对结点进行修改操作,不能进行增删, 即迭代器遍历时不能进行size改变的操作。(List也同理)
//这段代码会报Exception in thread "main" java.util.ConcurrentModificationException错误,删除掉else可以正常执行
Map map = new HashMap<>();
map.put(1, 9);
map.put(2, 20);
map.forEach((key, value) -> {
if (key == 1) {
map.put(1, 10);
} else {
map.put(9, 22);
}
});
System.out.println(map.get(1));
System.out.println(map.size());
总结
HashMap中key的第一判断条件为hash(null为0, 否则hash = kek.hashCode ^ (key.hashCode >>> 16)), hash相同判断第二条件,equals或者==满足一个即认定为相同key,put内容key满足以上条件时,只会更新value,不会更新key。
HashMap定位key所在table数组下标为table的长度size - 1 & key.hash。
HashMap添加内容时,先确定该数据在table表的存储下标index,当下标index处没有结点直接将该下标赋值给新添加数据的这个结点,如果有数据就尾插法添加到结点后面,当链表数据长度大于等于8(binCount >= TREEIFY_THRESHOLD - 1,binCount从0开始,TREEIFY_THRESHOLD等于8)就将该条链表转化为红黑树。
HashMap扩容:当size > threshold进行扩容(注:size为HashMap存储的元素大小,length为table表大小),每次扩容实际上是将hashMap的table表length*2,table表的length必须为2的n(区间1-30)次方。table*2后将原来数组下标的链表(红黑树)分为两个链表(红黑树,注:当红黑树大小<=6就会将红黑树转化为链表),此时会重新计算两条链表对应的下标index,一条链表为hoIndex,一条为低loIndex,两个index的关系为hoIndex = loIndex + size/2;
hashMap中有很多的迭代器,HashIterator 是他们共同的父类,hashMap中有一个modCount对HashMap的每次结点操作进行了记录,HashIterator中也有一个变量expectedModCount,在初始化迭代器的时候会将modCount赋值给expectedModCount,在进行遍历期间会对两个值进行比较,如果不想等级会抛出异常,所以在迭代器遍历期间不能对map进行添加和删除,因为添加删除modCount会+1,但是可以进行修改。
HashMap中还存在其他的迭代器这里没有进行贴出,本次源码差不多就到这里了。