Java 开发的同学应该都使用或者听说过 Google 提供的 Guava 工具包。日常使用最多的肯定是集合相关的工具类,还有 Guava cache,除了这些之外 Guava 还提供了很多有用的功能,鉴于日常想用的时候找不到,这里就梳理一下 Guava 中那些好用的工具类,想优化代码的时候不妨过来看看!

集合

普通集合
  1. List<String> list = Lists.newArrayList();
  2. Set<String> set = Sets.newHashSet();
  3. Map<String, String> map = Maps.newHashMap();
Set 取交集、并集、差集
  1. HashSet<Integer> setA = Sets.newHashSet(1, 2, 3, 4, 5);
  2. HashSet<Integer> setB = Sets.newHashSet(4, 5, 6, 7, 8);
  3. Sets.SetView<Integer> union = Sets.union(setA, setB);
  4. System.out.println("union:" + union);
  5. Sets.SetView<Integer> difference = Sets.difference(setA, setB);
  6. System.out.println("difference:" + difference);
  7. Sets.SetView<Integer> intersection = Sets.intersection(setA, setB);
  8. System.out.println("intersection:" + intersection);
map 取交集、并集、差集
  1. HashMap<String, Integer> mapA = Maps.newHashMap();
  2. mapA.put("a", 1);
  3. mapA.put("b", 2);
  4. mapA.put("c", 3);
  5. HashMap<String, Integer> mapB = Maps.newHashMap();
  6. mapB.put("b", 20);
  7. mapB.put("c", 3);
  8. mapB.put("d", 4);
  9. MapDifference<String, Integer> differenceMap = Maps.difference(mapA, mapB);
  10. Map<String, MapDifference.ValueDifference<Integer>> entriesDiffering = differenceMap.entriesDiffering();
  11. //左边差集
  12. Map<String, Integer> entriesOnlyLeft = differenceMap.entriesOnlyOnLeft();
  13. //右边差集
  14. Map<String, Integer> entriesOnlyRight = differenceMap.entriesOnlyOnRight();
  15. //交集
  16. Map<String, Integer> entriesInCommon = differenceMap.entriesInCommon();
  17. System.out.println(entriesDiffering); // {b=(2, 20)}
  18. System.out.println(entriesOnlyLeft); // {a=1}
  19. System.out.println(entriesOnlyRight); // {d=4}
  20. System.out.println(entriesInCommon); // {c=3}
不可变集合(immutable)

不可变集合的特性有:

  • 在多线程操作下,是线程安全的;
  • 所有不可变集合会比可变集合更有效的利用资源;
  • 中途不可改变。

如果你的需求是想创建一个一经初始化后就不能再被改变的集合那么它适合你,因为这些工具类根本就没给你提供修改的 API,这意味着你连犯错误的机会都没有。

  1. ImmutableList<Integer> iList = ImmutableList.of(12,54,87);
  2. ImmutableSet<Integer> iSet = ImmutableSet.of(354,54,764,354);
  3. ImmutableMap<String, Integer> iMap = ImmutableMap.of("k1", 453, "k2", 534);

以上 Immutable 开头的相关集合类的 add、remove 方法都被声明为 deprecated。当你手误点到了这些方法发现是 deprecated 的时候你不会还想着使用吧。

注意:每个Guava immutable集合类的实现都拒绝 null 值。

有趣的集合

MultiSet: 无序+可重复

我们映像中的 Set 应该是无序的,元素不可重复的。MultiSet 颠覆了三观,因为它可以重复。

定义一个 MultiSet 并添加元素:

  1. Multiset<Integer> set = HashMultiset.create();
  2. set.add(3);
  3. set.add(3);
  4. set.add(4);
  5. set.add(5);
  6. set.add(4);

你还可以添加指定个数的同一个元素:

  1. set.add(7, 3);

这表示你想添加 3 个 7。

打印出来的 MultiSet 也很有意思:

  1. [3 x 2, 4 x 2, 5, 7 x 3]

2 个 3,2 个 4, 一个 5, 3 个 7。

获取某个元素的个数:

  1. int count = set.count(3);

这个工具类确实很有意思,帮我们实现了 word count。

Multimap :key 可以重复的 map

