HashMap和ConcurrentHashMap(JDK1.8之前)

                                                  HashMap和ConcurrentHashMap(JDK1.8之前)

       1.HashMap的底层原理

       1.1.HashMap主要是用于存储key-value键值对形式的数据,每一个键值对都是一个Entry对象,这些Entry对象存储在一个数组中,这个数组就是HashMap的核心。而数组的每一个元素不止存储一个Entry对象,而是一个Entry链表。HashMap的数组默认大小为16,数组每个元素默认都是null。

       1.2.对于HashMap,我们主要使用PutGet方法进行存储和获取数据:

       Put方法:当我们需要插入一个key-value数据时(hashMap.put("a", 0) ),我们利用一个哈希函数来获取到entry应该存储在数组的下标值,如果当前下标有值,采用头插法就把数据放在链表头(默认后插入的数据使用率最高),把next指针指到刚才的头结点。

       Get方法:当我们需要回去某个key对于的数据时,我们会根据key去做一个哈希函数来获取到下标值,由于刚才所说的Hash冲突,同一个位置有可能匹配到多个Entry,这时候就需要顺着对应链表的头节点,一个一个向下来查找循环Entry链表直到获取到当前key对应的数据值。

       1.3.哈希函数(用于获取当前元素所在数组小标值):HashCode(Key) &  (Length - 1)

       用key的hashcode值与当前数组长度(长度为2的幂默认是16)-1的值在做位运算来获取下标,这种计算方式的优势:

        1.采用位运算计算效率很高

        2.长度为2的幂默认是16:2的幂-1数字的二进制都为1,这种方式计算出来的index分布比较均匀。取决于每个key的hashcode的二进制后几位数字。

        1.4HashMap不是线程安全的。

         主要是HashMap在使用过程中当数据量太大时会自动扩容(resize):HashMap.Size   >=  Capacity * LoadFactor。

         HashMap.Size:当前HashMap的大小(数据量)。

        Capacity :HashMap的当前长度,是2的幂。

        LoadFactor:HashMap负载因子,默认值为0.75f。

       Resize主要做了两件事情:

        1.扩容:创建一个新的Entry空数组,长度是原数组的2倍。

        2.reHash:重新hash计算存储

/**
 * 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;
        }
    }
}

   当多线程情况下,在Resize的时候就有可能出现环形链表,就会出现死循环。

  2.ConcurrentHashMap的底层原理

       2.1.上面说到多线程条件下,使用HashMap在扩容的时候会出现死循环,这个时候我们需要使用ConcurrentHashMap(线程安全的),ConcurrentHashMap采用的分段锁(Segment),也就是会分成多个Segment,每一个Segment就是一个HashMap。

       当两个Segment同时插入数据时,相互之间是不受影响的。

       当同一个Segment同时插入和读取数据,也是不受影响的。

       当多线程同时对同一个Segment插入数据时,会在插入方法上加上锁,保证数据正确性。

   2.2.对于ConcurrentHashMap,我们主要使用PutGet方法进行存储和获取数据:

       Put:1.为输入的Key做Hash运算,得到hash值

               2.通过hash值,定位到对应的Segment对象

               3.获取锁

               4.再次通过hash值,定位到Segment下数组的下标

               5.根据下标赋值到数组的相应位置

               6.释放锁

         Get:1.为输入的Key做Hash运算,得到hash值

                  2.根据hash值,定位到Segment

                  3.根据hash值,定位到Segment数组的具体位置,获取数据

      2.3.由于ConcurrentHashMap采用的是分段锁形式,在获取size大小的时候就复杂一些:

           1.遍历所有的Segment

           2.把Segment的元素数加起来

           3.把Segment的修改次数加起来

           4.判断所有Segment修改次数是不是大于上次的总修改次数,如果大于就意味着在计算的时候,有修改,就需要重新计算一次,反之直接返回结果值。

          5.如果尝试的次数达到了阈值,这对每个Segment进行加锁,重新计算(由于加锁肯定符合判断条件)。

          6.释放锁,统计结束

public int size() {
    // Try a few times to get accurate count. On failure due to
   // continuous async changes in table, resort to locking.
   final Segment<K,V>[] segments = this.segments;
    int size;
    boolean overflow; // true if size overflows 32 bits
    long sum;         // sum of modCounts
    long last = 0L;   // previous sum
    int retries = -1; // first iteration isn't retry
    try {
        for (;;) {
            if (retries++ == RETRIES_BEFORE_LOCK) {
                for (int j = 0; j < segments.length; ++j)
                    ensureSegment(j).lock(); // force creation
            }
            sum = 0L;
            size = 0;
            overflow = false;
            for (int j = 0; j < segments.length; ++j) {
                Segment<K,V> seg = segmentAt(segments, j);
                if (seg != null) {
                    sum += seg.modCount;
                    int c = seg.count;
                    if (c < 0 || (size += c) < 0)
                        overflow = true;
                }
            }
            if (sum == last)
                break;
            last = sum;
        }
    } finally {
        if (retries > RETRIES_BEFORE_LOCK) {
            for (int j = 0; j < segments.length; ++j)
                segmentAt(segments, j).unlock();
        }
    }
    return overflow ? Integer.MAX_VALUE : size;
}

猜你喜欢

转载自blog.csdn.net/jadebai/article/details/80811701