HashMap实现原理

HashMap实现原理

基本组成

HashMap 由Entry数组组成,Entry下是链表(jdk1.8变成红黑树)。
HashMap是基于hashing的原理,当我们给put()方法传递键和值时,我们先对键调用hashCode()方法,返回的hashCode用于找到bucket位置来储存Entry对象。

如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?

默认的负载因子大小为0.75,也就是说,当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置。

put过程:

  1. 先判断key是不是为空,为空做插入操作;
  2. 对key做hash操作,跟据hash结果定位数组下标,判断该下标的entry是否存在,如果存在,遍历链表查找key值相同的entry,并更新其value值,并返回旧值;如果不存在执行第3步新增entry操作;
  3. 新增时,判断数据(已插入entry的数量)长度是否大于等于12=16*0.75,如果是则resize操作。
  if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
  }

创建entry,插入链头

你了解重新调整HashMap大小存在什么问题吗?

当重新调整HashMap大小的时候,确实存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。
在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。
如果条件竞争发生了,那么就死循环了。(同时调整理大小导致链环)
我们看下过程:
Entry的结构为:

transient Entry[] table = (Entry[]) EMPTY_TABLE;
   static class Entry implements Map.Entry {
        final K key;
        V value;
        Entry next;
        int hash;

resize过程:

当 ((size >= threshold) 才resize
其中
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
即:threshold=16*0.75=12时resize

 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
 static final float DEFAULT_LOAD_FACTOR = 0.75f;

  void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);
    }
    void transfer(Entry[] newTable) {
        int newCapacity = newTable.length;
        for (Entry e : table) {
            while(null != e) {
                Entry next = e.next;//第一个线程执行到停止,第二个线程执行完后,第一个线程接着执行
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];//此处理发生循环:原table为3->7 ,e=3 当第二个线程执行完后,newTable[i]=7->3   第一个线程执行此行时,  3  ->(7->3) 此时产生环
                newTable[i] = e;
                e = next;
            }
        }
    }

并发执行时,从entry头开始拷贝时
原:entry head 3 ->7 tail
新:entry head 7 ->3 tail

你可能感兴趣的:(HashMap实现原理)