Guava HashMultimap usage and precautions

Hello, everyone, good morning | noon | afternoon | evening | early morning, I am a gray ash, today I will introduce you a relatively basic knowledge point HashMultmap;

Guava can basically be said to be a package that is likely to be introduced in java development projects. The protagonist introduced today is a special container HashMultmap . Its data structure can be simply understood as Map<K, Set<V>>

So why did I suddenly think of introducing it, because I didn't understand it deeply enough yesterday, I used it as Map<K, List<V>> , and there was a problem; one time

<!-- more -->

1. Introduction to the data model

Normally, when using a new data object, we should first understand its data model;

Looking directly at the source code, you will find that the structure of the actual stored data is Map<K, Collection<V>>

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

There are also many implementations of Map in jdk, so which one is it?

Starting from the construction method, let's take a look at the initialization process of this map member

 private HashMultimap(int expectedKeys, int expectedValuesPerKey) {
    super(Platform.newHashMapWithExpectedSize(expectedKeys));
    this.expectedValuesPerKey = 2;
    Preconditions.checkArgument(expectedValuesPerKey >= 0);
    this.expectedValuesPerKey = expectedValuesPerKey;
}

private HashMultimap(Multimap<? extends K, ? extends V> multimap) {
    super(Platform.newHashMapWithExpectedSize(multimap.keySet().size()));
    this.expectedValuesPerKey = 2;
    this.putAll(multimap);
}

The key point is Platform.newHashMapWithExpectedSize , familiar friends have been able to give the answer quickly, this map is what we commonly use HashMap

The next thing to pay attention to is the Collection in the value, what container type is it; for it, it is located from the time of adding elements put(key, value)

The key source code is as follows

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

This writing method is believed to be familiar to everyone. When it exists, it is directly added to the container; when it does not exist, the container is created by createCollection and inserted into the Map; the specific implementation logic is as follows

 // com.google.common.collect.HashMultimap#createCollection
Set<V> createCollection() {
    return Platform.newHashSetWithExpectedSize(this.expectedValuesPerKey);
}

So the underlying data storage of HashMultimap is our old friend HashMap<K, HashSet<V>>

2. Simple introduction

Basically, the use posture of HashMultimap is very simple. The following is a simple example to demonstrate, basically just look at it.

2.1 Container Creation

 // 创建一个默认的 HashMap<String, Set<Integer>>,容器的初始化容量与HashMap的默认值一样
HashMultimap<String, Integer> map = HashMultimap.create();

// 当我们知道容器的个数时,推荐使用下面这种方式, 
// HashMap 设置容量为8, 每个HashSet的容量初始化为16
HashMultimap<String, Integer> map2 = HashMultimap.create(8, 16);

// 另外一个就是基于MultMap来创建的case了
HashMultimap<String, Integer> map3 = HashMultimap.create(map);

Note the third implementation above, what needs to be understood is map3.get(key) != map.get(key)

That is, the new container initialized based on the original container, its value is a new container object, and all the elements in the previous value are stuffed into the new container, not the container object that is directly referenced (in this way, is it more Think a deep copy, not a shallow copy?)

2.2 Adding elements

 // 添加单个元素
map.put("hello", 510);


// 添加多个元素
map.putAll("skill", Arrays.asList(1, 2, 3, 4, 1));

Notice

  • Because value is a HashSet, duplicate elements are ignored
  • Stuffed duplicate elements are ignored
  • Again, adding duplicate elements will be ignored

(Yes, that's where I went wrong...)

2.3 Remove elements

 // 移除skill对应的集合中,value=3的元素
map.remove("skill", 3);

// 移除key
map.removeAll("hello");

2.4 Replacing elements

If we want to replace the entire value with a new set, we can use replaceValue

 // 直接替换skill对应的value集合,新的值为 {100, 200, 300}
map.replaceValues("skill", Arrays.asList(100, 200, 300));

2.5 Getting elements and traversing

 // 获取对应的value集合,当不存在时,返回空集合(不是null,简直是贴心)
Set<Integer> set = map.get("skill");

Iteration in foreach method

 for (Map.Entry<String, Integer> entry: map.entries()) {
    System.out.println(entry.getKey() + ":" + entry.getValue());
}

Note that the above iterative member Map.Entry<String, Integer> , its key is still the key of HashMap, and the value is none of the elements in this set. For example, when the value in the container is ("skill": [100,200,300]), At this point the output is as follows

 skill:200
skill:100
skill:300
````

#### 2.6 输出所有的key

// output all keys,
map.keys()

// output key set
map.keySet();

 他们两有啥区别?看个实例

HashMultimap<String, Integer> map = HashMultimap.create();
map.replaceValues("skill", Arrays.asList(100, 200, 300));
System.out.println("keys=" + map.keys());
System.out.println("keySet=" + map.keySet());

 输出如下

keys=[skill x 3]
keySet=[skill]

 上面这个`skill x 3`是什么鬼,实际上表示`skill`有三个,返回的容器可以理解为List,不去重

而下面的`KeySet()`则返回的是个Set,会去重


#### 2.7 输出所有的value

map.values()

 通过上面的再理解这个就简单了,所有的value都合并再一个List,接下来我们看一下两种遍历方式

HashMultimap<String, Integer> map = HashMultimap.create();
map.putAll("skill", Arrays.asList(100, 200, 300));
map.put("a", 100);

for (Integer v: map.values()) {

 System.out.println(v);

}

 实际输出如下

100
100
200
300

 ### 3. 小结

这里主要介绍的是Gauva的容器HashMultimap的数据模型及使用姿势,知识点相对来说比较基础,再实际使用的时候,请牢记,把它看作是简单方便易使用的 `HashMap<K, HashSet<V>>` 即可,重点注意value中的元素不能重复即可

那么当我们希望value是个List时,可以怎么整呢?

- 此时可以使用 LinkedMultiValueMap 来替代,它的底层数据结构实际就是 `HashMap<K, LinkedHashMap<V>>`
- 使用 `ArrayListMultimap` 也可以,底层数据结构为 `HashMap<K, ArrayList<V>>`


最后提一句,guava的这几个容器的实现,其源码阅读起来不会吃力,且设计思路也非常典型,比如如果让我们自己来基于jdk的基础容器实现一个类似的容器,如何优雅的去实现呢? 这里就给了一个标准答案,强烈推荐有兴趣的小伙伴瞅一下


## 一灰灰的联系方式 

尽信书则不如无书,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

- 个人站点:[https://blog.hhui.top](https://blog.hhui.top)
- 微博地址: [小灰灰Blog](https://weibo.com/p/1005052169825577/home)
- QQ: 一灰灰/3302797840
- 微信公众号:**一灰灰blog**

![QrCode](https://spring.hhui.top/spring-blog/imgs/info/info.png)

小灰灰Blog
251 声望46 粉丝