Java小技巧:创建带缓存的过程

在平常开发中,我们经常遇到这样一类过程:有一定计算量,被频繁调用,但对于任意一个参数,结果是恒定的(换句话说,就是纯函数),为了减少频繁调用的性能开销,我们可能会写出这样的代码:

java 复制代码
public class ExampleUnitTest {

    Map<Integer, String> cacheMap = new HashMap<>();

    public String doTaskWithCache(Integer i) {
        String s = cacheMap.get(i); // 先检查是否有缓存
        if (s == null) {
            s = calculateResult(i);
            cacheMap.put(i, s);  // 计算结果存到缓存里
        }
        return s;
    }
}

但是,这样写的话,每次有需要带缓存的函数,创建缓存以及检查结果是否已缓存的那代码就得重写一遍,想想就有些麻烦,本文就介绍一个简单的工具Memorizer来简化这个过程。

Memorizer 的实现

因为实现代码比较少,我们看实现代码

java 复制代码
public class Memorizer<T, V> {
    public static <T, V> Function<T, V> memorize(Function<T, V> function) {
        Map<T, V> cacheMap = new HashMap<>();
        return (T t) -> Optional.ofNullable(cacheMap.get(t)).orElseGet(() -> {
            V value = function.apply(t);
            cacheMap.put(t, value);
            return value;
        });
    }

    public static <T, V> Function<T, V> weakMemorize(Function<T, V> function) {
        Map<T, V> cacheMap = new WeakHashMap<>();
        return (T t) -> Optional.ofNullable(cacheMap.get(t)).orElseGet(() -> {
            V value = function.apply(t);
            cacheMap.put(t, value);
            return value;
        });
    }
}

这个工具有两个方法,都接受一个单参函数作为参数,并返回一个同类型的单参函数,意思是:传入要缓存的函数,返回该函数的带缓存的版本。

weakMemorize方法返回的函数的缓存可能会被回收,适合那些内存资源紧张或者想减少内存压力的场景,不过这也会导致一定概率的缓存失效的情况。

Memorizer 的使用

我们用``来改写上面的例子,就成了:

java 复制代码
public class ExampleUnitTest {
    private Function<Integer, String> doTaskWithCache = (i) -> calculateResult(i);
}

写在最后

这里只给出了单参数的实现,如果要考虑多参数的缓存,可以尝试用下面这种方式扩展:

java 复制代码
public class Memorizer<T, V> {
    public static <T1, T2, V, K> BiFunction<T1, T2, V> memorize(BiFunction<T1, T2, V> function, BiFunction<T1, T2, K> keyExtractor) {
        Map<K, V> cacheMap = new HashMap<>();
        return (t1, t2) -> {
            K key = keyExtractor.apply(t1, t2);
            return Optional.ofNullable(cacheMap.get(key)).orElseGet(() -> {
                V value = function.apply(t1, t2);
                cacheMap.put(key, value);
                return value;
            });
        };
    }

    public static <T1, T2, V, K> BiFunction<T1, T2, V> weakMemorize(BiFunction<T1, T2, V> function, BiFunction<T1, T2, K> keyExtractor) {
        Map<K, V> cacheMap = new WeakHashMap<>();
        return (t1, t2) -> {
            K key = keyExtractor.apply(t1, t2);
            return Optional.ofNullable(cacheMap.get(key)).orElseGet(() -> {
                V value = function.apply(t1, t2);
                cacheMap.put(key, value);
                return value;
            });
        };
    }
}
相关推荐
我命由我123453 分钟前
Windows 操作系统 - 启用 D 盘虚拟内存
java·运维·服务器·windows·学习·java-ee·运维开发
墨狂之逸才10 分钟前
Android 工业手持机蓝牙扫描失败:为什么同时需要蓝牙和定位权限
android
xcl092516 分钟前
智慧场馆解决方案系统源码:从架构设计到部署实战
java·spring boot
liangbo728 分钟前
06-JVM垃圾回收器之CMS
java·jvm
黑马程序员毕设30 分钟前
基于Java的仪器管理系统设计与实现
java·开发语言·spring boot·后端·微信小程序
骇客野人43 分钟前
SpringBoot电商购物车、结算、下单、库存方案设计与落地实施步骤
java·spring boot·后端
Android打工仔1 小时前
Kotlin 协程源码解析(七)`delay()` 到底是如何在 `Dispatchers.Main` 上恢复的?
android·kotlin
哪 吒1 小时前
华为OD机试 - 云服务安全策略最优选择 - 深度优先搜索DFS(Java 新系统 200分)
java·华为od·深度优先
律宏阔1 小时前
Android 红外功能实现与红外协议开发笔记:ConsumerIrManager、NEC、PWM、曼彻斯特编码
android
拒绝内耗。1 小时前
程序员学架构(一):一张图看懂 Java 后端架构:一个请求怎样从手机到数据库?
java·智能手机·架构