最新版 Guava为29

<dependency>
   <groupId>com.google.guava</groupId>
   <artifactId>guava</artifactId>
   <version>29.0-jre</version>
</dependency>


Guava是Google开源的一个项目,github上面的描述为Google core libraries for Java,其实就是Google内部沉淀的一个java工具类包。它的一些工具类或思想也被JDK认可以及引入了,比如Optional,并且在很多其他开源框架也能看到guava的身影,所以学习这个工具类包对于我们日常开发是很有帮助的。工具的作用就是提升效能。


字符串操作 

Joiner来处理我们常出现的字符串拼接操作。

        List<String> words = Lists.newArrayList("123","456","789",null);

        //不使用guava         StringBuilder sb = new StringBuilder();
        for(String word : words){
            if(word==null){
                sb.append("default");
            }else {
                sb.append(word);
            }
            sb.append(",");
        }
        if(sb.length()>1){
            sb.deleteCharAt(sb.length()-1);
        }
        System.out.println(sb.toString());

        //使用guava         System.out.println(Joiner.on(",").useForNull("default").join(words));
        System.out.println(Joiner.on(",").skipNulls().join(words));

        Map<String, String> data = ImmutableMap.of("a""1""b""2");
        System.out.println(Joiner.on(",").withKeyValueSeparator("-").join(data));
        //output:a-1,b-2         Map<String, Integer> data2 = ImmutableMap.of("a"1"b"2);
        System.out.println(Joiner.on(",").withKeyValueSeparator("-").join(data2));
        //output:a-1,b-2 

使用了 guava后代码是变得多么简洁,并且这个工具类是绝对没有bug的,我们自己写这种代码还保不定出错。

Joiner的使用方式分为三步。

  1. on方法用来设置链接符

  2. 在on方法之后 join方法之前 ,我们可以做一些扩展操作,比如我上面代码的useForNull是为null值设置默认值。

  3. join方法用来设置被操作的集合

除了useForNull之外,Joiner的扩展操作还有

  1. skipNulls 跳过null值

  2. withKeyValueSeparator 用来处理对Map的输出


Splitter思想和Joiner类似,我们直接看例子   

        Splitter.on(",").omitEmptyStrings().splitToList("123,456,789,,23").forEach(a->{
            System.out.println(a);
        });
        Splitter.on(",").limit(2).splitToList("123,456,789,,23").forEach(a->{
            System.out.println(a);
        });
        Splitter.on(",").trimResults().splitToList("12 3, 456 ,789,,23").forEach(a->{
            System.out.println(a);
        });
        Map<String,String> map = Splitter.on(",").withKeyValueSeparator("-").split("1-2,3-5");
        System.out.println(map);

介绍下on后面的扩展操作
omitEmptyStrings 用来省略空白
limit 用来限制结果个数,也就是前几个分隔符会生效
trimResults 去除结果头尾空格
withKeyValueSeparator 将String转换Map

CharMatcher

CharMatcher用来从字符串匹配出自己想要的那部分,操作也被抽象为两步

  1. 选择匹配模式

  2. 选择如何处理这些匹配到的字符

不理解?看下demo就清楚了

        System.out.println(CharMatcher.inRange('0','9').retainFrom("asfds12312fds444"));
        //12312444         System.out.println(CharMatcher.inRange('0','9').removeFrom("asfds12312fds444"));
        //asfdsfds         System.out.println(CharMatcher.inRange('0','9').or(CharMatcher.whitespace()).retainFrom("as fds123 12 fds444"));
        // 123 12 444 

CharMatcher是相当的灵活,有什么匹配需求看对应API即可。

集合相关

Guava 集合示例
普通的Collection集合
List<String> list = Lists.newArrayList(); Set<String> set = Sets.newHashSet(); Map<String, String> map = Maps.newHashMap();
Guava的不可变集合创建
ImmutableList<String> iList = ImmutableList.of("a", "b", "c");
ImmutableSet<String> iSet = ImmutableSet.of("e1", "e2");
ImmutableMap<String, String> iMap = ImmutableMap.of("k1", "v1", "k2", "v2");
immutable 不可变对象特点
1.在多线程操作下,是线程安全的。

2.所有不可变集合会比可变集合更有效的利用资源。

3.中途不可改变

Map-List 对比

