java的Guava工具包介绍

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

集合

普通集合

List<String> list = Lists.newArrayList();
Set<String> set = Sets.newHashSet();
Map<String, String> map = Maps.newHashMap();

Set 取交集、并集、差集

HashSet<Integer> setA = Sets.newHashSet(1, 2, 3, 4, 5);
HashSet<Integer> setB = Sets.newHashSet(4, 5, 6, 7, 8);
Sets.SetView<Integer> union = Sets.union(setA, setB);
System.out.println("union:" + union);
Sets.SetView<Integer> difference = Sets.difference(setA, setB);
System.out.println("difference:" + difference);
Sets.SetView<Integer> intersection = Sets.intersection(setA, setB);
System.out.println("intersection:" + intersection);

map 取交集、并集、差集

HashMap<String, Integer> mapA = Maps.newHashMap();
mapA.put("a", 1);
mapA.put("b", 2);
mapA.put("c", 3);
HashMap<String, Integer> mapB = Maps.newHashMap();
mapB.put("b", 20);
mapB.put("c", 3);
mapB.put("d", 4);
MapDifference<String, Integer> differenceMap = Maps.difference(mapA, mapB);
Map<String, MapDifference.ValueDifference<Integer>> entriesDiffering = differenceMap.entriesDiffering();
//左边差集
Map<String, Integer> entriesOnlyLeft = differenceMap.entriesOnlyOnLeft();
//右边差集
Map<String, Integer> entriesOnlyRight = differenceMap.entriesOnlyOnRight();
//交集
Map<String, Integer> entriesInCommon = differenceMap.entriesInCommon();
System.out.println(entriesDiffering);   // {b=(2, 20)}
System.out.println(entriesOnlyLeft);    // {a=1}
System.out.println(entriesOnlyRight);   // {d=4}
System.out.println(entriesInCommon);    // {c=3}

不可变集合(immutable)

不可变集合的特性有:

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

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

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

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

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

有趣的集合

MultiSet: 无序+可重复

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

定义一个 MultiSet 并添加元素:

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

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

set.add(7, 3);

这表示你想添加 3 个 7。

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

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

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

获取某个元素的个数:

int count = set.count(3);

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

Multimap :key 可以重复的 map

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

Multimap<String, String> map = LinkedHashMultimap.create();
map.put("key", "haha");
map.put("key", "haha1");
Collection<String> key = map.get("key");
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

双向 Map

(Bidirectional Map) 键与值都不能重复

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

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

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

双键 map - 超级实用

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

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

工具类

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

  • 数组相关:Lists
  • 集合相关:Sets
  • map 相关:Maps

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

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

List<Integer> list = Lists.newArrayList();
list.add(34);
list.add(64);
list.add(267);
list.add(865);
String result = Joiner.skipNulls().on("-").join(list);
System.out.println(result);
输出:34-64-267-865

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

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

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

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

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

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

将 String 转为 map:

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

Comparator 的实现

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

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

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

示例:

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

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

统计中间代码运行时间

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

Stopwatch stopwatch = Stopwatch.createStarted();
try {
  //TODO 模拟业务逻辑
  Thread.sleep(2000L);
} catch (InterruptedException e) {
  e.printStackTrace();
}
long nanos = stopwatch.elapsed(TimeUnit.SECONDS);
System.out.println(nanos);

Guava Cache - 本地缓存组件

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

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

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

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

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

单机限流工具类 - RateLimiter

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

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

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

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

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

RateLimiter r = RateLimiter.create(5);
while (true) {
  System.out.println("get 1 tokens: " + r.acquire() + "s");
}
输出:
get 1 tokens: 0.0s
get 1 tokens: 0.197059s
get 1 tokens: 0.195338s
get 1 tokens: 0.196918s
get 1 tokens: 0.19955s
get 1 tokens: 0.199062s
get 1 tokens: 0.195589s
get 1 tokens: 0.195061s
......  

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

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

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

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

RateLimiter r = RateLimiter.create(2, 3, TimeUnit.SECONDS);
while (true) {
  System.out.println("get 1 tokens: " + r.acquire(1) + "s");
  System.out.println("get 1 tokens: " + r.acquire(1) + "s");
  System.out.println("end");
}
输出:
get 1 tokens: 0.0s
get 1 tokens: 1.33068s
end
get 1 tokens: 0.995792s
get 1 tokens: 0.662838s
end
get 1 tokens: 0.494775s
get 1 tokens: 0.497293s
end
get 1 tokens: 0.49966s
get 1 tokens: 0.49625s
end

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

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

到此这篇关于java的Guava工具包介绍的文章就到这了,更多相关Guava工具包内容请搜索脚本之家以前的文章或继续浏览下面的相关文章,希望大家以后多多支持脚本之家!

相关文章

  • Java基于直方图应用的相似图片识别实例

    Java基于直方图应用的相似图片识别实例

    这篇文章主要介绍了Java基于直方图应用的相似图片识别实例,是非常实用的技巧,多见于图形里游戏中,需要的朋友可以参考下
    2014-09-09
  • 浅谈spring-boot-rabbitmq动态管理的方法

    浅谈spring-boot-rabbitmq动态管理的方法

    这篇文章主要介绍了浅谈spring-boot-rabbitmq动态管理的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-12-12
  • java中封装JDBC工具类的实例分析

    java中封装JDBC工具类的实例分析

    在本篇内容里小编给各位分享了一篇关于java中封装JDBC工具类的实例分析,对此有兴趣的朋友们可以学习下。
    2021-03-03
  • 利用Socket.io 实现消息实时推送功能

    利用Socket.io 实现消息实时推送功能

    这篇文章主要介绍了利用Socket.io 实现消息实时推送功能,需要的朋友可以参考下
    2017-12-12
  • Java JDBC基本使用方法详解

    Java JDBC基本使用方法详解

    这篇文章主要介绍了Java JDBC基本使用方法,结合实例形式详细分析了java JDBC基本原理、用法及操作注意事项,需要的朋友可以参考下
    2020-04-04
  • SpringBoot中的声明式事务详解

    SpringBoot中的声明式事务详解

    这篇文章主要介绍了SpringBoot中的声明式事务详解,Spring采用统一的机制来处理不同的数据访问技术的事务, Spring的事务提供一个PlatformTransactionManager的接口,不同的数据访问技术使用不同的接口实现,需要的朋友可以参考下
    2023-08-08
  • spring boot 使用Mybatis-plus查询方法解析

    spring boot 使用Mybatis-plus查询方法解析

    这篇文章主要介绍了spring boot 使用Mybatis-plus查询方法解析,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-09-09
  • java如何根据时间戳生成有序ID

    java如何根据时间戳生成有序ID

    这篇文章主要介绍了java如何根据时间戳生成有序ID问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-04-04
  • Spring Boot + FreeMarker 实现动态Word文档导出功能

    Spring Boot + FreeMarker 实现动态Word文档导出功能

    Spring Boot与FreeMarker的组合,为开发者提供了一个强大的平台,可以轻松实现动态Word文档的导出,本文将指导你如何使用Spring Boot与FreeMarker模板引擎,创建一个简单的应用,用于根据数据库数据动态生成Word文档并下载,感兴趣的朋友一起看看吧
    2024-06-06
  • 一文带你回顾Java中的垃圾回收机制

    一文带你回顾Java中的垃圾回收机制

    这篇文章主要给大家介绍了关于Java中垃圾回收机制的相关资料, Java 程序,内存是托管于 JVM 的,即对象的创建和内存的回收都是由 JVM 自行完成的,开发人员是无权干涉的,只能尽量去优化,需要的朋友可以参考下
    2021-08-08

最新评论