“我报名参与金石计划1期挑战——分割10万奖池,这是我的第4篇文章,点击查看活动详情”

概述

本篇文章首要讲解下Map宗族中3个相对冷门的容器,分别是WeakHashMap、EnumMap、IdentityHashMap, 想必咱们在平时的工作中也很少用到,或者压根不知道他们的特性以及适用场景,本篇文章就带你一探究竟。

WeakHashMap

介绍

WeakHashMap称为弱三列映射,完成了Map接口,具有如下特性:

  • WeakHashMap中的entry是一个弱引证,当除了本身有对key的引证外,此key没有其他引证,那么GC之后此map会自动丢弃此值。
  • 不是线程安全的
  • 能够存储null

演示事例

  public static void main(String[] args) {
        String a = new String("a");
        String b = new String("b");
        Map weakmap = new WeakHashMap();
        weakmap.put(a, "aaa");
        weakmap.put(b, "bbb");
        a = null;
        b = null;
        // 进行gc
        System.gc();
        Iterator j = weakmap.entrySet().iterator();
        while (j.hasNext()) {
            Map.Entry en = (Map.Entry) j.next();
            System.out.println("weakmap:" + en.getKey() + ":" + en.getValue());
        }
    }

运行成果:

你不知道的Map家族中的那些冷门容器

现已被gc收回了。

原理完成

你不知道的Map家族中的那些冷门容器

从这儿咱们能够看到其内部的Entry继承了WeakReference,也便是弱引证,所以就具有了弱引证的特点。

弱引证的特点是在垃圾收回器线程扫描它所统辖的内存区域的进程中,一旦发现了只具有弱引证的目标,不管当前内存空间满足与否,都会收回它的内存。不过,因为垃圾收回器是一个优先级很低的线程,因而不一定会很快发现那些只具有弱引证的目标。

WeakReference中有个成员变量ReferenceQueue,他的效果是GC会整理掉目标之后,引证目标会被放到ReferenceQueue中,然后遍历这个queue进行删去即可Entry。WeakHashMap内部有一个expungeStaleEntries函数,在这个函数内部完成移除其内部不用的entry然后到达的自动开释内存的目的。因而咱们每次拜访WeakHashMap的时分,都会调用这个expungeStaleEntries函数整理一遍。

你不知道的Map家族中的那些冷门容器

运用场景

在现在的并发众多的大环境下,咱们应该都用过缓存,缓存都是放在内存中的,而内存几乎是核算机中最名贵也是最稀缺的资源,所以需求谨慎的运用,否则很容易就呈现 OOM。缓存的首要效果是为了更快的处理事务、降低服务器的压力,那么就要确保缓存命中率,这儿假定整个缓存是一个 key-value 结构的(以键值对缓存为例),HashMap 作为强引证目标在没有自动将 key 删去时是不会被 JVM 收回的,这样 HashMap 中的目标就会越积越多直到 OOM 错误;那么如何做到既让缓存的命中率高又不占用那么多的内存,这儿就能够选用 WeakHashMap,当然不会有 HashMap 100% 的命中率(假定内存满足),但是在确保程序正常的前提下更好的完成了缓存这套解决方案。

EnumMap

介绍

用于枚举类型键的专用Map完成。枚举映射中的一切键有必要来自创建映射时显式或隐式指定的单个枚举类型。

相对于HashMap中枚举作为key, EnumMap内部以一个非常紧凑的数组存储value,并且依据enum类型的key直接定位到内部数组的索引,并不需求核算hashCode(),不但效率最高,并且没有额外的空间糟蹋。

  • 不是线程安全的
  • 能够存放null值

演示事例

