九阳神功-Guava使用技巧

九阳神功我觉得是一个比较基础的武功,能够反弹伤害(避免垃圾代码),学了它,张无忌才能驾驭其他更高级的武功。

介绍

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

下面我会大致通过demo介绍下使用,具体细节待各位自己深入了解,总会有你惊喜的地方。

引入guava

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

字符串操作

Joiner

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

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<String,String>

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提供了一些自定义的新集合类,用来解决业务开发中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<V>>或Map<K, Set<V>> 的抽象,我们在开发中也肯定经常有统计一个key下有哪些value之类场景。

        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<K,V>转换为Map<V,K>。通过这个数据结构能够满足你需要通过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 cache的升级版框架(性能提升),Caffeine,兼容guava cache api

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

        LoadingCache<String,String> cache = CacheBuilder.newBuilder()
                .maximumSize(100)
                .maximumWeight(1000)
                .weigher(new Weigher<String, String>() {
                    @Override
                    public int weigh(String key, String value) {
                        return key.length();
                    }
                })
                .expireAfterAccess(10, TimeUnit.MINUTES)
                .expireAfterAccess(10, TimeUnit.MINUTES)
                .build(new CacheLoader<String, String>() {
                    @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来生成我们的缓存。

最后

上面只介绍了guava中一小部分的常用工具类,还是很建议读者全面了解一下,等遇到需求时,也算是一种解决方案。下面我会贴上guava的wiki链接,基本常用的都有介绍,有能力的同学也可以通过看源码来深入学习guava。

参考

guava wiki

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270