google-guava工具包常用工具类详解(持续更新)
  o21g2AkC8e2r 2023年12月23日 13 0



文章目录

  • 一、google-guava工具包简介
  • 1、概述
  • 2、引包
  • 二、常用工具类
  • 1、LoadingCache 缓存
  • 2、RateLimiter限流器
  • 3、EventBus事件总线
  • (1)基本使用
  • (2)多消费者
  • (3)DeadEvent
  • (4)AsyncEventBus异步消费
  • 参考资料


一、google-guava工具包简介

1、概述

Guava项目包含我们在基于Java的项目中所依赖的几个Google核心库:集合、缓存、原语支持、并发库、公共注释、字符串处理、I/O等等。这些工具中的每一个都被谷歌员工在生产服务中每天使用,也被许多其他公司广泛使用。

2、引包

想要使用guava很简单,只需要引入依赖包就可以使用了:

<!-- maven -->
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>32.1.3-jre</version>
</dependency>
// gradle
// https://mvnrepository.com/artifact/com.google.guava/guava
implementation group: 'com.google.guava', name: 'guava', version: '32.1.3-jre'

二、常用工具类

1、LoadingCache 缓存

LoadingCache 在实际场景中有着非常广泛的使用,通常情况下如果遇到需要大量时间计算或者缓存值的场景,就应当将值保存到缓存中。LoadingCache 和 ConcurrentMap 类似,但又不尽相同。

最大的不同是 ConcurrentMap 会永久的存储所有的元素值直到他们被显示的移除,但是 LoadingCache 会为了保持内存使用合理会根据配置自动将过期值移除。

import com.google.common.cache.*;

import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

public class CacheMain {

