前置知识
1:HashMap在1.7 是 Entry数组+链表 1.8是 Node数组+链表+红黑树
重要变量的解释
首先是该方法的变量如下图
DEFAULT_INITIAL_CAPACITY
表示的意思就是 默认初始化的容量16 注意 容量必须是 2的幂次方倍数,至于源码如何实现的,是利用后面的位运算进行实现的,后面会讲。
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
MAXIMUM_CAPACITY
表示这个容器的长度最多不超过1,073,741,824
static final int MAXIMUM_CAPACITY = 1 << 30;
DEFAULT_LOAD_FACTOR
就是所谓的负载负载因子,当达到 这个要求:负载因子*当前容量<当前
static final float DEFAULT_LOAD_FACTOR = 0.75f;
TREEIFY_THRESHOLD
则是 从链表变为树的一个阈值,当新插入的节点加起来>8 才有可能进行树化,其是从后面的源码可以知道 进行树化的具体条件: 该列表 已经有8个节点了 才能变成红黑树 (加上这个新节点9个)先插入 并且 所有元素的个数大于等于64个元素才能树化 如果 达到了 9个 但是没到 64个 就扩容 让链表变短 这个64则是 下面要介绍 MIN_TREEIFY_CAPACITY 源码:
static final int TREEIFY_THRESHOLD = 8;
UNTREEIFY_THRESHOLD
这个变量表示的是 从树化变成链化的条件。当树化的元素小于6则进行 链化。
static final int UNTREEIFY_THRESHOLD = 6;
MIN_TREEIFY_CAPACITY
就是进行树化的那个 表容量的判断 详细解释 TREEIFY_THRESHOLD 注意这个参数的值至少是 4*TREEIFY_THRESHOLD
static final int MIN_TREEIFY_CAPACITY = 64;
方法开始执行:
看方法的启动:
无参构造创建:
很明显这次的初始化 我们只设置了负载因子,并没有进行数组的初始化
有参构建
直接看 两个参数的那个 有参构造 因为一个的也是调用的两个: 他只是 帮你 把负载因子给你用默认的传进去 有参构建:
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);
}
tableSizeFor方法: 进行测试的代码: 结果+1 则是他们最近的 2的次方数 注意 默认大小是16 就算你传进去个 3 他不会给你初始化4的大小的 因为在执行resize 方法的时候会进行判断的。
put方法
putValue方法
第四个 参数 如果为 true 则表示 存放进行相同的key值 但是value值不会进行改变了 默认为flase 第五个 为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;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e;
K k;
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 {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) {
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
resize 初始化或加倍表大小
具体的解释 已经写在代码中 自己写的 又不完善的地方 欢迎提出
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;
}
else if (oldThr > 0)
newCap = oldThr;
else {
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;
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 {
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);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
split (扩容时将树进行拆分)
注意 treenode 继承自node 它不仅时树 也是列表的结构
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
TreeNode<K,V> b = this;
TreeNode<K,V> loHead = null, loTail = null;
TreeNode<K,V> hiHead = null, hiTail = null;
int lc = 0, hc = 0;
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;
}
}
if (loHead != null) {
if (lc <= UNTREEIFY_THRESHOLD)
tab[index] = loHead.untreeify(map);
else {
tab[index] = loHead;
if (hiHead != null)
loHead.treeify(tab);
}
}
if (hiHead != null) {
if (hc <= UNTREEIFY_THRESHOLD)
tab[index + bit] = hiHead.untreeify(map);
else {
tab[index + bit] = hiHead;
if (loHead != null)
hiHead.treeify(tab);
}
}
}
untreeify(将节点变成链表)
final Node<K,V> untreeify(HashMap<K,V> map) {
Node<K,V> hd = null, tl = null;
for (Node<K,V> q = this; q != null; q = q.next) {
Node<K,V> p = map.replacementNode(q, null);
if (tl == null)
hd = p;
else
tl.next = p;
tl = p;
}
return hd;
}
treeify(将节点变成树)
final void treeify(Node<K,V>[] tab) {
TreeNode<K,V> root = null;
for (TreeNode<K,V> x = this, next; x != null; x = next) {
next = (TreeNode<K,V>)x.next;
x.left = x.right = null;
if (root == null) {
x.parent = null;
x.red = false;
root = x;
}
else {
K k = x.key;
int h = x.hash;
Class<?> kc = null;
for (TreeNode<K,V> p = root;;) {
int dir, ph;
K pk = p.key;
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)
dir = tieBreakOrder(k, pk);
TreeNode<K,V> xp = p;
if ((p = (dir <= 0) ? p.left : p.right) == null) {
x.parent = xp;
if (dir <= 0)
xp.left = x;
else
xp.right = x;
root = balanceInsertion(root, x);
break;
}
}
}
}
moveRootToFront(tab, root);
}
treeifyBin(列表进行树化)
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)
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);
}
}
总结:
扩容的条件
1.8 hashmap的扩容机制很简单: 就是当前的size(目前存放的元素)>threshold(负载因子(默认是0.75)*容量) 1.7 hahsmap的扩容机制 额外有个 条件:当前插入的格子不为空 下图是源码证明
扩容对元素坐标的处理
**1.8:**不论是树还是列表 都采用了low和 hig 进行存放 low表示 就算扩容了坐标的位置依旧不变, hig表示 坐标的位置需要改变(+oldcap即可) 1.7老实巴交的计算hash的坐标然后放进去
key=null
key为null 是存放在0这个坐标的。
什么时候初始化
看了源码都知道 是在调用put的时候才会进行初始化
链表->红黑树(树化)的条件
结论: ** 你得是第9的元素 并且 所有元素的数量>=64 才能树化** 下图是部分解释 具体细节看putvalue的注释 很详细的 就是 你put 一个值叫key 叫a ,找出坐标 取出坐标对应的key值 假设为b 前面的判断了 然后到我图片这里 Bigcount=0 但是 你看下一步就是 直接指向next 这就是意味着 bigcount=0 对应的是 第2个元素! 而进入树化 需要 bigcount >=8-1 bigcount=7 对应的是第9个元素 而插入是在判断前操作的 所以 你得是第9的元素 才能树化
树化条件为什么是8
这个在源码的注释有写:就是 8的时候概率很低了
红黑树->链表
结论 红黑树的数量<=6 就开始链化
为什么不是 <=7 链化 而是6
因为 如果是7的话 频繁进行插入删除的话 会导致 容错空间就 8 超过8树化 小于8 链化 频繁切换 浪费效率。
坐标的计算为什么hash & (length- 1) 不是key & (length-1)
解释 多加了一层运算 是为了是存储的位置更加的散乱 加的这个hash算法 会使得 key的高位也进入到运算之中,如果只是key & (length-1) 如果length=16 那 这个运算只会和后四位有关15(1111) 到时候 数据的分布不会那么的散乱。
modCount
这个变量记录你增删的操作次数 用于快速失败fail-fast 这就是为什么 你用for each 进行遍历时候 如果进行 增加删除操作 会报错的原因,他会进行 迭代器里面记录的 和 hahsmap里面记录的 这个值比较 要是不一样 就会报错。
有新的面试问题 会及时更新
|