004-guava 集合-新增集合类型-MultiSet, MultiMap, BiMap , Table, ClassToInstanceMap, RangeSe, RangeMap等

一、概述

  Guava引入了很多JDK没有的、但明显有用的新集合类型。这些新类型是为了和JDK集合框架共存,而没有往JDK集合抽象中硬塞其他概念。作为一般规则,Guava集合非常精准地遵循了JDK接口契约。

二、使用

2.1、MultiSet[无序+可重复]-工具类Multisets

  Guava提供了一个新集合类型 Multiset,它可以多次添加相等的元素。维基百科从数学角度这样定义Multiset:”集合[set]概念的延伸,它的元素可以重复出现…与集合[set]相同而与元组[tuple]相反的是,Multiset元素的顺序是无关紧要的:Multiset {a, a, b}和{a, b, a}是相等的”。——译者注:这里所说的集合[set]是数学上的概念,Multiset继承自JDK中的Collection接口,而不是Set接口,所以包含重复元素并没有违反原有的接口契约。

  这个接口没有实现java.util.Set接口,Set接口规定里面是不能够放入重复的元素,如果放入重复元素会被覆盖掉的;然而Multiset接口却是可以放入重复元素的,Set接口中的元素是[1,2,3],Multiset中确可以[1✖️2,2✖️3,3✖️3]来表示多个相同的元素。

Guava提供了多种Multiset的实现,大致对应JDK中Map的各种实现:

Map 对应的Multiset 是否支持null元素
HashMap HashMultiset
TreeMap TreeMultiset 是(如果comparator支持的话)
LinkedHashMap LinkedHashMultiset
ConcurrentHashMap ConcurrentHashMultiset
ImmutableMap ImmutableMultiset

使用

    @Test
    public void testMultiSet(){
        Multiset<String> multiset= HashMultiset.create();
        multiset.add("aa");
        multiset.add("bb");
        multiset.add("cc",2);
        System.out.println(multiset);//[aa, bb, cc x 2]
        System.out.println(multiset.size()); //4
        System.out.println(multiset.count("cc"));//2
        multiset.setCount("bb",4);
        System.out.println(multiset);//[aa, bb x 4, cc x 2]
    }

  

2.2、SortedMultiset

  SortedMultiset是Multiset 接口的变种,它支持高效地获取指定范围的子集。比方说,你可以用 latencies.subMultiset(0,BoundType.CLOSED, 100, BoundType.OPEN).size()来统计你的站点中延迟在100毫秒以内的访问,然后把这个值和latencies.size()相比,以获取这个延迟水平在总体访问中的比例。

  TreeMultiset实现SortedMultiset接口。在撰写本文档时,ImmutableSortedMultiset还在测试和GWT的兼容性。

2.3、MultiMap[key-value key可以重复 ]-工具类Multimaps

  程序开发中使用Map<K, List<V>>或Map<K, Set<V>>,并且要忍受这个结构的笨拙。例如,Map<K, Set<V>>通常用来表示非标定有向图。Guava的 Multimap可以很容易地把一个键映射到多个值。换句话说,Multimap是把键映射到任意多个值的一般方式。

  很少会直接使用Multimap接口,更多时候你会用ListMultimap或SetMultimap接口,它们分别把键映射到List或Set。

  Multimap提供了多种形式的实现。在大多数要使用Map<K, Collection<V>>的地方,你都可以使用它们:

实现 键行为类似 值行为类似
ArrayListMultimap HashMap ArrayList
HashMultimap HashMap HashSet
LinkedListMultimap* LinkedHashMap* LinkedList*
LinkedHashMultimap** LinkedHashMap LinkedHashMap
TreeMultimap TreeMap TreeSet
ImmutableListMultimap ImmutableMap ImmutableList
ImmutableSetMultimap ImmutableMap ImmutableSet

  除了两个不可变形式的实现,其他所有实现都支持null键和null值

  *LinkedListMultimap.entries()保留了所有键和值的迭代顺序。详情见doc链接。

  **LinkedHashMultimap保留了映射项的插入顺序,包括键插入的顺序,以及键映射的所有值的插入顺序。

  请注意,并非所有的Multimap都和上面列出的一样,使用Map<K, Collection<V>>来实现(特别是,一些Multimap实现用了自定义的hashTable,以最小化开销)

  如果你想要更大的定制化,请用Multimaps.newMultimap(Map, Supplier<Collection>)list和 set版本,使用自定义的Collection、List或Set实现Multimap。

