hashMap1.7
底层是Entry对象的列表和table数组
Entry对象的属性
final K key;
V value;
Entry<K,V> next;
int hash;
1 开始的时候table数组为空 没有初始化 开始初始化
下列初始时化方法中capacity字段值为16默认的,toSize值 为16 通过roundUpToPowerOf2()方法计算出来的。roundUpToPowerOf2方法 计算出来的值都是大于等于toSize的2的幂次方数 例如3-16,4-16,15-16 17-32,32-32,33-64
2 put方法中key为null时 将为null时封装的Entry对象放在table数组的首位或首位的链表元素上 addEntry方法就是table数组中保存数据的 后续在分析
3 put的key不为空时有int hash = hash(key)计算出key的hash值,根据hash值计算table数组的下标
这就是为啥容量计算时有这规律(roundUpToPowerOf2方法 计算出来的值都是大于等于toSize的2的幂次方数) hash值与上数组长度减一
4 put 时根据key的hash值计算出下标 ,根据下标找到table数组中的链表元素 并遍历链表节点,根据key的值和hash值判断是否有相等的(e.hash == hash && ((k = e.key) == key || key.equals(k)) ,如有 将对应的value值被新的覆盖,将老的value值返回
5 如果链表中没有相同key的元素,那么就作为新值进行添加,
- 如果是第一次添加,该图的if里面不会执行,里面执行的是扩容逻辑,后续分析
- 下图方法是具体的添加元素的的逻辑,方法参数 hash 新增key的hash值 、key和value就是新值、bucketIndex 根据hash值计算出的table下标。根据下标获取元素,此元素也是一个链表的头节点,图中e 就是头节点的引用。 new Entry<>(hash, key, value, e)这一行是创建新节点,并将取得的头节点e赋值给新节点的next属性上,然后将新节点赋值给table表上,新节点就成了对应table数组中的头节点。
6 扩容map中元素数量size大于等于threshold(table数组长度X0.75 (加载因子)),并且根据新增key的hash值计算出的下标对应的table元素不为空。
扩容时新建table数组并且长度是老数组的2倍(2*table.length),newTable就是新的数组
下图是真正的扩容方法,遍历老table数组的链表和遍历链表上的节点,每个节点的hash值不变,然后根据hash值重新计算节点迁移到新table上的下标。有两个规律
- 计算出的下标要么为原来老数组中的下标值 ,要么为老数组的下标值加上老数组的长度。例如原数组长度为3,扩容新数组长度为6,某个元素的在老数组中下标为2那么在新数组中下标可能为2可能为2+3(老数组长度)
- 链表的顺序 老新数组是相反的
顺序分析
e=1 e.next=2 next=2 newTable[i]=null
e.next=null,newTable[i]=1 e=2 此时链表元素第一个节点为1
e=2 e.next=3 next=3 newTable[i]=2
e.next=3 newTable[i]=2 e=3 此时链表元素第一个节点是2 第二个节点是1
e=3 e.next=null next=null newTable[i]=null
e.next=null newTable[i]=3 e=null 此时链表元素第一个节点是3 第二个节点是2 第三个节点是1
样例代码 放数据 和扩容
package test;
public class Test {
public static void main(String[] args) {
Entry[] table =new Entry[10];
Entry[] newTable =new Entry[20];
Entry e1 = table[0];
table[0] = new Entry("3", "3", e1);
e1 = table[0];
table[0] = new Entry("2", "2", e1);
e1 = table[0];
table[0] = new Entry("1", "1", e1);
for (Entry e : table) {
while(null != e) {
Entry next = e.next;
e.next = newTable[1];
newTable[1] = e;
e = next;
}
}
System.out.println(newTable);
}
static class Entry{
String key;
String value;
Entry next;
public Entry(String key,String value,Entry entry){
this.key=key;
this.value=value;
this.next=entry;
}
}
}