Guava Cache 使用学习

概述

缓存是日常开发中经常应用到的一种技术手段,合理的利用缓存可以极大的改善应用程序的性能。
Guava官方对Cache的描述连接
缓存在各种各样的用例中非常有用。例如,当计算或检索值很昂贵时,您应该考虑使用缓存,并且不止一次需要它在某个输入上的值。
缓存ConcurrentMap要小,但不完全相同。最根本的区别在于一个ConcurrentMap坚持所有添加到它直到他们明确地删除元素。
另一方面,缓存一般配置为自动退出的条目,以限制其内存占用。在某些情况下,一个LoadingCache可以即使不驱逐的条目是有用的,因为它的自动缓存加载。

适用性

你愿意花一些记忆来提高速度。You are willing to spend some memory to improve speed.
您希望Key有时会不止一次被查询。You expect that keys will sometimes get queried more than once.
你的缓存不需要存储更多的数据比什么都适合在。(Guava缓存是本地应用程序的一次运行)。Your cache will not need to store more data than what would fit inRAM. (Guava caches are local to a single run of your application.
它们不将数据存储在文件中,也不存储在外部服务器上。如果这样做不适合您的需要,考虑一个工具像memcached。
实战

maven依赖

<dependency>
   <groupId>com.google.guava</groupId>
   <artifactId>guava</artifactId>
   <version>23.0</version>
</dependency>

加载

在使用缓存前,首先问自己一个问题:有没有合理的默认方法来加载或计算与键关联的值?如果有的话,你应当使用CacheLoader。如果没有,或者你想要覆盖默认的加载运算,同时保留”获取缓存-如果没有-则计算”[get-if-absent-compute]的原子语义,你应该在调用get时传入一个Callable实例。缓存元素也可以通过Cache.put方法直接插入,但自动加载是首选的,因为它可以更容易地推断所有缓存内容的一致性。自动加载就是createCacheLoader中的,当cache.get(key)不存在的时候,会主动的去加载值的信息并放进缓存中去。

Guava Cache有以下两种创建方式:

创建 CacheLoader

LoadingCache是附带CacheLoader构建而成的缓存实现。创建自己的CacheLoader通常只需要简单地实现V load(K key) throws Exception方法。例如,你可以用下面的代码构建LoadingCache:
CacheLoader: 当检索不存在的时候,会自动的加载信息的!

public static com.google.common.cache.CacheLoader<String, Employee> createCacheLoader() {
       return new com.google.common.cache.CacheLoader<String, Employee>() {
           @Override
           public Employee load(String key) throws Exception {
               log.info("加载创建key:" + key);
               return new Employee(key, key + "dept", key + "id");
           }
       };
  }
 LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
               .maximumSize(1000)
               .expireAfterAccess(30L, TimeUnit.MILLISECONDS)
               .build(createCacheLoader());
···
#创建 Callable

所有类型的Guava Cache,不管有没有自动加载功能,都支持get(K, Callable)方法。这个方法返回缓存中相应的值,或者用给定的Callable运算并把结果加入到缓存中。在整个加载方法完成前,缓存项相关的可观察状态都不会更改。这个方法简便地实现了模式”如果有缓存则返回;否则运算、缓存、然后返回”。
···
Cache<Key, Value> cache = CacheBuilder.newBuilder()
    .maximumSize(1000)
    .build(); // look Ma, no CacheLoader
...
try {
  // If the key wasn't in the "easy to compute" group, we need to
  // do things the hard way.
  cache.get(key, new Callable<Value>() {
    @Override
    public Value call() throws AnyException {
      return doThingsTheHardWay(key);
    }
  });
} catch (ExecutionException e) {
  throw new OtherException(e.getCause());
}

显式插入

使用cache.put(key, value)方法可以直接向缓存中插入值,这会直接覆盖掉给定键之前映射的值。使用Cache.asMap()视图提供的任何方法也能修改缓存。但请注意,asMap视图的任何方法都不能保证缓存项被原子地加载到缓存中。进一步说,asMap视图的原子运算在Guava Cache的原子加载范畴之外,所以相比于Cache.asMap().putIfAbsent(K,V),Cache.get(K, Callable) 应该总是优先使用。

缓存回收

1. 大小

    @Test
    public void testSize() throws ExecutionException, InterruptedException {
        LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
                .maximumSize(3)
                .build(CacheLoaderCreatetor.createCacheLoader());
        cache.getUnchecked("wangji");
        cache.getUnchecked("wangwang");
        cache.getUnchecked("old wang");
        assertThat(cache.size(), equalTo(3L));

        cache.getUnchecked("new wang");
        Employee employee = cache.getIfPresent("wangji"); //不会重新加载创建cache
        log.info("最新的把老的替换掉:" + (employee == null ? "是的" : "否"));
        Employee newEmployee = cache.getIfPresent("new wang"); //不会重新加载创建cache
        log.info("获取结果:" + newEmployee);
    }

2. 权重(感觉用的比较少)不同的缓存项有不同的“权重”(weights)——例如,如果你的缓存值,占据完全不同的内存空间,你可以使用CacheBuilder.weigher(Weigher)指定一个权重函数,并且用CacheBuilder.maximumWeight(long)指定最大总重。在权重限定场景中,除了要注意回收也是在重量逼近限定值时就进行了,还要知道重量是在缓存创建时计算的,因此要考虑重量计算的复杂度

@Test
    public void testWeight() throws ExecutionException, InterruptedException {
        //如果不同的高速缓存条目有不同的“权重”,例如,如果你的缓存值有着完全不同的记忆的足迹--你可以用cachebuilder指定一个权重函数。秤(秤)和一个cachebuilder最大缓存量的最大重量(长)
        LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
                .maximumWeight(150)
                .weigher(new Weigher<String, Employee>() {
                    public int weigh(String key, Employee employee) {
                        int weight = employee.getName().length() + employee.getEmpID().length() + employee.getDept().length();//权重计算器
                        log.info("weight is :" + weight);
                        return weight;
                    }
                })
                .build(CacheLoaderCreatetor.createCacheLoader());
        cache.get("wangji");
        log.info("cacheSize:" + cache.size());
        cache.get("wangwang");
        log.info("cacheSize:" + cache.size());
        cache.get("old wang");
        log.info("cacheSize:" + cache.size());
        cache.get("new wang");
        log.info("cacheSize:" + cache.size());
    }
···
##3. 定时回收(Timed Eviction)

Access time => Write/Update/Read
···
/**
   * TTL->time to live
   * Access time => Write/Update/Read
   */
  @Test
  public void testEvictionByAccessTime() throws ExecutionException, InterruptedException {
      LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
              .expireAfterAccess(2, TimeUnit.SECONDS)
              .build(CacheLoaderCreatetor.createCacheLoader());
      cache.getUnchecked("wangji");
      TimeUnit.SECONDS.sleep(3);
      Employee employee = cache.getIfPresent("wangji"); //不会重新加载创建cache
      log.info("被销毁:" + (employee == null ? "是的" : "否"));
      cache.getUnchecked("guava");

      TimeUnit.SECONDS.sleep(2);
      employee = cache.getIfPresent("guava"); //不会重新加载创建cache
      log.info("被销毁:" + (employee == null ? "是的" : "否"));

      TimeUnit.SECONDS.sleep(2);
      employee = cache.getIfPresent("guava"); //不会重新加载创建cache
      log.info("被销毁:" + (employee == null ? "是的" : "否"));

      TimeUnit.SECONDS.sleep(2);
      employee = cache.getIfPresent("guava"); //不会重新加载创建cache
      log.info("被销毁:" + (employee == null ? "是的" : "否"));

      TimeUnit.SECONDS.sleep(2);
      employee = cache.getIfPresent("guava"); //不会重新加载创建cache
      log.info("被销毁:" + (employee == null ? "是的" : "否"));

  }

Write time => write/update

 /**
   * Write time => write/update
   */
  @Test
  public void testEvictionByWriteTime() throws ExecutionException, InterruptedException {
      LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
              .expireAfterWrite(2, TimeUnit.SECONDS)
              .build(CacheLoaderCreatetor.createCacheLoader());
      cache.getUnchecked("guava");
      TimeUnit.SECONDS.sleep(2);
      Employee employee = cache.getIfPresent("guava"); //不会重新加载创建cache
      log.info("被销毁:" + (employee == null ? "是的" : "否"));

      TimeUnit.SECONDS.sleep(2);
      employee = cache.getIfPresent("guava"); //不会重新加载创建cache
      log.info("被销毁:" + (employee == null ? "是的" : "否"));

      cache.put("guava", new Employee("guava", "guava" + "dept", "guava" + "id")); //手动插入
      TimeUnit.SECONDS.sleep(2);
      employee = cache.getIfPresent("guava"); //不会重新加载创建cache
      log.info("被销毁:" + (employee == null ? "是的" : "否"));

      cache.put("guava", new Employee("guava", "guava" + "dept", "guava" + "id"));
      TimeUnit.SECONDS.sleep(2);
      employee = cache.getIfPresent("guava"); //不会重新加载创建cache
      log.info("被销毁:" + (employee == null ? "是的" : "否"));

  }

4. 基于引用的回收(Reference-based Eviction)强(strong)、软(soft)、弱(weak)、虚(phantom)引用-参考

通过使用弱引用的键、或弱引用的值、或软引用的值,Guava Cache可以把缓存设置为允许垃圾回收:
CacheBuilder.weakKeys():使用弱引用存储键。当键没有其它(强或软)引用时,缓存项可以被垃圾回收。因为垃圾回收仅依赖恒等式(==),使用弱引用键的缓存用==而不是equals比较键。
CacheBuilder.weakValues():使用弱引用存储值。当值没有其它(强或软)引用时,缓存项可以被垃圾回收。因为垃圾回收仅依赖恒等式(==),使用弱引用值的缓存用==而不是equals比较值。
CacheBuilder.softValues():使用软引用存储值。软引用只有在响应内存需要时,才按照全局最近最少使用的顺序回收。考虑到使用软引用的性能影响,我们通常建议使用更有性能预测性的缓存大小限定(见上文,基于容量回收)。使用软引用值的缓存同样用==而不是equals比较值。

@Test
  public void testWeakKey() throws ExecutionException, InterruptedException {
      LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
              .weakValues()
              .weakKeys()
              .softValues()
              .build(CacheLoaderCreatetor.createCacheLoader());
      cache.getUnchecked("guava");
      cache.getUnchecked("wangji");

      System.gc();
      TimeUnit.MILLISECONDS.sleep(100);
      Employee employee = cache.getIfPresent("guava"); //不会重新加载创建cache
      log.info("被销毁:" + (employee == null ? "是的" : "否"));
  }

显式清除

任何时候,你都可以显式地清除缓存项,而不是等到它被回收:
个别清除:Cache.invalidate(key)
批量清除:Cache.invalidateAll(keys)
清除所有缓存项:Cache.invalidateAll()

移除监听器

通过CacheBuilder.removalListener(RemovalListener),你可以声明一个监听器,以便缓存项被移除时做一些额外操作。缓存项被移除时,RemovalListener会获取移除通知[RemovalNotification],其中包含移除原因[RemovalCause]、键和值。

@Test
    public void testCacheRemovedNotification() {
        CacheLoader<String, String> loader = CacheLoader.from(String::toUpperCase);
        RemovalListener<String, String> listener = notification ->
        {
            if (notification.wasEvicted()) {
                RemovalCause cause = notification.getCause();
                log.info("remove cacase is :" + cause.toString());
                log.info("key:" + notification.getKey() + "value:" + notification.getValue());
            }
        };
        LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .maximumSize(3)
                .removalListener(listener)// 添加删除监听
                .build(loader);
        cache.getUnchecked("wangji");
        cache.getUnchecked("wangwang");
        cache.getUnchecked("guava");
        cache.getUnchecked("test");
        cache.getUnchecked("test1");
    }

警告:默认情况下,监听器方法是在移除缓存时同步调用的。因为缓存的维护和请求响应通常是同时进行的,代价高昂的监听器方法在同步模式下会拖慢正常的缓存请求。在这种情况下,你可以使用RemovalListeners.asynchronous(RemovalListener, Executor)把监听器装饰为异步操作。

清理什么时候发生?

使用CacheBuilder构建的缓存不会”自动”执行清理和回收工作,也不会在某个缓存项过期后马上清理,也没有诸如此类的清理机制。相反,它会在写操作时顺带做少量的维护工作,或者偶尔在读操作时做——如果写操作实在太少的话。
这样做的原因在于:如果要自动地持续清理缓存,就必须有一个线程,这个线程会和用户操作竞争共享锁。此外,某些环境下线程创建可能受限制,这样CacheBuilder就不可用了。
相反,我们把选择权交到你手里。如果你的缓存是高吞吐的,那就无需担心缓存的维护和清理等工作。如果你的 缓存只会偶尔有写操作,而你又不想清理工作阻碍了读操作,那么可以创建自己的维护线程,以固定的时间间隔调用Cache.cleanUp()。ScheduledExecutorService可以帮助你很好地实现这样的定时调度。

刷新

刷新和回收不太一样。正如LoadingCache.refresh(K)所声明,刷新表示为键加载新值,这个过程可以是异步的。在刷新操作进行时,缓存仍然可以向其他线程返回旧值,而不像回收操作,读缓存的线程必须等待新值加载完成。

 @Test
    public void testCacheRefresh() throws InterruptedException {
        AtomicInteger counter = new AtomicInteger(0);
        CacheLoader<String, Long> cacheLoader = CacheLoader
                .from(k -> {
                    counter.incrementAndGet();
                    log.info("创建 key :" + k);
                    return System.currentTimeMillis();
                });
        LoadingCache<String, Long> cache = CacheBuilder.newBuilder()
                .refreshAfterWrite(2, TimeUnit.SECONDS) // 2s后重新刷新
                .build(cacheLoader);


        Long result1 = cache.getUnchecked("guava");
        TimeUnit.SECONDS.sleep(3);
        Long result2 = cache.getUnchecked("guava");
        log.info(result1.longValue() != result2.longValue() ? "是的" : "否");

    }

CacheBuilder.refreshAfterWrite(long, TimeUnit)可以为缓存增加自动定时刷新功能。和expireAfterWrite相反,refreshAfterWrite通过定时刷新可以让缓存项保持可用,但请注意:缓存项只有在被检索时才会真正刷新(如果CacheLoader.refresh实现为异步,那么检索不会被刷新拖慢)。因此,如果你在缓存上同时声明expireAfterWrite和refreshAfterWrite,缓存并不会因为刷新盲目地定时重置,如果缓存项没有被检索,那刷新就不会真的发生,缓存项在过期时间后也变得可以回收。

操作类

public class Employee {
    private final String name;
    private final String dept;
    private final String empID;

    public Employee(String name, String dept, String empID) {
        this.name = name;
        this.dept = dept;
        this.empID = empID;
    }

    public String getName() {
        return name;
    }

    public String getDept() {
        return dept;
    }

    public String getEmpID() {
        return empID;
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
                .add("Name", this.getName()).add("Department", getDept())
                .add("EmployeeID", this.getEmpID()).toString();
    }

}

加载类

@Slf4j
public class CacheLoaderCreatetor {
   public static com.google.common.cache.CacheLoader<String, Employee> createCacheLoader() {
       return new com.google.common.cache.CacheLoader<String, Employee>() {
           @Override
           public Employee load(String key) throws Exception {
               log.info("加载创建key:" + key);
               return new Employee(key, key + "dept", key + "id");
           }
       };
   }

   public static com.google.common.cache.CacheLoader<String, Employee> createNUllCacheLoader() {
       return new com.google.common.cache.CacheLoader<String, Employee>() {
           @Override
           public Employee load(String key) throws Exception {
               log.info("加载创建key:" + key);
               if (key.equals("null")) {
                   return null;
               }
               return new Employee(key, key + "dept", key + "id");
           }
       };
   }

   public static com.google.common.cache.CacheLoader<String, Optional<Employee>> createNullValueUseOptionalCacheLoader() {
       return new com.google.common.cache.CacheLoader<String, Optional<Employee>>() {
           @Override
           public Optional<Employee> load(String key) throws Exception {
               log.info("加载创建key:" + key);
               if (key.equals("null")) {
                   return Optional.fromNullable(null);
               } else {
                   return Optional.fromNullable( new Employee(key, key + "dept", key + "id"));
               }
           }


       };
   }
}

测试类

package guava.cache;

import com.google.common.base.Optional;
import com.google.common.cache.*;
import lombok.extern.slf4j.Slf4j;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertThat;

/**
 * descrption: guava缓存的使用
 * authohr: wangji
 * date: 2018-02-05 14:05
 */
@Slf4j
public class CacheLoaderTest {
    @Test
    public void testBaisc() throws ExecutionException, InterruptedException {
        LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
                .maximumSize(1000)
                .expireAfterAccess(30L, TimeUnit.MILLISECONDS)
                .build(CacheLoaderCreatetor.createCacheLoader());
        Employee employee = cache.get("wangji");
        log.info("获取结果:" + employee.toString());

        TimeUnit.MILLISECONDS.sleep(31);
        employee = cache.getUnchecked("wangji");
        log.info("重新创建加载信息:" + employee.toString());
    }

    @Test
    public void testSize() throws ExecutionException, InterruptedException {
        LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
                .maximumSize(3)
                .build(CacheLoaderCreatetor.createCacheLoader());
        cache.getUnchecked("wangji");
        cache.getUnchecked("wangwang");
        cache.getUnchecked("old wang");
        assertThat(cache.size(), equalTo(3L));

        cache.getUnchecked("new wang");
        Employee employee = cache.getIfPresent("wangji"); //不会重新加载创建cache
        log.info("最新的把老的替换掉:" + (employee == null ? "是的" : "否"));
        Employee newEmployee = cache.getIfPresent("new wang"); //不会重新加载创建cache
        log.info("获取结果:" + newEmployee);
    }

    @Test
    public void testWeight() throws ExecutionException, InterruptedException {
        //如果不同的高速缓存条目有不同的“权重”,例如,如果你的缓存值有着完全不同的记忆的足迹--你可以用cachebuilder指定一个权重函数。秤(秤)和一个cachebuilder最大缓存量的最大重量(长)
        LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
                .maximumWeight(150)
                .weigher(new Weigher<String, Employee>() {
                    public int weigh(String key, Employee employee) {
                        int weight = employee.getName().length() + employee.getEmpID().length() + employee.getDept().length();//权重计算器
                        log.info("weight is :" + weight);
                        return weight;
                    }
                })
                .build(CacheLoaderCreatetor.createCacheLoader());
        cache.get("wangji");
        log.info("cacheSize:" + cache.size());
        cache.get("wangwang");
        log.info("cacheSize:" + cache.size());
        cache.get("old wang");
        log.info("cacheSize:" + cache.size());
        cache.get("new wang");
        log.info("cacheSize:" + cache.size());
    }

    /**
     * TTL->time to live
     * Access time => Write/Update/Read
     */
    @Test
    public void testEvictionByAccessTime() throws ExecutionException, InterruptedException {
        LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
                .expireAfterAccess(2, TimeUnit.SECONDS)
                .build(CacheLoaderCreatetor.createCacheLoader());
        cache.getUnchecked("wangji");
        TimeUnit.SECONDS.sleep(3);
        Employee employee = cache.getIfPresent("wangji"); //不会重新加载创建cache
        log.info("被销毁:" + (employee == null ? "是的" : "否"));
        cache.getUnchecked("guava");

        TimeUnit.SECONDS.sleep(2);
        employee = cache.getIfPresent("guava"); //不会重新加载创建cache
        log.info("被销毁:" + (employee == null ? "是的" : "否"));

        TimeUnit.SECONDS.sleep(2);
        employee = cache.getIfPresent("guava"); //不会重新加载创建cache
        log.info("被销毁:" + (employee == null ? "是的" : "否"));

        TimeUnit.SECONDS.sleep(2);
        employee = cache.getIfPresent("guava"); //不会重新加载创建cache
        log.info("被销毁:" + (employee == null ? "是的" : "否"));

        TimeUnit.SECONDS.sleep(2);
        employee = cache.getIfPresent("guava"); //不会重新加载创建cache
        log.info("被销毁:" + (employee == null ? "是的" : "否"));

    }

    /**
     * Write time => write/update
     */
    @Test
    public void testEvictionByWriteTime() throws ExecutionException, InterruptedException {
        LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
                .expireAfterWrite(2, TimeUnit.SECONDS)
                .build(CacheLoaderCreatetor.createCacheLoader());
        cache.getUnchecked("guava");
        TimeUnit.SECONDS.sleep(2);
        Employee employee = cache.getIfPresent("guava"); //不会重新加载创建cache
        log.info("被销毁:" + (employee == null ? "是的" : "否"));

        TimeUnit.SECONDS.sleep(2);
        employee = cache.getIfPresent("guava"); //不会重新加载创建cache
        log.info("被销毁:" + (employee == null ? "是的" : "否"));

        cache.put("guava", new Employee("guava", "guava" + "dept", "guava" + "id")); //手动插入
        TimeUnit.SECONDS.sleep(2);
        employee = cache.getIfPresent("guava"); //不会重新加载创建cache
        log.info("被销毁:" + (employee == null ? "是的" : "否"));

        cache.put("guava", new Employee("guava", "guava" + "dept", "guava" + "id"));
        TimeUnit.SECONDS.sleep(2);
        employee = cache.getIfPresent("guava"); //不会重新加载创建cache
        log.info("被销毁:" + (employee == null ? "是的" : "否"));

    }

    /**
     * Strong/soft/weak/Phantom reference
     * https://www.cnblogs.com/daxin/p/5604923.html
     * http://cd826.iteye.com/blog/2036659
     */
    @Test
    public void testWeakKey() throws ExecutionException, InterruptedException {
        LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
                .weakValues()
                .weakKeys()
                .build(CacheLoaderCreatetor.createCacheLoader());
        cache.getUnchecked("guava");
        cache.getUnchecked("wangji");

        System.gc();
        TimeUnit.MILLISECONDS.sleep(100);
        Employee employee = cache.getIfPresent("guava"); //不会重新加载创建cache
        log.info("被销毁:" + (employee == null ? "是的" : "否"));
    }

    @Test
    public void testSoftKey() throws InterruptedException {
        LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
                .expireAfterWrite(2, TimeUnit.SECONDS)
                .softValues()
                .build(CacheLoaderCreatetor.createCacheLoader());
        int i = 0;
        for (; ; ) {
            cache.put("Alex" + i, new Employee("Alex" + 1, "Alex" + 1, "Alex" + 1));
            log.info("The Employee [" + (i++) + "] is store into cache.");
            log.info("cache size" + cache.size());
            TimeUnit.MILLISECONDS.sleep(600);
        }
    }

    @Rule
    public ExpectedException thrown = ExpectedException.none();

    /**
     * 测试为空的情况
     *
     * @throws InterruptedException
     * @throws Exception
     */
    @Test
    public void testLoadNullValue() throws InterruptedException, Exception {
        LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
                .expireAfterWrite(2, TimeUnit.SECONDS)
                .softValues()
                .build(CacheLoaderCreatetor.createNUllCacheLoader());
        thrown.expect(CacheLoader.InvalidCacheLoadException.class);
        try {
            //不存在创建了一个null的value,不被允许的!
            cache.getUnchecked("null");
        } catch (Exception e) {
            log.error("error", e);
            throw e;
        }
    }

    @Test
    public void testLoadNullValueUseOptional() {
        LoadingCache<String, Optional<Employee>> cache = CacheBuilder.newBuilder().build(CacheLoaderCreatetor.createNullValueUseOptionalCacheLoader());
        Optional<Employee> employeeOptional = cache.getUnchecked("guava");
        if (employeeOptional.isPresent()) {
            Employee employee = employeeOptional.get();
            log.info("employee:" + employee.toString());
        }

        Optional<Employee> employeeNull = cache.getUnchecked("null");
        if (!employeeNull.isPresent()) {
            log.info("employee is null");
            Employee def = cache.getUnchecked("null").or(new Employee("default", "default", "default"));
            log.info("employee deful:" + def.toString());
        }
    }

    @Test
    public void testCacheRemovedNotification() {
        CacheLoader<String, String> loader = CacheLoader.from(String::toUpperCase);
        RemovalListener<String, String> listener = notification ->
        {
            if (notification.wasEvicted()) {
                RemovalCause cause = notification.getCause();
                log.info("remove cacase is :" + cause.toString());
                log.info("key:" + notification.getKey() + "value:" + notification.getValue());
            }
        };
        LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .maximumSize(3)
                .removalListener(listener)// 添加删除监听
                .build(loader);
        cache.getUnchecked("wangji");
        cache.getUnchecked("wangwang");
        cache.getUnchecked("guava");
        cache.getUnchecked("test");
        cache.getUnchecked("test1");
    }

    @Test
    public void testCachePreLoad() {
        CacheLoader<String, String> loader = CacheLoader.from(String::toUpperCase);
        LoadingCache<String, String> cache = CacheBuilder.newBuilder().build(loader);
        Map<String, String> preData = new HashMap<String, String>() {
            {
                put("guava", "guava");
                put("guava1", "guava1");
            }
        };
        cache.putAll(preData); //提前插入
        log.info("cache size :" + cache.size());
        log.info("guava:" + cache.getUnchecked("guava"));
    }

    @Test
    public void testCacheRefresh() throws InterruptedException {
        AtomicInteger counter = new AtomicInteger(0);
        CacheLoader<String, Long> cacheLoader = CacheLoader
                .from(k -> {
                    counter.incrementAndGet();
                    log.info("创建 key :" + k);
                    return System.currentTimeMillis();
                });
        LoadingCache<String, Long> cache = CacheBuilder.newBuilder()
                .refreshAfterWrite(2, TimeUnit.SECONDS) // 2s后重新刷新
                .build(cacheLoader);


        Long result1 = cache.getUnchecked("guava");
        TimeUnit.SECONDS.sleep(3);
        Long result2 = cache.getUnchecked("guava");
        log.info(result1.longValue() != result2.longValue() ? "是的" : "否");

    }

}

之前项目中用到过一次,这次在来学习一下

package guava.cache;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * descrption:
 * authohr: wangji
 * date: 2018-02-05 19:55
 */
public class CacheMap {
    private static final Logger log = LoggerFactory.getLogger(CacheMap.class);

    /**
     * @desction: 使用google guava缓存处理
     * @author: wangji
     * @date: 2017/11/22 9:59
     */
    private static Cache<String,Object> cache;
    static {
        cache = CacheBuilder.newBuilder().maximumSize(10000)
                .expireAfterWrite(24, TimeUnit.HOURS)
                .initialCapacity(10)
                .removalListener(new RemovalListener<String, Object>() {
                    @Override
                    public void onRemoval(RemovalNotification<String, Object> rn) {
                        if(log.isInfoEnabled()){
                            log.info("被移除缓存{}:{}",rn.getKey(),rn.getValue());
                        }
                    }
                }).build();
    }

    /**
     * @desction: 获取缓存
     * @author: wangji
     * @date: 2017/11/22 9:50
     */
    public  static Object get(String key){
        return StringUtils.isNotEmpty(key)?cache.getIfPresent(key):null;
    }
    /**
     * @desction: 放入缓存
     * @author: wangji
     * @date: 2017/11/22 9:50
     */
    public static void put(String key,Object value){
        if(StringUtils.isNotEmpty(key) && value !=null){
            cache.put(key,value);
        }
    }
    /**
     * @desction: 移除缓存
     * @author: wangji
     * @date: 2017/11/22 9:50
     */
    public static void remove(String key){
        if(StringUtils.isNotEmpty(key)){
            cache.invalidate(key);
        }
    }
    /**
     * @desction: 批量删除缓存
     * @author: wangji
     * @date: 2017/11/22 9:49
     */
    public static void remove(List<String> keys){
        if(keys !=null && keys.size() >0){
            cache.invalidateAll(keys);
        }
    }
}

作者:汪小哥
来源:CSDN
原文:https://blog.csdn.net/u012881904/article/details/79263787
版权声明:本文为博主原创文章,转载请附上博文链接!

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

推荐阅读更多精彩内容

  • com.google.common.cache 1、背景 缓存,在我们日常开发中是必不可少的一种解决性能问题的方法...
    拾壹北阅读 22,210评论 0 25
  • guava cache简介 为什么会有guava cache 实际开发中,有时候会有一些不常修改,但是经常会被用到...
    黄二的NPE阅读 844评论 0 2
  • 感恩今天早上醒来,持续在神的同在中,感觉手麻麻的心暖暖的,好感恩神的爱,主啊!我本不配得你这样的来爱我,我常常得罪...
    周淑峰阅读 586评论 0 0
  • 我真的很不错,我棒在哪里,我棒在很自信,大家都说自信是迈向成功的第一步,如果没有自信那就没有了人生的目标,所以我...
    陈劭硕四三班阅读 89评论 0 1
  • 哎!说起来我们也算是早恋。那时女孩只是会偷偷地小心翼翼地将心事都藏起来,不会流露出半点喜欢男孩的痕迹。 那天,男孩...
    辰曦向阳阅读 348评论 0 2