这个 map 也很有意思。正常的 map 为了区分不同的 key,它倒好,直接给你来一样的 key 。

  1. Multimap<String, String> map = LinkedHashMultimap.create();
  2. map.put("key", "haha");
  3. map.put("key", "haha1");
  4. Collection<String> key = map.get("key");
  5. System.out.println(key);

使用很简单,用一个 key 可以获取到该 key 对应的两个值,结果用 list 返回。恕我无知,我还没想到这个 map 能够使用的场景。

Multimap 提供了多种实现:

Multimap 实现 key 字段类型 value 字段类型
ArrayListMultimap HashMap ArrayList
HashMultimap HashMap HashSet
LinkedListMultimap LinkedHashMap LinkedList
LinkedHashMultimap LinkedHashMap LinkedHashSet
TreeMultimap TreeMap TreeSet
ImmutableListMultimap ImmutableMap ImmutableList
ImmutableSetMultimap ImmutableMap ImmutableSet
BiMap:双向 Map (Bidirectional Map) 键与值都不能重复

这个稍稍正常一点。如果 key 重复了则会覆盖 key ,如果 value 重复了则会报错。

  1. public static void main(String[] args) {
  2. BiMap<String, String> biMap = HashBiMap.create();
  3. biMap.put("key", "haha");
  4. biMap.put("key", "haha1");
  5. biMap.put("key1", "haha");
  6. String value = biMap.get("key");
  7. System.out.println(value);
  8. }

上面的示例中键 ”key“ 有两个,运行可以发现 get 的时候会用 ”haha1" 覆盖 ”haha“,另外 value 为 ”haha“ 也有两个,你会发现运行上面的代码不会报错,这是因为 ”key“ 对应的 value 已经被 "haha1" 覆盖了。否则是会报错。

双键 map - 超级实用

双键的 map ,我突然感觉我发现了新大陆。比如我有一个业务场景是:根据职位和部门将公司人员区分开来。key 可以用职位 + 部门组成一个字符串,那我们有了双键 map 之后就没这种烦恼。

  1. public static void main(String[] args) {
  2. Table<String, String, List<Object>> tables = HashBasedTable.create();
  3. tables.put("财务部", "总监", Lists.newArrayList());
  4. tables.put("财务部", "职员",Lists.newArrayList());
  5. tables.put("法务部", "助理",Lists.newArrayList());
  6. System.out.println(tables);
  7. }
工具类

JDK里大家耳熟能详的是Collections 这个集合工具类, 提供了一些基础的集合处理转换功能, 但是实际使用里很多需求并不是简单的排序, 或者比较数值大小, 然后 Guava 在此基础上做了许多的改进优化, 可以说是 Guava 最为成熟/流行的模块之一。

数组相关:Lists

集合相关:Sets

map 相关:Maps

连接符(Joiner)和分隔符(Splitter)

Joiner 做为连接符的使用非常简单,下例是将 list 转为使用连接符连接的字符串:

  1. List<Integer> list = Lists.newArrayList();
  2. list.add(34);
  3. list.add(64);
  4. list.add(267);
  5. list.add(865);
  6. String result = Joiner.skipNulls().on("-").join(list);
  7. System.out.println(result);
  8. 输出:34-64-267-865

将 map 转为自定义连接符连接的字符串:

  1. Map<String, Integer> map = Maps.newHashMap();
  2. map.put("key1", 45);
  3. map.put("key2",234);
  4. String result = Joiner.on(",").withKeyValueSeparator("=").join(map);
  5. System.out.println(result);
  6. 输出:
  7. key1=45,key2=234

分隔符 Splitter 的使用也很简单:

  1. String str = "1-2-3-4-5-6";
  2. List<String> list = Splitter.on("-").splitToList(str);
  3. System.out.println(list);
  4. 输出:
  5. [1, 2, 3, 4, 5, 6]

如果字符串中带有空格,还可以先去掉空格:

  1. String str = "1-2-3-4- 5- 6 ";
  2. List<String> list = Splitter.on("-").omitEmptyStrings().trimResults().splitToList(str);
  3. System.out.println(list);