public static void main(String[] args) {
        // 构造函数传入类型
        Map<DayOfWeek, String> map = new EnumMap<>(DayOfWeek.class);
        map.put(DayOfWeek.MONDAY, "星期一");
        map.put(DayOfWeek.TUESDAY, "星期二");
        map.put(DayOfWeek.WEDNESDAY, "星期三");
        map.put(DayOfWeek.THURSDAY, "星期四");
        map.put(DayOfWeek.FRIDAY, "星期五");
        map.put(DayOfWeek.SATURDAY, "星期六");
        map.put(DayOfWeek.SUNDAY, "星期日");
        System.out.println(map);
        System.out.println(map.get(DayOfWeek.MONDAY));
    }
    enum DayOfWeek {
        MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
    }

原理完成

put办法源码如下:

 public V put(K key, V value) {
        // 对枚举类型进行检查,看key和构造函数传入的class类型是否共同
        typeCheck(key);
        // 枚举的顺序
        int index = key.ordinal();
        // 本来方位的值
        Object oldValue = vals[index];
        // 设置值
        vals[index] = maskNull(value);
        if (oldValue == null)
            size++;
        return unmaskNull(oldValue);
    }

经过put源码发现是经过数组的方式完成存储,并且也不需求进行扩容。

运用场景

假如项目中遇到针对枚举作为key的映射容器,能够优先选择EnumMap。

IdentityHashMap

介绍

该类运用散列表完成Map接口,在比较键(和值)时运用引证持平代替目标持平。换句话说,在一个IdentityHashMap中,当且仅当(k1==k2)两个键k1和k2被认为是持平的。(在正常的Map完成(如HashMap)两个键k1和k2被认为是持平的,当且仅当(k1==null ?k2 = = null: k1.equals (k2)))。

  • 不是线程安全的
  • 无序
  • key不能够是null

演示事例

public static void main(String[] args) {
        // hashMap
        Map<Integer, String> hashMap = new HashMap<>();
        // identityHashMap
        Map<Integer, String> identityHashMap = new IdentityHashMap<>();
        hashMap.put(new Integer(200), "a");
        hashMap.put(new Integer(200), "b");
        identityHashMap.put(new Integer(200), "a");
        identityHashMap.put(new Integer(200), "b");
        //遍历hashmap
        System.out.println("hashmap 成果:");
        hashMap.forEach((key, value) -> {
            System.out.println("key = " + key + ", value = " + value);
        });
        //遍历hashmap
        System.out.println("identityHashMap 成果:");
        identityHashMap.forEach((key, value) -> {
            System.out.println("key = " + key + ", value = " + value);
        });
    }

运行成果:

你不知道的Map家族中的那些冷门容器

原理完成

IdentityHashMap底层的数据结构便是数组,咱们重视下put办法:

你不知道的Map家族中的那些冷门容器

调用hash办法,获取key在table的方位index,然后进行赋值操作,也是分成了3种状况:

1.item == k,找到了对应的key,value存在key右相邻的方位,对tab[i + 1]进行更新,并回来本来的值;

2.item == null,表明table中没有对应的key值,跳出for循环,履行tab[i] = k和tab[i + 1] = value进行新key的刺进操作。个人觉得这儿的扩容时机选择的不太好,十分困难找到的更新方位,因为扩容给整没了,还得再次重新核算,能够和HashMap一样,在更新后再扩容。

3.item != null && item != key,表明hash抵触产生,调用nextKeyIndex获取处理抵触后的index方位,然后重复上面的进程。

咱们再来看下hash办法:

你不知道的Map家族中的那些冷门容器

IdentityHashMap中获取hash值选用的System.identityHashCode办法,在不重写Object.hashCode办法时,System.identityHashCode和Object.hashCode回来的值相同,相当于目标的仅有的HashCode。System.identityHashCode(null)一直回来0, 无论是否重写Object.hashCode,都不影响System.identityHashCode的履行成果。

运用场景

当咱们有必要运用地址持平来判别值持平的场合,以及咱们确认只要其地址不持平,则其equals办法的成果也必定不持平的场合。

总结

本文首要讲解了会集不常用的Map, 当然咱们也需求了解他们的特性,在有些时分还是会用到的。