220525-Guava HashMultimap使用及注意事项

文章目录
  1. 1. 数据模型介绍
  2. 2. 简单使用介绍
    1. 2.1 容器创建
    2. 2.2 添加元素
    3. 2.3 移除元素
    4. 2.4 替换元素
    5. 2.5 获取元素及遍历
    6. 2.6 输出所有的key
    7. 2.7 输出所有的value
  3. 3. 小结
  • 一灰灰的联系方式
  • hello,各位大佬上午|中午|下午|晚上|凌晨好,我是一灰灰,今天给大家介绍一个相对基础的知识点 HashMultmap;

    guava基本上可以说是java开发项目中,大概率会引入的包,今天介绍的主角是一个特殊的容器 – HashMultmap,可以简单的将它的数据结构理解为Map<K, Set<V>>

    那么为什么会突然想到介绍一下它呢,因为昨天刚因为对它理解不够深刻,把它当作了Map<K, List<V>>来使用,结果出了问题;既然如此那就好好盘一盘,反思一下

    1. 数据模型介绍

    正常来讲,在使用一个新的数据对象时,我们应该先的了解它的数据模型;

    直接看源码,会发现实际存储数据的结构为 Map<K, Collection<V>>

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

    再jdk中Map也有很多实现,那么具体是哪个呢?

    从构造方法出发,来看下这个map成员的初始化过程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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)

    关键源码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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;其具体的实现逻辑如下

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

    所以HashMultimap的底层数据存储就是我们的老朋友 HashMap<K, HashSet<V>>

    2. 简单使用介绍

    基本来讲,HashMultimap的使用姿势非常简单了,下面给出简单实例演示一下,基本上看看就会了

    2.1 容器创建

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 创建一个默认的 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 添加元素

    1
    2
    3
    4
    5
    6
    // 添加单个元素
    map.put("hello", 510);


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

    注意

    • 因为value是HashSet,所以重复的元素会忽略
    • 塞入重复的元素会忽略
    • 再次申明,添加重复的元素会忽略

    (没错,我就是这里出了问题……)

    2.3 移除元素

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

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

    2.4 替换元素

    如果我们希望将整个value都换成一个新的集合,那么可以使用replaceValue

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

    2.5 获取元素及遍历

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

    foreach方式的迭代

    1
    2
    3
    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])时,此时输出如下

    1
    2
    3
    4
    skill:200
    skill:100
    skill:300
    `

    2.6 输出所有的key

    1
    2
    3
    4
    5
    // 输出所有的key,
    map.keys()

    // 输出key集合
    map.keySet();

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

    1
    2
    3
    4
    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());

    输出如下

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

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

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

    2.7 输出所有的value

    1
    map.values()

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

    1
    2
    3
    4
    5
    6
    7
    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);
    }

    实际输出如下

    1
    2
    3
    4
    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或者有更好的建议,欢迎批评指正,不吝感激

    QrCode

    # Guava

    评论

    Your browser is out-of-date!

    Update your browser to view this website correctly. Update my browser now

    ×