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;
//返回2的幂次方
this.threshold = tableSizeFor(initialCapacity);
}
复制代码
对于上面的构造器,我们需要注意的是`this.threshold = tableSizeFor(initialCapacity);`这边的 threshold 为 2的幂次方,而不是`capacity * load factor`,当然此处并非是错误,因为此时 table 并没有真正的被初始化,初始化动作被延迟到了`putVal()`当中,所以 threshold 会被重新计算。
/**
根据指定的容量以及默认负载因子(0.75)初始化一个空的 HashMap 实例
如果 initCapacity是负数,那么将抛出 IllegalArgumentException
*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
/**
Constructs a new HashMap with the same mappings as the
specified Map. The HashMap is created with
default load factor (0.75) and an initial capacity sufficient to
hold the mappings in the specified Map.
@param m the map whose mappings are to be placed in this map
@throws NullPointerException if the specified map is null
*/
public HashMap(Map extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
复制代码
### 查询
/**
返回指定 key 所对应的 value 值,当不存在指定的 key 时,返回 null。
当返回 null 的时候并不表明哈希表中不存在这种关系的映射,有可能对于指定的 key,其对应的值就是 null。
因此可以通过 containsKey 来区分这两种情况。
*/
public V get(Object key) {
Node e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
/**
1.首先通过 key 的哈希值找到其所在的哈希桶
2.对于 key 所在的哈希桶只有一个元素,此时就是 key 对应的节点,
3.对于 key 所在的哈希桶超过一个节点,此时分两种情况:
如果这是一个 TreeNode,表明通过红黑树存储,在红黑树中查找
如果不是一个 TreeNode,表明通过链表存储(链地址法),在链表中查找
4.查找不到相应的 key,返回 null
*/
final Node
Node[] tab; Node first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
复制代码
### 存储
/**
*/
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
/**
1.判断哈希表 table 是否为空,是的话进行扩容操作
2.根据键 key 计算得到的 哈希桶数组索引,如果 table[i] 为空,那么直接新建节点
3.判断 table[i] 的首个元素是否等于 key,如果是的话就更新旧的 value 值
4.判断 table[i] 是否为 TreeNode,是的话即为红黑树,直接在树中进行插入
5.遍历 table[i],遍历过程发现 key 已经存在,更新旧的 value 值,否则进行插入操作,插入后发现链表长度大于8,则将链表转换为红黑树
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node[] tab; Node p; int n, i;
//哈希表 table 为空,进行扩容操作
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// tab[i] 为空,直接新建节点
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node e; K k;
//tab[i] 首个元素即为 key,更新旧值
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 {
//遍历 tab[i],key 已经存在,更新旧的 value 值,否则进心插入操作,插入后链表长度大于8,将链表转换为红黑树
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//链表长度大于8
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;
}
}
//key 已经存在,更新旧值
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
//HashMap插入元素表明进行了结构性调整
++modCount;
//实际键值对数量超过 threshold,进行扩容操作
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
复制代码
### 扩容
/**
初始化或者对哈希表进行扩容操作。如果当前哈希表为空,则根据字段阈值中的初始容量进行分配。
否则,因为我们扩容两倍,那么对于桶中的元素要么在原位置,要么在原位置再移动2次幂的位置。
*/
final Node
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;
}
//容量没有超过最大值,容量变为原来两倍
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
//调用了HashMap的带参构造器,初始容量用threshold替换,
//在带参构造器中,threshold的值为 tableSizeFor() 的返回值,也就是2的幂次方,而不是 capacity * load factor
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[] newTab = (Node[])new Node[newCap];
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node e;
if ((e = oldTab[j]) != null) {
//将原哈希桶置空,以便GC
oldTab[j] = null;
//当前节点不是以链表形式存在,直接计算其应放置的新位置
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
//当前节点是TreeNode
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;
do {
next = e.next;
//原索引
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
//原索引 + oldCap
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;
}
复制代码
因为哈希表使用2次幂的拓展(指长度拓展为原来的2倍),所以在扩容的时候,元素的位置要么在原位置,要么在原位置再移动2次幂的位置。为什么是这么一个规律呢?我们假设 n 为 table 的长度,图(a)表示扩容前的key1和key2两种key确定索引位置的示例,图(b)表示扩容后key1和key2两种key确定索引位置的示例,其中hash1是key1对应的哈希与高位运算结果。
![](https://user-gold-cdn.xitu.io/2018/12/11/1679cf910636ca83?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)
元素在重新计算hash之后,因为n变为2倍,那么n-1的mask范围在高位多1bit(红色),因此新的index就会发生这样的变化:
![](https://user-gold-cdn.xitu.io/2018/12/11/1679cf910689805f?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)
因此,我们在扩容的时候,只需要看看原来的hash值新增的那个 bit 是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap”,可以看看下图为16扩充为32的resize示意图:
![](https://user-gold-cdn.xitu.io/2018/12/11/1679cf910667f6c0?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)
### 删除
/**
*/
public V remove(Object key) {
Node e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
/**Java
1.根据 key 的哈希值在哈希桶中查找是否存在这个包含有这个 key 的节点
链表头节点是要查找的节点
节点是TreeNode,在红黑树中查找
在链表中进行查找
2.如果查找到对应的节点,进行删除操作
从红黑树中删除
将链表头节点删除
在链表中删除
@param hash key 的 hash 值
@param key 指定的 key
@param value 当 matchhValue 为真时,则要匹配这个 value
@param matchValue 为真并且与 value 相等时进行删除
@param movable if false do not move other nodes while removing
@return the node, or null if none
*/
final Node
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) {
//节点为TreeNode,在红黑树中查找是否存在指定的key
if (p instanceof TreeNode)
node = ((TreeNode)p).getTreeNode(hash, key);
else {
//在链表中查找是否存在指定的key
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;
}
/**
*/
public void clear() {
Node[] tab;
当然在这还有更多整理总结的Java进阶学习笔记和面试题未展示,在这也是免费分享给那些有需要的朋友,其中囊括了Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高并发等架构资料和完整的Java架构学习进阶导图!
CodeChina开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频】
更多Java架构进阶资料展示
e.next;
//进行结构特性调整
++modCount;
--size;
afterNodeRemoval(node);
return node;
}
}
return null;
}
/**
*/
public void clear() {
Node[] tab;
[外链图片转存中…(img-6EwCeV24-1630459767779)]
当然在这还有更多整理总结的Java进阶学习笔记和面试题未展示,在这也是免费分享给那些有需要的朋友,其中囊括了Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高并发等架构资料和完整的Java架构学习进阶导图!
CodeChina开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频】[外链图片转存中…(img-h5i2IWYW-1630459767781)]
更多Java架构进阶资料展示
[外链图片转存中…(img-g5Ui4fVl-1630459767783)]
[外链图片转存中…(img-D8gZ1qKD-1630459767785)]