HashMap

it2022-05-08  11

1.什么是HashMap?

HashMap是一个用于存储Key-Value键值对的集合,每一个键值对也叫Entry.这些个键值对分散存储在一个数组中,这个数组就是HashMap的主干,数组的每个元素初始化为null,

1,为什么用了一维数组:数组存储区间是连续的,占用内存严重,故空间复杂度很大。但数组的查找时间复杂度小,为O(1);数组的特点是:寻址容易,插入和删除困难

2,为什么用了链表:链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,达O(N)。链表的特点是:寻址困难,插入和删除容易而HashMap是两者的结合,用一维数组存放散列地址,以便更快速的遍历;用链表存放地址值,以便更快的插入和删除!

2.HashMap的Put Get方法

Put调用put方法例如调用hashMap.put("apple",0),会在数组中插入一个key为"apple"的元素,通过一个hash函数确定Entry的插入位置index=hash("apple")但是数组的长度有限,可能会发生index冲突,HashMap数组的每一个元素是一个Entry,也是一个链表的头结点,当新来的Entry映射发生冲突时,会使用头插法(发明者认为后插入的Entry被查找的可能性大),将其插入链表中,每一个Entry对象通过Next指针指向它的下一个结点Get首先会将输入的key做一次映射,得到对应的index,查看头结点的key是否是target,如果是就找到了;如果不是就查看next结点

3.HashMap长度

HashMap默认初始长度16,并且每次自动扩展或者手动初始化时,长度必须是2的幂Hash: index=HashCode(Key)&(length-1);如果长度不是2的整数幂,会增加重复几率的而且2的整数幂-1其实二进制都是1,相当于取HashCode的后几位,尽量保证HashCode分布均匀

4.HashMap的扩充

HashMap的容量是有限的,经过多次的插入,会达到一定的饱和度,Key映射位置发生冲突的几率提高,所以要Resize扩充HashMap数组

Resize的因素:Capacity:HashMap的当前长度LoadFactor:HashMap得负载因子,默认值为0.75f

HashMap是否能进行Resize的条件:HashMap.size >= Capacity * LoadFactor

Resize步骤:扩容:创建一个新的Entry空数组,长度是原来的两倍ReHash:遍历原来的数组,把所有的Entry重新Hash到新数组.

ReHash源码:

/** * Transfers all entries from current table to newTable. */ void transfer(Entry[] newTable, boolean rehash) { int newCapacity = newTable.length; for (Entry<K,V> e : table) { while(null != e) { Entry<K,V> next = e.next; if (rehash) { e.hash = null == e.key ? 0 : hash(e.key); } int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } } }

5.HashMap出现环的原理

正常的Rehash过程

假设表size=2,key=3,7,5 这里所使用的rehash算法就是index=key&(size-1)

所以key=3,5,7的Entry对象都指向了数组index=1的位置

而capacity=3,size=2,size<capacity*0.75

所以数组应进行扩容.

上面有数组扩容时,table rehash到newTable的代码,这里只摘取while循环的代码

while(null != e) { Entry<K,V> next = e.next; if (rehash) { e.hash = null == e.key ? 0 : hash(e.key); } int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } 

Rehash过程图:

并发下的Rehash过程

假设有两个线程:线程A和线程B

1.线程A执行一行代码后挂起

while(null != e) { Entry<K,V> next = e.next; //线程A执行完这句时挂起 if (rehash) { e.hash = null == e.key ? 0 : hash(e.key); } int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; }   

 

2.线程B全部执行完

然而线程A: e指向key:3,next指向key:7,e.next=next;

       线程B: e指向key:3,next指向key:7,next.next=e

3.这时线程A抢到了执行权,回来继续执行

e.next=newTable[i]; //导致key3.next指向了null newTable[i]=e; //线程A的index3指向了key3 e=next; //e指向了key7

  

4.因为线程A执行的结果:key3.next=key7,所以下一次循环将key7插在key3所在链的头,然后e和next都往下移

 5.执行e.next=newTable[i];导致key3.next=key7,所以出现环形链表

 

转载于:https://www.cnblogs.com/Hangtutu/p/8025854.html


最新回复(0)