时间的法外狂徒

导航

java集合Map体系

一、常用Map类

HashMap:最常用的实现类。java8以前:数组+链表;java8:数组+链表/红黑树

LinkedHashMap:相比较HashMap,元素顺序排列。

TreeMap:key自动排序

HashTable:效率不高的线程安全类。

ConccurentHashMap:效率高的线程安全类。

 

二、HashMap的底层实现

HashMap是存储映射关系的集合,key-value方式。基本元素时entry,每个entry就是一个键值对映射。

HashMap内部维护的key的是Set集合,保证key的唯一,所以HashMap的Key必须要保证重写过HashCode和Equals方法。维护value的是Collection集合。

    transient Set<K>        keySet;
    transient Collection<V> values;

put方法的逻辑:

1、如何HashMap没有初始化,则初始化,定义size。

2、对key求Hash值,然后在根据size计算下标位置。

3、如果没有碰撞,即下标位置没有元素,直接添加元素。

4、如果碰撞了,以链表的方式放到链表后面。

5、如果链表长度超过阀值(8),就把链表转成红黑树。

6、如果链表长度低于6,就把红黑树转回链表。

7、如果该下标存在相同的key,就替换掉value。

8、如果元素的数量达到总容量0.75,就需要resize(扩容2倍重排)。

 

java8之后,HashMap使用数组+链表+红黑树的方式。

1、为什么要添加红黑树

当散列算法出现极端情况时,即所以的hash值计算出来的的下标都一样,那么链表中的数据会很庞大,不利于查询。所以当同一下标位置的元素超过一定数量,就将链表转换成红黑树,提高查询效率。

2、为什么使用key的Hash值及HashMap如何由hash找到元素。

维护key的是set集合,以为着key的唯一。而确定唯一的方式就是重写后的equals方法,但是如果要只使用equals方法比较,在数据元素较多时,效率会慢。所以通过保证equals方法相同的key,hash也相同,来确定同样的key会计算出同样的下标。这样就可以提高比较的效率。同时,因为不保证不同的key的hash一定不同,所以会有碰撞产生。

那么HashMap如何通过key的hash值计算出小标呢。

看代码:

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

这段代码的意义在于尽量保证散列值的均匀,将32位int值右移16位,在与原hash值异或运算。

 Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);

只看部分代码:i = (n - 1) & hash

这段代码中,i 就是需要的下标位置,n是map的容量大小。将上一步结果的hash值与n做与运算得出下标。

 

3、将HashMap包装成一个线程安全的map

        Map<String,String> map = new HashMap();
        Map safeHashMap = Collections.synchronizedMap(map);

这样包装,和HashTable没什么区别,都是把整个map加锁。

二、HashTable效率为什么低

因为他把整个map加了锁,同一时间只能操作一个元素。

三、同样是线程安全,为什么ConccurentHashMap效率高

java5有了ConccurentHashmap,通过锁细粒度化,将整个锁拆解成多个锁进行优化。通过分段锁Segment实现。例如:一个map有12个元素,没三个元素一把锁,这样理论上就有HashMap的4倍效率。早期的ConccurentHashMap默认分了16段锁,就有HashMap16倍效率。

java8之后ConccurentHashMap使用:CAS+synchronized使锁跟家细化。它在找到下标之后,如果没有元素,则使用CAS循环插入,失败则进入下一次循环。如果下标有元素节点,就获取该下标点的synchronized锁,进入同步操作。这样就使得每一个元素都相当于一个锁,使锁更加的细粒化,并且对于空下标,可以直接插入,同时用CAS保证该操作线程安全。

posted on 2020-05-22 23:34  抄手砚  阅读(385)  评论(0编辑  收藏  举报