将 String 转为 map:

  1. String str = "key1=54,key2=28";
  2. Map<String,String> map = Splitter.on(",").withKeyValueSeparator("=").split(str);
  3. System.out.println(map);
  4. 输出:
  5. {key1=54, key2=28}

Comparator 的实现

Java 提供了 Comparator 可以用来对对象进行排序。Guava 提供了排序器 Ordering 类封装了很多实用的操作。

Ordering 提供了一些有用的方法:

  1. natural() 对可排序类型做自然排序,如数字按大小,日期按先后排序
  2. usingToString() 按对象的字符串形式做字典排序[lexicographical ordering]
  3. from(Comparator) 把给定的Comparator转化为排序器
  4. reverse() 获取语义相反的排序器
  5. nullsFirst() 使用当前排序器,但额外把null值排到最前面。
  6. nullsLast() 使用当前排序器,但额外把null值排到最后面。
  7. compound(Comparator) 合成另一个比较器,以处理当前排序器中的相等情况。
  8. lexicographical() 基于处理类型T的排序器,返回该类型的可迭代对象Iterable<T>的排序器。
  9. onResultOf(Function) 对集合中元素调用Function,再按返回值用当前排序器排序。

示例:

  1. UserInfo build = UserInfo.builder().uid(234L).gender(1).build();
  2. UserInfo build1 = UserInfo.builder().uid(4354L).gender(0).build();
  3. Ordering<UserInfo> byOrdering = Ordering.natural().nullsFirst().onResultOf((Function<UserInfo, Comparable<Integer>>) input -> input.getGender());
  4. System.out.println(byOrdering.compare(build1, build));

build 的 gender 大于 build1 的,所以返回 -1,反之返回 1。

统计中间代码运行时间

Stopwatch 类提供了时间统计的功能,相当于帮你封装了调用 System.currentTimeMillis() 的逻辑。

  1. Stopwatch stopwatch = Stopwatch.createStarted();
  2. try {
  3. //TODO 模拟业务逻辑
  4. Thread.sleep(2000L);
  5. } catch (InterruptedException e) {
  6. e.printStackTrace();
  7. }
  8. long nanos = stopwatch.elapsed(TimeUnit.SECONDS);
  9. System.out.println(nanos);

Guava Cache - 本地缓存组件

Guava Cache 在日常的使用中非常地频繁,甚至都没有意识到这是第三方提供的工具类而是把它当成了 JDK 自带的实现。

  1. // LoadingCache是Cache的缓存实现
  2. LoadingCache<String, Object> cache = CacheBuilder.newBuilder()
  3. //设置缓存大小
  4. .maximumSize(1000)
  5. //设置到期时间
  6. .expireAfterWrite(10, TimeUnit.MINUTES)
  7. //设置缓存里的值两分钟刷新一次
  8. .refreshAfterWrite(2, TimeUnit.MINUTES)
  9. //开启缓存的统计功能
  10. .recordStats()
  11. //构建缓存
  12. .build(new CacheLoader<String, Object>() {
  13. //此处实现如果根据key找不到value需要去如何获取
  14. @Override
  15. public Object load(String s) throws Exception {
  16. return new Object();
  17. }
  18. //如果批量加载有比反复调用load更优的方法则重写这个方法
  19. @Override
  20. public Map<String, Object> loadAll(Iterable<? extends String> keys) throws Exception {
  21. return super.loadAll(keys);
  22. }
  23. });

设置本地缓存使用 CacheBuilder.newBuilder(),支持设置缓存大小,缓存过期时间,缓存刷新频率等等。如果你想统计缓存的命中率, Guava Cache 也提供了这种能力帮你汇总当前缓存是否有效。

同时缓存如果因为某种原因未自动刷新或者清除,Guava Cache 也支持用户手动调用 API 刷新或者清除缓存。

  1. cache.invalidateAll();//清除所有缓存项
  2. //清理的时机:在写操作时顺带做少量的维护工作,或者偶尔在读操作时做——如果写操作实在太少的话
  3. //如果想自己维护则可以调用Cache.cleanUp();
  4. cache.cleanUp();
  5. //另外有时候需要缓存中的数据做出变化重载一次,这个过程可以异步执行
  6. cache.refresh("key");

单机限流工具类 - RateLimiter