普通写法
1. Map<String,List<Integer>> map = new HashMap<String,List<Integer>>(); 2. List<Integer> list = new ArrayList<Integer>(); 3. list.add(1); 4. list.add(2); 5. map.put("test", list);
    System.out.println(map.get("test")); #需要5步,执行结果[1, 2] 
Guava写法
1. Multimap<String,Integer> mapM = ArrayListMultimap.create();
2. mapM.put("test",1);
3. mapM.put("test",2);
    System.out.println(mapM.get("test")); #需要3步,执行结果[1, 2] 

备注:执行结果都一样,但是代码少了近一半




新集合

Guava提供了一些自定义的新集合类,用来解决业务开发中JDK自带集合满足不了我们需求的问题。意思就是说,以前你做一个功能要在老集合上面进行复杂操作,但是使用新集合之后,它直接能满足你的需求。

MultiSet

MultiSet的特性是可以用来统计集合内元素出现的次数,在JDK自带集合类中,我们会使用以下代码实现这个功能

        List<String> words = Lists.newArrayList("a","b","c","b","b","c");
        Map<String, Integer> counts = new HashMap<String, Integer>();
        for (String word : words) {
            Integer count = counts.get(word);
            if (count == null) {
                counts.put(word, 1);
            } else {
                counts.put(word, count + 1);
            }
        }
        //output          // {a=1, b=3, c=2} 

但是是用了MultiSet后

        List<String> words = Lists.newArrayList("a","b","c","b","b","c");

        Multiset<String> multiset1 = HashMultiset.create();
        for(String word : words){
            multiset1.add(word);
        }
        System.out.println(multiset1);

        Multiset<String> multiset2 = HashMultiset.create(words);
        multiset2.add("d",4);
        System.out.println(multiset2);
        //output         //[a, b x 3, c x 2]         //[a, b x 3, c x 2, d x 4]         //1 

Multiset的实现类有很多个,这边我使用了HashMultiset。
具体使用上我们通过create方法初始化Multiset实例,通过add增加元素,然后通过count可以得到这个元素出现的次数。除了通过add增加元素,在create初始化的时候,我们也能传入数组进行初始化。

SortedMultiset

SortedMultiset是Multiset的变体,增加了针对元素次数的排序功能,接口实现类为TreeMultiset

使用方式如下

        SortedMultiset<Integer> sortedMultiset = TreeMultiset.create();
        sortedMultiset.add(2,3);
        sortedMultiset.add(3,5);
        sortedMultiset.add(4,4);
        System.out.println(sortedMultiset);
        sortedMultiset = sortedMultiset.descendingMultiset();
        System.out.println(sortedMultiset);
        System.out.println(sortedMultiset.firstEntry().getElement());

        sortedMultiset = sortedMultiset.subMultiset(3,BoundType.OPEN,2,BoundType.CLOSED);
        System.out.println(sortedMultiset);

        //output         //[2 x 3, 3 x 5, 4 x 4]         //[4 x 4, 3 x 5, 2 x 3]         //4         //[2 x 3] 

不过这个SortedMultiset是针对元素进行排序的,而不是元素次数,所以使用这个集合类的时候,最好保存数字类型的元素。并且它的subMultiset是针对这个排序规则来的,比如我上面是倒序的,使用subMultiset是3到2,而不是2到3。

guava文档中SortedMultiset的使用案例是用来统计接口时延的分布,所以key为Long类型。

MultiMap

MultiMap可以理解为对Map<k, list

        ListMultimap<String,Integer> listMultimap = MultimapBuilder
                .treeKeys()
                .arrayListValues()
                .build();
        listMultimap.put("1",1);
        listMultimap.put("1",2);
        listMultimap.put("2",1);
        System.out.println(listMultimap);

        List<Integer> value = listMultimap.get("1");
        value.add(3);
        System.out.println(listMultimap);

        listMultimap.removeAll("2");
        listMultimap.remove("1",1);
        System.out.println(listMultimap);

        Map<String, Collection<Integer>> mapView = listMultimap.asMap();
        System.out.println(mapView);

        SetMultimap<String,Integer> setMultimap = MultimapBuilder
                .treeKeys()
                .hashSetValues()
                .build();
        //output         //{1=[1, 2], 2=[1]}         //{1=[1, 2, 3], 2=[1]}         //{1=[2, 3]}         //{1=[2, 3]} 