示例:

    @Test
    public void testMultiMap() {
        Multimap<String, String> multimap = ArrayListMultimap.create();
        multimap.put("fruit", "bannana");
        multimap.put("fruit", "apple");//key可以重复
        multimap.put("fruit", "apple");//value可以重复,不会覆盖之前的
        multimap.put("fruit", "peach");
        multimap.put("fish", "crucian");//欧洲鲫鱼
        multimap.put("fish", "carp");//鲤鱼
        Collection<String> fruits = multimap.get("fruit");
        System.err.println(fruits);//[bannana, apple, apple, peach]
        
        //对比 HashMultimap
        Multimap<String,String> multimap2= HashMultimap.create();
        multimap2.put("fruit2", "bannana");
        multimap2.put("fruit2", "apple");
        multimap2.put("fruit2", "apple");

        System.err.println(multimap2.size());//2
        System.err.println(multimap2.get("fruit2"));//[apple, bannana]     注意: 这里只有一个apple
    }

2.4、BiMap[双向Map(Bidirectional Map) 键与值都不能重复]

  传统上,实现键值对的双向映射需要维护两个单独的map,并保持它们间的同步。但这种方式很容易出错,而且对于值已经在map中的情况,会变得非常混乱。例如:

Map<String, Integer> nameToId = Maps.newHashMap();
Map<Integer, String> idToName = Maps.newHashMap();
nameToId.put("Bob", 42);
idToName.put(42, "Bob");
//如果"Bob"和42已经在map中了,会发生什么?
//如果我们忘了同步两个map,会有诡异的bug发生...

BiMap<K, V>是特殊的Map:

  可以用 inverse()反转BiMap<K, V>的键值映射
  保证值是唯一的,因此 values()返回Set而不是普通的Collection
  在BiMap中,如果你想把键映射到已经存在的值,会抛出IllegalArgumentException异常。如果对特定值,你想要强制替换它的键,请使用 BiMap.forcePut(key, value)。

    @Test
    public void testBiMap() {
        BiMap<String, Integer> userId = HashBiMap.create();
        userId.put("lhx",30);
        userId.put("zll",28);
        String userForId = userId.inverse().get(30);
        System.out.println(userForId);//lhx

        userId.put("jm",30);//报错
        String userForId2 = userId.inverse().get(30);
        System.out.println(userForId2);//lhx
    }

BiMap的各种实现

值实现 键实现 对应的BiMap实现
HashMap HashMap HashBiMap
ImmutableMap ImmutableMap ImmutableBiMap
EnumMap EnumMap EnumBiMap
EnumMap HashMap EnumHashBiMap

注:Maps类中还有一些诸如synchronizedBiMap的BiMap工具方法.

2.5、Table【双键的Map Map--> Table-->rowKey+columnKey+value //和sql中的联合主键有点像】-工具类Tables

  行、列、值。当使用多个键做索引的时候,可能会用类似Map<FirstName, Map<LastName, Person>>的实现,这种方式很丑陋,使用上也不友好。

  Guava为此提供了新集合类型Table,它有两个支持所有类型的键:”行”和”列”。

  Table有如下几种实现:

  • HashBasedTable:本质上用HashMap<R, HashMap<C, V>>实现;
  • TreeBasedTable:本质上用TreeMap<R, TreeMap<C,V>>实现;
  • ImmutableTable:本质上用ImmutableMap<R, ImmutableMap<C, V>>实现;注:ImmutableTable对稀疏或密集的数据集都有优化。
  • ArrayTable:要求在构造时就指定行和列的大小,本质上由一个二维数组实现,以提升访问速度和密集Table的内存利用率。ArrayTable与其他Table的工作原理有点不同,请参见Javadoc了解详情。