    public static void main(String[] args) {

        RemovalListener<String, String> removalListener = new RemovalListener<String, String>() {
            public void onRemoval(RemovalNotification<String, String> removal) {
                System.out.println("移出了key:" + removal.getKey());
                System.out.println("移出了key:" + removal.getCause());
                System.out.println("移出了key:" + removal.getValue());
            }
        };

        // 定义缓存,key-value的形式
        LoadingCache<String, String> caches = CacheBuilder
                .newBuilder()
                // 缓存最大值,超过最大值会移出
                .maximumSize(10)
                // 自上次读取或写入时间开始,10分钟过期
                .expireAfterAccess(10, TimeUnit.MINUTES)
                // 自上次写入时间开始,10分钟过期
                .expireAfterWrite(10, TimeUnit.MINUTES)
                // 基于权重驱逐key
                .maximumWeight(100000)
                .weigher(new Weigher<String, String>() {
                    public int weigh(String k, String v) {
                        // 获取权重
                        return v.getBytes().length;
                    }
                })
                // 设置移出监听器(同步的,高并发可能会阻塞)
                .removalListener(removalListener)
                // 构造获取缓存数据的方法
                .build(
                        new CacheLoader<String, String>() {
                            public String load(String key) {
                                return createValue(key);
                            }
                        });

        // 需要检查异常
        try {
            System.out.println(caches.get("key"));
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        // 不会检查异常
        caches.getUnchecked("key");
        caches.getIfPresent("key");

        // 使用Callable,将call()方法的返回值作为缓存
        try {
            System.out.println(caches.get("ckey", new Callable<String>() {
                @Override
                public String call() throws Exception {
                    return "Callable";
                }
            })); // Callable
            System.out.println(caches.get("ckey")); // Callable
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        // 直接放缓存
        caches.put("key2", "v2");

        // 将缓存作为ConcurrentMap输出
        System.out.println(caches.asMap());

        // 删除key
        caches.invalidate("key");
        caches.invalidateAll(Arrays.asList("key", "key1"));
        caches.invalidateAll();

        // 清除过期缓存,一般在写入的时候才会清理部分缓存,如果需要,可以手动清除一下
        caches.cleanUp();

    }


    private static String createValue(String key) {
        System.out.println("获取value");
        return "value";
    }
}

2、RateLimiter限流器

常用方法:

/**
* 创建一个稳定输出令牌的RateLimiter,保证了平均每秒不超过permitsPerSecond个请求
* 当请求到来的速度超过了permitsPerSecond,保证每秒只处理permitsPerSecond个请求
* 当这个RateLimiter使用不足(即请求到来速度小于permitsPerSecond),会囤积最多permitsPerSecond个请求
*/
public static RateLimiter create(double permitsPerSecond) 
/**
* 创建一个稳定输出令牌的RateLimiter,保证了平均每秒不超过permitsPerSecond个请求
* 还包含一个热身期(warmup period),热身期内,RateLimiter会平滑的将其释放令牌的速率加大,直到起达到最大速率
* 同样,如果RateLimiter在热身期没有足够的请求(unused),则起速率会逐渐降低到冷却状态
* 
* 设计这个的意图是为了满足那种资源提供方需要热身时间,而不是每次访问都能提供稳定速率的服务的情况(比如带缓存服务,需要定期刷新缓存的)
* 参数warmupPeriod和unit决定了其从冷却状态到达最大速率的时间
*/
public static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit);
//每秒限流 permitsPerSecond,warmupPeriod 则是数据初始预热时间,从第一次acquire 或 tryAcquire 执行开时计算
public static RateLimiter create(double permitsPerSecond, Duration warmupPeriod)
//获取一个令牌,阻塞,返回阻塞时间
public double acquire()
//获取 permits 个令牌,阻塞,返回阻塞时间
public double acquire(int permits)
// 获取一个令牌,如果获取不到立马返回false
public boolean tryAcquire()
//获取一个令牌,超时返回
public boolean tryAcquire(Duration timeout)
获取 permits 个令牌,超时返回
public boolean tryAcquire(int permits, Duration timeout)
RateLimiter limiter = RateLimiter.create(2, 3, TimeUnit.SECONDS);
System.out.println("get one permit cost time: " + limiter.acquire(1) + "s");
System.out.println("get one permit cost time: " + limiter.acquire(1) + "s");
System.out.println("get one permit cost time: " + limiter.acquire(1) + "s");
System.out.println("get one permit cost time: " + limiter.acquire(1) + "s");
System.out.println("get one permit cost time: " + limiter.acquire(1) + "s");
System.out.println("get one permit cost time: " + limiter.acquire(1) + "s");
System.out.println("get one permit cost time: " + limiter.acquire(1) + "s");
System.out.println("get one permit cost time: " + limiter.acquire(1) + "s");
---------------  结果 -------------------------
get one permit cost time: 0.0s
get one permit cost time: 1.331672s
get one permit cost time: 0.998392s
get one permit cost time: 0.666014s
get one permit cost time: 0.498514s
get one permit cost time: 0.498918s
get one permit cost time: 0.499151s
get one permit cost time: 0.488548s

因为RateLimiter滞后处理的,所以第一次无论取多少都是零秒
可以看到前四次的acquire,花了三秒时间去预热数据,在第五次到第八次的acquire耗时趋于平滑

3、EventBus事件总线

EventBus是Guava的事件处理机制,是设计模式中的观察者模式(生产/消费者编程模型)的优雅实现。对于事件监听和发布订阅模式,EventBus是一个非常优雅和简单解决方案,我们不用创建复杂的类和接口层次结构。

(1)基本使用

import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;

public class EventBusMain {


    /**
     * 定义消息实体,EventBus只支持一个消息参数,多个参数需要自己封装为对象
     */
    public static class MyEvent {
        private String message;

        public MyEvent(String message) {
            this.message = message;
        }

        public String getMessage() {
            return message;
        }
    }

    /**
     * 定义消费者
     */
    public static class EventListener {

        // 使用@Subscribe 可以监听消息
        @Subscribe
        public void handler(MyEvent event) {
            System.out.println("消费者接收到消息:" + event.getMessage());
        }
    }


