首先认识一下HaspMap到底长啥样?key-value组成一个Entry对象放到数组中
0、HashMap中的字段
//默认加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
//当前 HashMap 所能容纳键值对数量的最大值,超过这个值,则需扩容
int threshold;
//node数组
transient Node<K,V>[] table;
final float loadFactor;
1、无参构造方法
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
2、有参构造方法
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
3、有参构造方法调用this
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);
}
4、根据容量返回一个值 当达到这个值的时候就需要扩容了
// 计算结果 例如4->4 5->8 6->8 7->8 9->16 15->16
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;
}
5、put方法 存元素
public V put(K key, V value) {
//根据键key计算出一个hash
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//刚开始tab是等于null的,构造函数的时候是不初始化这个node数组的
//第一次使用才初始化
Node<K,V>[] tab; Node<K,V> p; int n, i;
//transient Node<K,V>[] table; 一开始为null的
if ((tab = table) == null || (n = tab.length) == 0)
//第一次使用的时候resize扩容个之后会得
//到一个初始化(例如16大小的Node数组)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
//(n - 1) & hash 拿当前数组最大角标 & 用键算出来的
//hash值随机得到一个Node[]角标的位置 并存储元素
//注意:这里的new Node有两个实现,HashMap自身实现
//和LinkedHashMap集成HashMap实现
tab[i] = newNode(hash, key, value, null);
else {
//当产生hash碰撞的时候
Node<K,V> e; K k;
//取出角标位置处的Node节点的hash值 如果hash值一样的话
//如果键的hash值一样并且key的地址值不一样 那么就比较equals方法
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
//如果hash相同 并且他们的值也相同那么就直接替换旧的元素
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
//产生hash碰撞后 如果hashCode相同但是eques不同 元素还是要添加啊
// 那么找到当前位置的node节点的下一个节点
//如果为空的话那么直接将新的节点放在这个节点的后面形成链表
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//e = p.next遍历这个节点 遍历一次 binCount会加1
//那么当binCount 超过8的时候会进行二叉树化
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
//链表转平衡二叉树
treeifyBin(tab, hash);
break;
}
//再次判断键冲突后 遍历Node数组同一位置下 链表中的元素的
//hash相同 比较equals是不是相同 如果相同,说明一样这个位置存过了
//就不存了
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
//遍历的关键 把下一个节点赋给p
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//判断有没有超过阈值 超过的话 那么就需要扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
6、扩容
final Node<K,V>[] resize() {
//旧的node数组
//1、第一次执行put想HashMap中存元素那么table为null
Node<K,V>[] oldTab = table;
//旧的node数组的大小
//1、第一次执行那么oldCap =0
int oldCap = (oldTab == null) ? 0 : oldTab.length;
//1、无参构造函数进来 int oldThr = threshold=0
//2、有参构造函数 例如 new HashMap<>(15);那么计算的
//threshold=16
int oldThr = threshold;
//定义新的node数组大小和阈值
int newCap, newThr = 0;
//oldCap >0 Node数组已经初始化了 里面有值
if (oldCap > 0) {
//如果数组容量大于最大容量
if (oldCap >= MAXIMUM_CAPACITY) {
//那么扩容取int的最大值
threshold = Integer.MAX_VALUE;
return oldTab;
}
//如果没有达到最大容量那么 新的容量newCap 为原来的2倍
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
//那么新的阈值也为原来的2倍
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
//有参构造方法的时候 int oldThr = threshold
//那么newCap =threshold 例如16
newCap = oldThr;
else {
//1、无参构造函数创建的HashMap 那么newCap=16
newCap = DEFAULT_INITIAL_CAPACITY;
//1、无参构造函数创建的HashMap 计算阈值 0.75f*16
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
//1、无参构造方法 newCap = DEFAULT_INITIAL_CAPACITY;
//2、有参的话那么newThr =0
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
//1、无参构造函数 threshold =0.75f*16
//2、有参的话上面已经重新计算过了
//3、扩容为原来的2倍
threshold = newThr;
//正真初始化node数组的地方
@SuppressWarnings({"rawtypes","unchecked"})
//1、无参构造函数进来创建默认大小为16的Node[]
//2、扩容为原来的2倍
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
//遍历老的Node数组
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
//扩容并不一定Node数组上面每一个位置都有值
//只不过元素超过阈值的时候需要扩容,找到老数组数组上面
//不为空的元素
if ((e = oldTab[j]) != null) {
//将老Node数组j位置处置空
oldTab[j] = null;
if (e.next == null)
//老的Node数组的j位置的元素不是链表的话
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;
//对链表进行分组
//如果值为0,将 loHead 和 loTail 指向这个节点。
//如果后面还有节点 hash & oldCap 为0的话,则将节点链入
//loHead 指向的链表中,并将 loTail 指向该节点。
//如果值为非0的话,则让 hiHead 和 hiTail 指向该节点。
//完成遍历后,可能会得到两条链表,此时就完成了链表分组:
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;
}
7、链表转二叉树
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
// 桶数组容量小于 MIN_TREEIFY_CAPACITY=64,优先进行扩容而不是树化
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
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);
}
}
LinkedHashMap底层实现
LinkedHashMap遍历的时候是默认按照插入顺序遍历的
HashMap的链表直接是没有联系的就是挂在Node数组中,但是LinkedHashMap中的节点是有联系的他是一个双向链表所以插入的时候是有序的
LinkedHashMap没有put方法 完全使用父类HashMap中的put方法 只不过在构架链表的时候会有不同
具体体现在
tab[i] = newNode(hash, key, value, null);
//这是LinkedHshMap的创建节点方法
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
//创建LinkedHashMap内部类Entry
LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<K,V>(hash, key, value, e);
linkNodeLast(p);
return p;
}
//LinkedHashMap的内部类长这样 有个before和after
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
//新的节点进来后 维护双向链表的关系
//本质是用LinkHashMap中的head和tail节点去关联堆中的
//LinkHashMap中的内部类Entry对象 实现双向链表
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
//第一次p进来 tail是null的
//第二次p2进来先把上一个节点的p的地址值用last存起来
LinkedHashMap.Entry<K,V> last = tail;
//把第一个节点p的地址值给tail
//把第二个节点的地址值给tail
tail = p;
//第一次last是等于null的
if (last == null)
//头尾指向同一个节点
head = p;
else {
//last是p也是p2的上一个节点,把p2的上一个节点指向
//last也就是p
p.before = last;
//再把p2的地址值赋给p的after 这样双向链维护起来了
last.after = p;
}
}