示例

    @Test
    public void testTable() {
        Table<String, String, Integer> table = HashBasedTable.create();
        table.put("a", "b", 4);
        table.put("a", "c", 20);
        table.put("b", "c", 5);

        Map<String, Integer> a = table.row("a");// returns a Map mapping {b=4, c=20}
        System.out.println(a);

        Map<String, Integer> column = table.column("c");// returns a Map mapping {a=20, b=5}
        System.out.println(column);

        Integer integer = table.get("a", "c");
        System.out.println(integer); //20
    }

2.6、ClassToInstanceMap

  ClassToInstanceMap是一种特殊的Map:它的键是类型,而值是符合键所指类型的对象。

  为了扩展Map接口,ClassToInstanceMap额外声明了两个方法:T getInstance(Class<T>) 和T putInstance(Class<T>, T),从而避免强制类型转换,同时保证了类型安全。

  ClassToInstanceMap有唯一的泛型参数,通常称为B,代表Map支持的所有类型的上界。例如:

    class Person{
        private String name;

        public Person(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }

    @Test
    public void testClassToInstanceMap() {
        ClassToInstanceMap<Person> instanceMap=MutableClassToInstanceMap.create();
        instanceMap.putInstance(Person.class, new Person("lhx"));
        instanceMap.putInstance(Person.class, new Person("lhx2"));
        Person person = instanceMap.get(Person.class); // Person{name='lhx2'} 存储了 后一个
        System.out.println(person);
    }

  从技术上讲,ClassToInstanceMap<B>实现了Map<Class<? extends B>, B>——或者换句话说,是一个映射B的子类型到对应实例的Map。这让ClassToInstanceMap包含的泛型声明有点令人困惑,但请记住B始终是Map所支持类型的上界——通常B就是Object。

  对于ClassToInstanceMap,Guava提供了两种有用的实现:MutableClassToInstanceMap和 ImmutableClassToInstanceMap

2.7、RangeSet

  描述了一组不相连的、非空的区间。当把一个区间添加到可变的RangeSet时,所有相连的区间会被合并,空区间会被忽略。例如:

    @Test
    public void testRangeSet() {
        RangeSet<Integer> rangeSet = TreeRangeSet.create();

        rangeSet.add(Range.closed(1, 10)); // {[1,10]}
        rangeSet.add(Range.closedOpen(11, 15));//不相连区间:{[1,10], [11,15)}
        rangeSet.add(Range.closedOpen(15, 20)); //相连区间; {[1,10], [11,20)}
        rangeSet.add(Range.openClosed(0, 0)); //空区间; {[1,10], [11,20)}
        rangeSet.remove(Range.open(5, 10)); //分割[1, 10]; {[1,5], [10,10], [11,20)}

        System.out.println(rangeSet);//[[1..5], [10..10], [11..20)]
    }

 

  请注意,要合并Range.closed(1, 10)和Range.closedOpen(11, 15)这样的区间,你需要首先用Range.canonical(DiscreteDomain)对区间进行预处理,例如DiscreteDomain.integers()。

  注:RangeSet不支持GWT,也不支持JDK5和更早版本;因为,RangeSet需要充分利用JDK6中NavigableMap的特性。

2.8、RangeMap

  RangeMap描述了”不相交的、非空的区间”到特定值的映射。和RangeSet不同,RangeMap不会合并相邻的映射,即便相邻的区间映射到相同的值。例如:

    @Test
    public void testRangeMap() {
        RangeMap<Integer, String> rangeMap = TreeRangeMap.create();

        rangeMap.put(Range.closed(1, 10), "foo"); //{[1,10] => "foo"}
        rangeMap.put(Range.open(3, 6), "bar"); //{[1,3] => "foo", (3,6) => "bar", [6,10] => "foo"}
        rangeMap.put(Range.open(10, 20), "foo"); //{[1,3] => "foo", (3,6) => "bar", [6,10] => "foo", (10,20) => "foo"}
        rangeMap.remove(Range.closed(5, 11)); //{[1,3] => "foo", (3,5) => "bar", (11,20) => "foo"}

        System.out.println(rangeMap);//[[1..3]=foo, (3..5)=bar, (11..20)=foo]
    }

 

 

 

 

 

 

 

 

 

 

方式

posted @ 2019-09-25 13:35  bjlhx15  阅读(611)  评论(0编辑  收藏  举报
Copyright ©2011~2020 JD-李宏旭