Guava HashMultimap使用及注意事項
hello,各位大佬上午|中午|下午|晚上|凌晨好,我是一灰灰,今天給大家介紹一個相對基礎的知識點 HashMultmap;
guava基本上可以說是java開發專案中,大概率會引入的包,今天介紹的主角是一個特殊的容器 -- HashMultmap
,可以簡單的將它的資料結構理解為Map<K, Set<V>>
那麼為什麼會突然想到介紹一下它呢,因為昨天剛因為對它理解不夠深刻,把它當作了Map<K, List<V>>
來使用,結果出了問題;既然如此那就好好盤一盤,反思一下
<!-- more -->
1. 資料模型介紹
正常來講,在使用一個新的資料物件時,我們應該先的瞭解它的資料模型;
直接看原始碼,會發現實際儲存資料的結構為 Map<K, Collection<V>>
abstract class AbstractMapBasedMultimap<K, V> extends AbstractMultimap<K, V> implements Serializable {
private transient Map<K, Collection<V>> map;
}
再jdk中Map也有很多實現,那麼具體是哪個呢?
從構造方法出發,來看下這個map成員的初始化過程
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);
}
關鍵點就在 Platform.newHashMapWithExpectedSize
,熟悉的小夥伴已經能很快給出答案了,這個map就是我們常用的HashMap
接下來需要關注的就是value中的Collection,是什麼容器型別了;對於它,則從新增元素的時候來定位put(key, value)
關鍵原始碼如下
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;
}
}
這個寫法相信大家都不會陌生,存在時,直接新增到容器;不存在時,則通過 createCollection
來建立容器,並塞入Map;其具體的實現邏輯如下
// com.google.common.collect.HashMultimap#createCollection
Set<V> createCollection() {
return Platform.newHashSetWithExpectedSize(this.expectedValuesPerKey);
}
所以HashMultimap的底層資料儲存就是我們的老朋友 HashMap<K, HashSet<V>>
2. 簡單使用介紹
基本來講,HashMultimap的使用姿勢非常簡單了,下面給出簡單例項演示一下,基本上看看就會了
2.1 容器建立
// 建立一個預設的 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);
注意上面的第三種實現,需要理解的是 map3.get(key) != map.get(key)
即基於原來的容器初始化的新容器,其value是一個新的容器物件,將之前的value中所有元素,都塞入新的容器中,並不是直接引用就的容器物件(這麼一說是不是更想是深拷貝,而不是淺拷貝呢?)
2.2 新增元素
// 新增單個元素
map.put("hello", 510);
// 新增多個元素
map.putAll("skill", Arrays.asList(1, 2, 3, 4, 1));
注意
- 因為value是HashSet,所以重複的元素會忽略
- 塞入重複的元素會忽略
- 再次申明,新增重複的元素會忽略
(沒錯,我就是這裡出了問題......)
2.3 移除元素
// 移除skill對應的集合中,value=3的元素
map.remove("skill", 3);
// 移除key
map.removeAll("hello");
2.4 替換元素
如果我們希望將整個value都換成一個新的集合,那麼可以使用replaceValue
// 直接替換skill對應的value集合,新的值為 {100, 200, 300}
map.replaceValues("skill", Arrays.asList(100, 200, 300));
2.5 獲取元素及遍歷
// 獲取對應的value集合,當不存在時,返回空集合(不是null,簡直是貼心)
Set<Integer> set = map.get("skill");
foreach方式的迭代
for (Map.Entry<String, Integer> entry: map.entries()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
注意上面的迭代成員 Map.Entry<String, Integer>
,其key依然是HashMap的key,而value則是這個集合中的沒一個元素,比如容器中的值為("skill": [100,200,300])時,此時輸出如下
skill:200
skill:100
skill:300
````
#### 2.6 輸出所有的key
// 輸出所有的key,
map.keys()
// 輸出key集合
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)