    public static void main(String[] args) {
        // 定义EventBus
        EventBus eventBus = new EventBus("test");
        // 注册消费者
        EventListener listener = new EventListener();
        eventBus.register(listener);
        // 发送消息
        eventBus.post(new MyEvent("消息1"));
        eventBus.post(new MyEvent("消息2"));
        eventBus.post(new MyEvent("消息3"));
    }
}

结果:
消费者接收到消息:消息1
消费者接收到消息:消息2
消费者接收到消息:消息3

(2)多消费者

在消费者的方法中,会自动根据参数的类型,进行消息的处理。

/**
 * 定义消费者
 */
public static class EventListener {

    // 使用@Subscribe 可以监听消息
    @Subscribe
    public void handler1(MyEvent event) {
        System.out.println("消费者接收到MyEvent1消息:" + event.getMessage());
    }

    @Subscribe
    public void handler2(MyEvent event) {
        System.out.println("消费者接收到MyEvent2消息:" + event.getMessage());
    }

    @Subscribe
    public void handler3(String event) {
        System.out.println("消费者接收到String消息:" + event);
    }

    @Subscribe
    public void handler4(Integer event) {
        System.out.println("消费者接收到Integer消息:" + event);
    }

}


public static void main(String[] args) {
    // 定义EventBus
    EventBus eventBus = new EventBus("test");
    // 注册消费者
    EventListener listener = new EventListener();
    eventBus.register(listener);
    // 发送消息
    eventBus.post(new MyEvent("消息1"));
    eventBus.post("消息2");
    eventBus.post(99988);
}

结果:
消费者接收到MyEvent2消息:消息1
消费者接收到MyEvent1消息:消息1
消费者接收到String消息:消息2
消费者接收到Integer消息:99988

(3)DeadEvent

/**
 * 如果没有消息订阅者监听消息, EventBus将发送DeadEvent消息
 */
public static class DeadEventListener {

    // 消息类型必须是DeadEvent
    @Subscribe
    public void listen(DeadEvent event) {
        System.out.println(event);
    }
}


public static void main(String[] args) {
    // 定义EventBus
    EventBus eventBus = new EventBus("test");
    // 注册消费者
    EventListener listener = new EventListener();
    eventBus.register(listener);
    eventBus.register(new DeadEventListener()); // 注册DeadEventListener
    // 发送消息
    eventBus.post(new MyEvent("消息1"));
    eventBus.post("消息2");
    eventBus.post(99988);
    eventBus.post(123.12D); // 如果发送的消息,没有消费者,就会到DeadEvent
}

执行结果
消费者接收到MyEvent2消息:消息1
消费者接收到MyEvent1消息:消息1
消费者接收到String消息:消息2
消费者接收到Integer消息:99988
DeadEvent{source=EventBus{test}, event=123.12}

(4)AsyncEventBus异步消费

import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.Subscribe;

import java.util.concurrent.Executors;

public class EventBusMain2 {

    /**
     * 定义消费者
     */
    public static class EventListener {

        // 使用@Subscribe 可以监听消息
        @Subscribe
        public void handler1(String event) {
            System.out.println("消费者1接收到String消息:" + event + ",线程号:" + Thread.currentThread().getId());
        }

        @Subscribe
        public void handler2(String event) {
            System.out.println("消费者2接收到String消息:" + event + ",线程号:" + Thread.currentThread().getId());
        }
    }

    public static void main(String[] args) {
        // 定义EventBus,消费者消费是异步消费
        AsyncEventBus eventBus = new AsyncEventBus("test", Executors.newFixedThreadPool(10));
        // 注册消费者
        EventListener listener = new EventListener();
        eventBus.register(listener);
        // 发送消息
        System.out.println("发送消息,线程号:" + Thread.currentThread().getId());
        eventBus.post("消息2");
    }
}

执行结果:
发送消息,线程号:1
消费者2接收到String消息:消息2,线程号:21
消费者1接收到String消息:消息2,线程号:22

参考资料

官网:https://github.com/google/guava 官方文档:https://github.com/google/guava/wiki


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年12月23日 0

暂无评论

推荐阅读
o21g2AkC8e2r