トラッキングコード
正確に何が行われたかを追跡するには、次のコード例を取り上げます。
public static void main(String[] args) {
HashMap<String, Integer> map = new HashMap<>();//第一步
map.put("张三", 14);//第二步
map.put("李四", 15);//第三步
map.put("重地",1 );//第四步
map.put("通话",1 );//第五步
}
ステップ 1: 初期化
初期化方法new HashMap<>()
: このメソッドを入力しますが、Node 配列は作成されません (put で実行されます)、符号なし右シフト a <<< b、簡単な計算式: a ∗ 2 ba*2^bある∗2b
// 默认的初始容量是16 1 << 4 相当于 1*2的4次方
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
/**
* 构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空HashMap,size=0 。
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
ステップ 2: 最初に配置します
を入力しmap.put("张三", 14)
、キーを計算します: Zhang San のハッシュコード
key.hashCode(): 0000 0000 0000 1011 1101 0010 1110 1001 10 進数 774889
h >>> 16:0000 0000 0000 0000 0000 0000 0000 1011 = 0000 0000 0000 1011 1101 0010 1110 1001 >>> 16
^XOR演算:同じは0、違うは1
0000 0000 0000 1011 1101 0010 1110 1001
^
0000 0000 0000 0000 0000 0000 0000 1011
0000 0000 0000 1011 1101 0010 1110 0010 10進数 774,882
これが張三の最終的なハッシュ値です: 774,882
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
なぜ 16 ビット右シフトするのでしょうか? ハッシュを増やしてハッシュの衝突を減らすには
sum (length-1) 演算により、ほとんどの場合、長さは 2 の 16 乗未満になります。したがって、操作に関与するのは常にハッシュコードの下位 16 ビット (またはさらに下位) です。上位 16 ビットも操作に関与する場合、結果の添え字はよりハッシュ化されます。
したがって、上位 16 ビットは使用されませんが、上位 16 ビットも演算に参加させるにはどうすればよいでしょうか? そのため、ハッシュ(オブジェクトキー)メソッドが存在します。彼の hashCode() とそれ自体の上位 16 ビット ^ 演算を考えてみましょう。したがって、(h >>>> 16) は上位 16 ビットを取得し、hashCode() で ^ 演算を実行します。
例: (n - 1) & ハッシュ要素の格納位置を計算する場合、前に 16 ビット右シフトしなかった結果
たとえば、上位 16 ビットはすべて 1 です。
1111 1111 1111 1111 1101 0010 1110 1001 ハッシュ
&
0000 0000 0000 0000 0000 0000 0000 1111 n - 1 = 16-1= 15
0000 0000 0000 0000 0000 0000 0000 1001 9
上位ビットが 1 である別のハッシュの例を見てみましょう。
1101 1001 1001 1001 1101 0010 1110 1001 ハッシュ
&
0000 0000 0000 0000 0000 0000 0000 1111 n - 1 = 16-1= 15
0000 0000 0000 0000 0000 0000 0000 1001 9
上位ビットが計算に参加できないため、ハッシュ競合率が増加していることがわかります。16 の右シフトがあり、上位ビットも計算に参加できるようにすると、ハッシュ競合が軽減されます。配列の長さが非常に小さい場合に備えます。
n が非常に小さい場合、つまり配列の長さが 16 であると仮定すると、n - 1 は 1111 になります。そのような値と hashCode はビット単位の AND 演算に直接適用されます。実際には、ハッシュの最後の 4 ビットのみが演算されます。値が使用されます。ハッシュ値の上位ビットが大きく変化し、下位ビットがほとんど変化しない場合、ハッシュの競合が発生しやすいため、ここでは上位ビットと下位ビットの両方を使用してこの問題を解決します。
HashMap の長さが 2 の累乗である必要があるのはなぜですか?
HashMap で使用されるメソッドは非常に賢いため、hash & (table.length -1) を使用してオブジェクトのストレージ ビットを取得します。前述したように、HashMap の基になる配列の長さは常に 2 の n 乗です。これは HashMap の最適化の速度です。長さが常に 2 の n 乗である場合、ハッシュ & (長さ-1) 演算は長さを法にすること、つまり hash%length を取得することと同等ですが、& は % より効率的です。たとえば、n%32=n&(32-1) となります。
3. 次に、putVal(774,882, "张三", 14, false, true)
コードのコメントを入力して確認します。
/**
该表在首次使用时初始化,并根据需要调整大小。
分配时,长度始终是 2 的幂。(我们还在某些操作中允许长度为零,以允许当前不需要的引导机制。)
*/
transient Node<K,V>[] table;
/**
此 HashMap 已在结构上修改的次数 结构修改是更改 HashMap 中的映射数量或以其他方式修改其内部结构(例如,重新散列)的那些。 该字段用于使 HashMap 的 Collection-views 上的迭代器快速失败。 (请参阅 ConcurrentModificationException)。
*/
transient int modCount;
/**
此映射中包含的键值映射的数量。
*/
transient int size;
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
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;
}
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//1.1 第一次进来 table 为空(上面那个),为ture
if ((tab = table) == null || (n = tab.length) == 0)
//1.2 跳转到 第一次:运行扩容方法 resize(),扩容后现在tab为16容量的数组,每个索引(桶)都是为null
n = (tab = resize()).length;//16
//1.13 开始计算元素放哪个桶(索引位置)
/*
i = (n - 1) & hash 表示计算数组的索引赋值给i,即确定元素存放在哪个桶中。
0000 0000 0000 0000 0000 0000 0000 1111 15(n - 1)
&
0000 0000 0000 1011 1101 0010 1110 0010 774,882(hash)
0000 0000 0000 0000 0000 0000 0000 0010 2
p = tab[i = (n - 1) & hash]表示获取计算出的位置的数据赋值给结点p
此时i=2,p=tab[2]=null
*/
if ((p = tab[i = (n - 1) & hash]) == null)
//创建一个新节点给桶tab[2]=node(张三,14),此时如下图1.0
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) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) {
// existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
//1.14 刚开始为0,加完为1
++modCount;
//1.15
//判断实际大小是否大于threshold阈值,如果超过则扩容;
//刚开始 size=0 > threshold=16,false
//执行完 size=1
if (++size > threshold)
resize();
//1.16
//插入后回调,LinkedHashMap中被覆盖的afterNodeInsertion方法,用来回调移除最早放入Map的对象,对HashMap毫无作用
afterNodeInsertion(evict);
//1.17
return null;
}
図1.0
初回: 拡張メソッドのsize()を実行します。
配列を初期化するのは初めてです。容量 16 のノード配列を初期化します。
final Node<K,V>[] resize() {
//1.3
//第一次进来table,还是空
Node<K,V>[] oldTab = table;
//1.4
int oldCap = (oldTab == null) ? 0 : oldTab.length;//0
//1.5
//threshold刚开始为0
int oldThr = threshold;
//1.6
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 {
//1.7
//0初始阈值表示使用默认值
newCap = DEFAULT_INITIAL_CAPACITY;//16
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//12
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
//1.8
threshold = newThr;//12
@SuppressWarnings({
"rawtypes","unchecked"})
//1.9
//创建一个16大小Node数组
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
//1.10
//新数组赋值给hashmap属性table
table = newTab;
//1.11 为空不进入
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 {
// preserve order
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;
}
}
}
}
}
//1.12
//返回
return newTab;
}
この時点で最初のプットが完了します
ステップ 3: 2 回目の装着
移行putVal(842049, "李四", 15, false, true)
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;
//1.0 计算得出索引位置为1
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) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) {
// existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
//1.1 修改次数再次加1
++modCount;//2
//1.2
//1>12,false,执行完size变为2,就是键值对个数
if (++size > threshold)
resize();
//1.3 与hashmap无关
afterNodeInsertion(evict);
//1.4 返回
return null;
}
実行後のHashMap構造は次のようになります。
図1.1
ステップ4:3回目の装着
まだ入っていますputVal(1179410, "重地", 1, false, true)
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//1.0 目前table不为空,跳过
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//1.1 计算出i=2,与"张三"位置相同,值必然不为空,跳过,此时p为"张三"节点
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
/*1.3
判断这个桶的第一个元素是否和新节点是否相同,是赋值给e
p.hash == hash:"张三"的hash值与"重地"的hash值比较,false
(k = p.key) == key:(k="张三")=="重地",地址值不同,false
(key != null && key.equals(k)):"重地" != null && "重地".equals("张三")),false
不符合条件跳过
*/
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//1.4 不是树节点,跳过
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//1.5 遍历链表,如果到尾巴则插入,符合树化条件则树化;相等则覆盖;
for (int binCount = 0; ; ++binCount) {
//1.6 "张三"下个节点为null,(e=null)==null,true
if ((e = p.next) == null) {
/*1.7
创建一个新的结点插入到尾部,此时p.next指向的是一块新的堆内存地址,e还是null
插完之后map结构如图1.2所示
注意第四个参数next是null,因为当前元素插入到链表末尾了,那么下一个结点肯定是null。
*/
p.next = newNode(hash, key, value, null);
//1.8 0>7,false
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
//1.9 因为到达尾部,跳出循环
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//2.0 跳过
if (e != null) {
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
//2.1 执行完为3
++modCount;
//2.2 跳过,size执行完为3
if (++size > threshold)
resize();
afterNodeInsertion(evict);
//2.3
return null;
}
実行後のHashMap構造は次のようになります。
図1.2
ステップ5:4回目の装着
計算されたハッシュ値は「重要な場所」と同じなので、次のように入力します。putVal(1179410, "通话", 1, false, true)
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//1.0 table不为空,n=16,跳过
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//1.1 算出i=2,p='张三'节点,不为空,跳过
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
//1.2 p.hash('张三'),hash('通话') 不一样,key也不一样,跳过
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//1.3 不是树结构,跳过
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//遍历链表
for (int binCount = 0; ; ++binCount) {
//1.4 e这时候被下个节点赋值(第一次进来,就是第一个节点(张三)的下个节点(重地)),e=p.next="重地",跳过
//1.7 e="重地"下个节点=null,符合条件,进入
if ((e = p.next) == null) {
//1.8 创建 "重地"下个节点='通话'节点
p.next = newNode(hash, key, value, null);
//1.9 1>=7,fasle
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
//2.0 退出循环
break;
}
/*1.5
"重地" 和 '通话' hash 一样,true
k("重地")和key('通话')不同,false
(key != null && key.equals(k)),false
不符合条件,跳过
*/
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
//1.6 p被赋值为重地节点,相当于移动遍历的指针
p = e;
}
}
//2.1 还是为空,跳过
if (e != null) {
// existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
//2.2 执行完为4
++modCount;
//2.3 3>12,执行完为size=4
if (++size > threshold)
resize();
//2.4 跳过
afterNodeInsertion(evict);
//2.5 返回
return null;
}
実行後のHashMap構造は次のようになります。
[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムが備わっている可能性があります。画像を保存して直接アップロードすることをお勧めします (img-Z36LPQk4-1644454618330) (C:\Users\Administrator\Desktop\図 1.3)。 png)]
図 1.3
if (e != null) { // キー
V の既存のマッピング oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
//2.2 実行後は 4
++modCount;
//2.3 3>12 実行後は size=4
if (++size > Threshold)
assign();
//2.4 Skip
afterNodeInsertion(evict);
/ /2.5 returnreturn
null;
}
执行完,HashMap结构如下
![在这里插入图片描述](https://img-blog.csdnimg.cn/71b68003ab1b4bef8559b87d4b6c52ad.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBARmlyZV9Ta3lfSG8=,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center)
图1.3