• Guava HashMultimap使用及注意事项


    Guava HashMultimap使用及注意事项

    hello,各位大佬上午|中午|下午|晚上|凌晨好,我是一灰灰,今天给大家介绍一个相对基础的知识点 HashMultmap;

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

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

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

    QrCode

  • 相关阅读:
    在弹出窗口中显示带checkbox的
    列属性设定-隐藏列
    Aggregations应用-合计(total)、小计(subtotal)、平均值(average)
    排序(sort)、小计(subtotal)
    过滤器(filter)
    显示图标(ICON)和提示信息(Tooltips)
    单元格style应用-按钮、热点(hotspot)、checkbox等
    布局列分组
    图床-1
    q-1
  • 原文地址:https://www.cnblogs.com/yihuihui/p/16310706.html
Copyright © 2020-2023  润新知