浅析guava容器multimap

2018-02-02  本文已影响0人  山虎hz

在日常开发中我们通常有需要对 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 的维护等 。 在接口的易用性上也非常贴合开发者。

上一篇下一篇

猜你喜欢

热点阅读