常用的限流算法有 漏桶算法、令牌桶算法。这两种算法各有侧重点:

  • 漏桶算法:漏桶的意思就像一个漏斗一样,水一滴一滴的滴下去,流出是匀速的。当访问量过大的时候这个漏斗就会积水。漏桶算法的实现依赖队列,一个处理器从队头依照固定频率取出数据进行处理。如果请求量过大导致队列堆满那么新来的请求就会被抛弃。漏桶一般按照固定的速率流出。
  • 令牌桶则是存放固定容量的令牌,按照固定速率从桶中取出令牌。初始给桶中添加固定容量令牌,当桶中令牌不够取出的时候则拒绝新的请求。令牌桶不限制取出令牌的速度,只要有令牌就能处理。所以令牌桶允许一定程度的突发,而漏桶主要目的是平滑流出。

RateLimiter 使用了令牌桶算法,提供两种限流的实现方案:

  • 平滑突发限流(SmoothBursty)
  • 平滑预热限流(SmoothWarmingUp)

实现平滑突发限流通过 RateLimiter 提供的静态方法来创建:

  1. RateLimiter r = RateLimiter.create(5);
  2. while (true) {
  3. System.out.println("get 1 tokens: " + r.acquire() + "s");
  4. }
  5. 输出:
  6. get 1 tokens: 0.0s
  7. get 1 tokens: 0.197059s
  8. get 1 tokens: 0.195338s
  9. get 1 tokens: 0.196918s
  10. get 1 tokens: 0.19955s
  11. get 1 tokens: 0.199062s
  12. get 1 tokens: 0.195589s
  13. get 1 tokens: 0.195061s
  14. ......

设置每秒放置的令牌数为 5 个,基本 0.2s 一次符合每秒 5 个的设置。保证每秒不超过 5 个达到了平滑输出的效果。

在没有请求使用令牌桶的时候,令牌会先创建好放在桶中,所以此时如果突然有突发流量进来,由于桶中有足够的令牌可以快速响应。RateLimiter 在没有足够令牌发放时采用滞后处理的方式,前一个请求获取令牌所需等待的时间由下一次请求来承受。

平滑预热限流并不会像平滑突发限流一样先将所有的令牌创建好,它启动后会有一段预热期,逐步将分发频率提升到配置的速率。

比如下面例子创建一个平均分发令牌速率为 2,预热期为 3 分钟。由于设置了预热时间是 3 秒,令牌桶一开始并不会 0.5 秒发一个令牌,而是形成一个平滑线性下降的坡度,频率越来越高,在 3 秒钟之内达到原本设置的频率,以后就以固定的频率输出。这种功能适合系统刚启动需要一点时间来“热身”的场景。

  1. RateLimiter r = RateLimiter.create(2, 3, TimeUnit.SECONDS);
  2. while (true) {
  3. System.out.println("get 1 tokens: " + r.acquire(1) + "s");
  4. System.out.println("get 1 tokens: " + r.acquire(1) + "s");
  5. System.out.println("end");
  6. }
  7. 输出:
  8. get 1 tokens: 0.0s
  9. get 1 tokens: 1.33068s
  10. end
  11. get 1 tokens: 0.995792s
  12. get 1 tokens: 0.662838s
  13. end
  14. get 1 tokens: 0.494775s
  15. get 1 tokens: 0.497293s
  16. end
  17. get 1 tokens: 0.49966s
  18. get 1 tokens: 0.49625s
  19. end

从上面的输出看前面两次获取令牌都很耗时,往后就越来越趋于平稳。

今天给大家介绍的常用的 Guava 工具类就这些,不过 JDK8 开始 Java官方 API 也在完善,比如像字符串相关的功能 JDK也很强大。都是工具,哪个好用就用哪个。

