HashMap

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,所以出现环形链表

 

posted @ 2017-12-12 01:55  KristinLee  阅读(666)  评论(1编辑  收藏  举报
jQuery火箭图标返回顶部代码