在日常开发中我们通常有需要对 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件事:
- 初始化容器,并将元素添加到容器里
- 维护 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 的维护等 。 在接口的易用性上也非常贴合开发者。
网友评论