首先我们可以看到MultiMap的初始化采用建造者模式,key和value 的实现是定制化的,可以根据自己具体需求选择对应实现。选择treeKeys就代表key是有序的。
其次通过get方法拿到的value List是浅拷贝。
SetMultimap是另外一种MultiMap的实现,不同之处么,Set去重。

BiMap

BiMap提供的功能是反转,就是说Map转换为Map。通过这个数据结构能够满足你需要通过value去查key的需求,而不是同时维护两个map。

        BiMap<String,String> biMap = HashBiMap.create();
        biMap.put("scj","programmer");
        //biMap.put("scj2","programmer");         System.out.println(biMap.get("scj"));
        System.out.println(biMap.inverse().get("programmer"));
        //output         //programmer         //scj 

通过inverse能够进行反转。
需要注意的是 value不能重复,不然会报错。毕竟反转后也是Map,所以value肯定不能重复。

Table

通过Map这个结构,我们可以通过key去找到我们的数据。Table的不同之处是,他提供了两个维度去找到我们的数据。

        Table<String,String,String> table = HashBasedTable.create();
        table.put("male","programmer","scj");
        table.put("female","beauty","ss");
        table.put("female","programmer","s2");

        System.out.println(table.get("male","programmer"));
        System.out.println(table.row("male").get("programmer"));
        System.out.println(table.column("programmer").get("female"));

三个泛型分别为Row,Column,Value,所以这个数据类型叫Table。那么问题来了,三维,四维,五维的叫什么。。
get方法通过row和column定位value
row/column方法通过Row/Column的维度得到对应的Map

集合工具类

以下集合工具类的好处是

  1. 提供了一些工厂方法,让我们创建集合更加方便

我们上面创建新集合,全部都是通过工厂方法的模式来的,并且guava也提供了JDK原生集合的工厂创建方法,见Lists,Sets,Maps。为什么推崇用工厂方法呢,因为在JDK8以前泛型不能省略,代码冗余。并且工厂方法API除了普通的创建之外也有很多变体。

        List<String> test = new ArrayList<String>();
        List<String> test2 = Lists.newArrayList();
        List<String> test3 = Lists.newArrayList("1","2");
        List<String> test4 = Lists.newArrayList(test);
  1. 封装了一些其他方法,让我们操纵集合更加方便
    这边我选取一些guava中我觉得好用的集合工具

|工具类|方法|作用|
|-|-|-|
|Sets|union|求两个的set并集|
|Sets|intersection|求两个set的交集|
|Sets|difference|求两个set的差集|
|Maps| difference|返回MapDifference用于比较两个Map的并/交/左差/右差集|

缓存

Guava提供了一个基于本地缓存的工具类,很好的封装了缓存的一些特性,使用方式如下。

        LoadingCache<String,String> cache = CacheBuilder.newBuilder()
                .maximumSize(100)
                .maximumWeight(1000)
                .weigher(new Weigher<StringString>() {
                    @Override                     public int weigh(String key, String value) {
                        return key.length();
                    }
                })
                .expireAfterAccess(10, TimeUnit.MINUTES)
                .expireAfterAccess(10, TimeUnit.MINUTES)
                .build(new CacheLoader<StringString>() {
                    @Override                     public String load(String key) throws Exception {
                        return key+"cache";
                    }
                });
        cache.put("test","23333");
        System.out.println(cache.get("test"));
        System.out.println(cache.get("scj"));
        //output         //2333         //scjcache 

同样的,使用建造者模式进行初始化。针对初始化,我总结了以下几点。

  1. 设置缓存容量

  2. 设置缓存过期策略

  3. 设置缓存生成策略
    缓存大小和过期策略都是为了解决就是应用内存有限以及缓存有效性的问题。
    对于缓存大小有Size和Weight两种模式。
    Size针对缓存的个数来设置上限。

上面代码只是为了说明使用方式,两种模式只能设置一种
Weight可以通过Weigher函数针对不同的缓存来返回不同Weight,所有缓存累加值不能超过maximumWeight。
当缓存容量超过限制值后,我们就需要根据缓存过期策略淘汰一些缓存。
expireAfterAccess会在缓存read或write后指定时间后失效。
expireAfterWrite会在缓存write后指定时间后失效。
上面代码只是为了说明使用方式,两种模式只能设置一种
缓存生成策略通过CacheLoader来封装我们缓存的生成逻辑。我们可以预先初始化缓存,当get的时候,如果key不在缓存中,就会通过CacheLoader来生成我们的缓存。