Guava LoadingCache不能缓存null值

测试的时候发现项目中的LoadingCache没有刷新,但是明明调用了refresh方法了。后来发现LoadingCache是不支持缓存null值的,如果load回调方法返回null,则在get的时候会抛出异常。

通过几个例子开看这个问题:

public void test_loadNull() {
    LoadingCache<String, String> stringCache = CacheBuilder.newBuilder()
            .maximumSize(10)
            .build(new CacheLoader<String, String>() {
                @Override
                public String load(String s) throws Exception {
                    System.out.println("xx");
                    if (s.equals("hello"))
                        return "world";
                    else
                        return null;
                }
            });

    try {
        System.out.println(stringCache.get("hello"));

        // get触发load,load返回null则抛出异常:
        // com.google.common.cache.CacheLoader$InvalidCacheLoadException: CacheLoader returned null for key other_key.
        System.out.println(stringCache.get("other_key"));
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}

public void test_loadNullWhenRefresh() {
    LoadingCache<String, String> stringCache = CacheBuilder.newBuilder()
            .maximumSize(10)
            .build(new CacheLoader<String, String>() {
                int i = 0;

                @Override
                public String load(String s) throws Exception {
                    if (i == 0) {
                        i  ;
                        return "world";
                    }
                    return null;
                }
            });

    try {
        System.out.println(stringCache.get("hello"));
        System.out.println(stringCache.get("hello"));

        // refresh的时候,如果load函数返回null,则refresh抛出异常:
        // Exception thrown during refresh
        // com.google.common.cache.CacheLoader$InvalidCacheLoadException: CacheLoader returned null for key hello.
        stringCache.refresh("hello");

        System.out.println(stringCache.get("hello"));
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}

public void test_loadNullAfterInvalidate() {
    LoadingCache<String, String> stringCache = CacheBuilder.newBuilder()
            .maximumSize(10)
            .build(new CacheLoader<String, String>() {
                int i = 0;

                @Override
                public String load(String s) throws Exception {
                    if (i == 0) {
                        i  ;
                        return "world";
                    }
                    return null;
                }
            });

    try {
        System.out.println(stringCache.get("hello"));
        System.out.println(stringCache.get("hello"));

        // invalidate不会触发load
        stringCache.invalidate("hello");

        // invalidate后,再次get,触发load,抛出异常:
        // com.google.common.cache.CacheLoader$InvalidCacheLoadException: CacheLoader returned null for key hello.
        System.out.println(stringCache.get("hello"));
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}

public void test_loadThrowException() {
    LoadingCache<String, String> stringCache = CacheBuilder.newBuilder()
            .maximumSize(10)
            .build(new CacheLoader<String, String>() {
                @Override
                public String load(String s) throws Exception {
                    if (s.equals("hello"))
                        return "world";
                    else
                        throw new IllegalArgumentException("only_hello");
                }
            });

    try {
        System.out.println(stringCache.get("hello"));

        // get触发load,load抛出异常,get也会抛出封装后的异常:
        // com.google.common.util.concurrent.UncheckedExecutionException: java.lang.IllegalArgumentException: only_hello
        System.out.println(stringCache.get("other_key"));
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}

所以如果你需要缓存“空”值,推荐的做法是使用Optional对象来封装结果:

public void test_loadUseOptional() {
    LoadingCache<String, Optional<String>> stringCache = CacheBuilder.newBuilder()
            .maximumSize(10)
            .build(new CacheLoader<String, Optional<String>>() {
                @Override
                public Optional<String> load(String s) throws Exception {
                    if (s.equals("hello"))
                        return Optional.of("world");
                    else
                        return Optional.absent();
                }
            });

    try {
        Optional<String> hello = stringCache.get("hello");
        if(hello.isPresent()) {
            System.out.println(hello.get());
        }

        Optional<String> otherKey = stringCache.get("other_key");
        if(otherKey.isPresent()){
            System.out.println(otherKey.get());
        }
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}

如果你的场景中认为null是不存在的,那么你可以在load函数中抛出异常,这个异常会通过get抛出。

另外还有一个问题,如果是key==null呢?答案是直接抛出java.lang.NullPointerException。Guava对于null是很不待见的。

参考资料

  • [Google Guava] 3-缓存 | 并发编程网 – ifeve.com
    http://ifeve.com/google-guava-cachesexplained/
  • Guava Cache使用笔记 - 代码说-Let code talk - ITeye博客
    http://bylijinnan.iteye.com/blog/2225074
  • guava - How to avoid caching when values are null? - Stack Overflow
    https://stackoverflow.com/questions/13379071/how-to-avoid-caching-when-values-are-null

本文独立博客地址:Guava LoadingCache不能缓存null值 | 木杉的博客

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Guava Cache是Google Guava库中提供的一种本地缓存解决方案。它是一个基于内存的缓存,可以在应用程序内部存储数据,提高应用程序性能。 Guava Cache提供了以下特性: 1. 自动加载:当缓存中不存在某个键的时,可以自动加载生成该。 2. 自动移除:缓存中的某些条目可以在一定时间内自动过期,或者可以使用大小限制来限制缓存中的条目数。 3. 针对不同的缓存数据设置不同的过期时间、存活时间、最大、最小等。 4. 支持同步和异步缓存。 使用Guava Cache非常简单,只需要按以下步骤操作: 1. 引入Guava库。 2. 创建一个CacheBuilder对象,用于配置缓存。 3. 调用build()方法创建一个Cache对象。 4. 使用put()方法向缓存中添加数据。 5. 使用get()方法从缓存中读取数据,如果缓存中不存在该键对应的,则可以自动加载。 6. 使用invalidate()方法从缓存中移除数据。 下面是一个简单的示例: ```java import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; public class GuavaCacheExample { public static void main(String[] args) throws ExecutionException { // 创建一个CacheBuilder对象 CacheBuilder<Object, Object> cacheBuilder = CacheBuilder.newBuilder() .maximumSize(100) // 设置缓存最大条目数 .expireAfterWrite(10, TimeUnit.MINUTES); // 设置缓存过期时间 // 创建一个Cache对象 LoadingCache<String, String> cache = cacheBuilder.build(new CacheLoader<String, String>() { @Override public String load(String key) throws Exception { System.out.println("loading " + key); // 自动加载数据 return "value-" + key; } }); // 添加数据到缓存cache.put("key1", "value1"); cache.put("key2", "value2"); // 从缓存中读取数据 System.out.println(cache.get("key1")); // 输出"value1" System.out.println(cache.get("key3")); // 输出"loading key3"和"value-key3" // 移除缓存中的数据 cache.invalidate("key1"); System.out.println(cache.get("key1", () -> "default")); // 输出"default" } } ``` 在这个示例中,我们使用CacheBuilder对象配置了缓存的最大条目数和过期时间。我们还使用CacheLoader对象创建了一个自动加载的缓存,当缓存中不存在某个键的时,可以自动加载生成该。我们使用put()方法向缓存中添加了两个数据,使用get()方法从缓存中读取了两个数据,并使用invalidate()方法从缓存中移除了一个数据。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值