浅析guava容器multimap

在日常开发中我们通常有需要对 List 容器进行分组的情况,比如对下面的list数据根据name字段来进行分组:

[
    {
        "date":"2018-01-31",
        "name":"wuzhong",
        "socre":0.8
    },
    {
        "date":"2018-01-30",
        "name":"wuzhong",
        "socre":0.9
    },
    {
        "date":"2018-01-31",
        "name":"wuzhong2",
        "socre":0.8
    }
]

通常我们的做法可能很自然的想到 Map<String,List<Item>> 的结构,比如代码如下:

Map<String,List<Item>> map = new HashMap<>();
for (Item item : list){
  List<Item> tmp = map.get(item.getName());
  if (null == tmp){
      tmp = new ArrayList<>();
      map.put(item.getName(),tmp);
  }
  tmp.add(item);
}

很简单, 但是代码量有点多,特别是需要判断List为null并初始化。

再用guava实现上述的功能:

Multimap<String,Item> multiMap = ArrayListMultimap.create();
for (Item item : list){
    multiMap.put(item.getName(),item);
}

代码量直接减少了一半...

怎么实现的

我们直接跟着 ArrayListMultimap 的源码进去,发现其父类和我们最初的设计一样,也是用了 Map<K, Collection<V>> 作为数据的容器,但是多了一个 totalSize 的字段。

abstract class AbstractMapBasedMultimap<K, V> extends AbstractMultimap<K, V>
    implements Serializable {
  private transient Map<K, Collection<V>> map;
  private transient int totalSize;

接着我们继续去看put方法的具体实现。

public boolean put(@Nullable K key, @Nullable V value) {
  Collection<V> collection = map.get(key);
  if (collection == null) {
    collection = createCollection(key);
    if (collection.add(value)) {
      totalSize++;
      map.put(key, collection);
      return true;
    } else {
      throw new AssertionError("New Collection violated the Collection spec");
    }
  } else if (collection.add(value)) {
    totalSize++;
    return true;
  } else {
    return false;
  }
}

它主要做了2件事:

  1. 初始化容器,并将元素添加到容器里
  2. 维护 totalSize

这样我们再调用 multimap.size()的方法直接就返回了,不需要再次遍历和统计的过程。

疑问

multimap 里 public List<V> get(@Nullable K key) 这个方法返回的是个List容器,如果我们直接对他操作,是不是也会影响totalsize呢?

Collection<Item> wuzhong2 = multiMap.get("wuzhong2");
wuzhong2.clear();
System.out.println(multiMap.size());     //输出2
System.out.println(multiMap.keySet());   //输出 wuzhong

结果是显而易见的,对guava返回的容器进行的操作的确是会影响它的宿主对象的。

具体的源码可以看下 com.google.common.collect.AbstractMapBasedMultimap.WrappedList ,他用了代理模式,底层还是用了一个 Collection 容器。

private class WrappedCollection extends AbstractCollection<V> {
    final K key;
    Collection<V> delegate;
    final WrappedCollection ancestor;
    final Collection<V> ancestorDelegate;
    
    public void clear() {
      int oldSize = size(); // calls refreshIfEmpty
      if (oldSize == 0) {
        return;
      }
      delegate.clear();
      totalSize -= oldSize;    //维护实时的totalsize
      removeIfEmpty(); //      //维护keyset,及时删除
    }
    

总结

multimap 整体上是对java底层api的二次封装,很好的处理了各种细节,比如子容器的判空处理,totalsize的计算效率, keys 的维护等 。 在接口的易用性上也非常贴合开发者。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,929评论 18 399
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 135,380评论 19 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 175,298评论 25 709
  • 漠然的那一片海圈, 是没心没肺的浪花褪去。 感恩那一片阳光, 是无以报答的明媚。 两种人生,两个世界; 两种态度,...
    虫二略知秋阅读 1,209评论 0 0
  • 前言 在人类的发展史中,生命存在一定的不确定性,总有许多不可预测的事情发生。过于恪...
    柳下叶阅读 11,038评论 0 2