强大的 Guava 工具类的更多相关文章

  1. [Google Guava] 强大的集合工具类:java.util.Collections中未包含的集合工具

    转载的,有问题请联系我 原文链接 译文链接 译者:沈义扬,校对:丁一 尚未完成: Queues, Tables工具类 任何对JDK集合框架有经验的程序员都熟悉和喜欢java.util.Collecti ...

  2. 集合-强大的集合工具类:java.util.Collections中未包含的集合工具

    任何对JDK集合框架有经验的程序员都熟悉和喜欢java.util.Collections包含的工具方法.Guava沿着这些路线提供了更多的工具方法:适用于所有集合的静态方法.这是Guava最流行和成熟 ...

  3. 工具篇:介绍几个好用的guava工具类

    前言 平时我们都会封装一些处理缓存或其他的小工具.但每个人都封装一次,重复造轮子,有点费时间.有没有一些好的工具库推荐-guava.guava是谷歌基于java封装好的开源库,它的性能.实用性,比我们 ...

  4. Google guava工具类的介绍和使用

    概述 工具类 就是封装平常用的方法,不需要你重复造轮子,节省开发人员时间,提高工作效率.谷歌作为大公司,当然会从日常的工作中提取中很多高效率的方法出来.所以就诞生了guava.. 高效设计良好的API ...

  5. Google的Guava工具类splitter和apache stringutil对比 编辑

    一直用的是apache的stringutil工具类,其实google的工具类项目 guava中居然也有字符串的分隔类splitter的,在 http://code.google.com/p/guava ...

  6. Guava工具类

    原文链接:http://blog.csdn.net/mnmlist/article/details/53425865 Objects类 Objects类有几个比较不错的方法,toString.hash ...

  7. 强大的xUtils工具类整理

    xUtils简单介绍 xUtils 包括了非常多有用的android工具. xUtils 支持大文件上传,更全面的http请求协议支持(10种谓词),拥有更加灵活的ORM,很多其它的事件注解支持且不受 ...

  8. Guava工具类学习

    目录 一.介绍 二.Optional类 1.定义 2.java8自带Optional 3.使用 三.Preconditions类 1.定义 2.使用 四.Ordering类 1.定义 2.使用 五.R ...

  9. Guava 工具类之Cache的使用

    一.guava cache 介绍 1.介绍 guava cache是Google guava中提供的一款轻量级的本地缓存组件,其特点是简单.轻便.完善.扩展性强,内存管理机制也相对完善. 2.使用缓存 ...

随机推荐

  1. js in depth & prototype & __proto__

    js in depth & prototype & proto 实例的 proto 与 父类的 prototype,同时指向 父类的构造函数: https://hackernoon.c ...

  2. 01.Numpy数组的基本应用

    数组的创建 数组的访问 数组的合并 数组的分割 数组创建 >>> import numpy as np 创建一维数组 >>> x = np.arange(10) & ...

  3. C#从1970年开始到现在时间的总秒数

    TimeSpan timeSpan = (DateTime.UtcNow - new DateTime(1970, 1, 1)); string timeStamp = ((int)timeSpan. ...

  4. Spring的BeanFactoryPostProcessor接口

    接口简介 BeanFactoryPostProcessor 接口是 Spring 初始化 BeanFactory 时对外暴露的扩展点,Spring IoC 容器允许 BeanFactoryPostPr ...

  5. idea将文件push之后如何回退

  6. 微信小程序:将yyyy-mm-dd格式的日期转换成yyyy-mm-dd hh:mm:ss格式的日期

    代码如下: changeDate1(e) { console.log(e); var date = new Date(e.detail.value); console.log(date); const ...

  7. JS输出为[object object]

    问题描述:在控制台打印时应输出对象,但是却输出[object object] 解决办法:先将数据转换为json格式,然后再转换为json对象 JSON.parse(JSON.stringify(use ...

  8. JavaScript async/await:优点、陷阱及如何使用

    翻译练习 原博客地址:JavaScript async/await: The Good Part, Pitfalls and How to Use ES7中引进的async/await是对JavaSc ...

  9. 如何用Eggjs从零开始开发一个项目(1)

    前言 "纸上得来终觉浅,绝知此事要躬行."虽然node一直在断断续续地学,但总是东一榔头西一榔头的,没有一点系统,所以打算写一个项目来串联一下之前的学习成果. 为什么选择Eggjs ...

  10. C++中tuple类型

    tuple是C++11新标准里的类型.它是一个类似pair类型的模板.pair类型是每个成员变量各自可以是任意类型,但是只能有俩个成员,而tuple与pair不同的是它可以有任意数量的成员.但是每个确 ...