在日常开发中我们通常有需要对 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
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
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
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的实现类
Multimap提供了丰富的实现,所以你可以用它来替代程序里的Map<K, Collection
|content1|content2|content3|
|Implementation | Keys 的行为类似 | Values的行为类似
|-------|-------|-------|
| ArrayListMultimap | HashMap | ArrayList
| HashMultimap | HashMap | HashSet
| LinkedListMultimap | LinkedHashMap* | LinkedList*
| LinkedHashMultimap | LinkedHashMap | LinkedHashSet
| TreeMultimap | TreeMap | TreeSet
| ImmutableListMultimap | ImmutableMap | ImmutableList
| ImmutableSetMultimap | ImmutableMap | ImmutableSet
以上这些实现,除了immutable的实现都支持null的键和值。
1、LinkedListMultimap.entries()能维持迭代时的顺序。
2、LinkedHashMultimap维持插入的顺序,以及键的插入顺序。
总结
multimap 整体上是对java底层api的二次封装,很好的处理了各种细节,比如子容器的判空处理,totalsize的计算效率, keys 的维护等 。 在接口的易用性上也非常贴合开发者。