Flutter 应用加速之本地缓存管理

前言

村里的老人说:“不会写缓存器的码农不是好程序员。

今天我们就来讲讲如何编写一个简单又通用的缓存管理模块。

需求分析

根据以往经验,每一个缓存器,除了要保持被缓存数据/对象之外,还需要同时记录两个与之紧密相关的时间:

  1. 过期时间 expired —— 缓存超过了一定时间,需要更新了,但当前数据仍然有效
  2. 失效时间 deprecated —— 缓存超过了很长时间,当前数据已经无效,不能再用了

在过期时间 expired 之前,缓存数据/对象可直接使用,无需刷新;
超过了过期时间 expired,但未超过失效时间 deprecated,此时缓存数据仍然有效,可以继续使用,但需要委托一个线程去获取最新数据/对象,然后再更新本地缓存;
如果后台线程多次更新失败,当前缓存数据/对象已经严重超时,即超过了 deprecated,此时应该丢弃当前缓存数据/对象,返回空数据/对象给调用者。

模块设计

首先我们设计一个 ```CacheHolder``` 类来保存被缓存数据/对象,以及与之对应的时间信息:

import 'package:object_key/object_key.dart' show Time;/// Holder for cache value with times in seconds
class CacheHolder <V> {CacheHolder(V? cacheValue, double cacheLifeSpan, {double? now}): _value = cacheValue, _life = cacheLifeSpan {now ??= Time.currentTimestamp;_expired = now + cacheLifeSpan;_deprecated = now + cacheLifeSpan * 2;}V? _value;final double _life;      // life span (in seconds)double _expired = 0;     // time to expireddouble _deprecated = 0;  // time to deprecatedV? get value => _value;/// update cache value with current time in secondsvoid update(V? newValue, {double? now}) {_value = newValue;now ??= Time.currentTimestamp;_expired = now + _life;_deprecated = now + _life * 2;}/// check whether cache is alive with current time in secondsbool isAlive({double? now}) {now ??= Time.currentTimestamp;return now < _expired;}/// check whether cache is deprecated with current time in secondsbool isDeprecated({double? now}) {now ??= Time.currentTimestamp;return now > _deprecated;}/// renewal cache with a temporary life span and current time in secondsvoid renewal(double? duration, {double? now}) {duration ??= 120;now ??= Time.currentTimestamp;_expired = now + duration;_deprecated = now + _life * 2;}}

该类提供 update() 和 renew() 两个函数来更新缓存信息,前者为获取到最新数据之后调用以更新数据及时间,后者仅刷新一下时间,用以推迟有效时间;
另外提供两个函数 isAlive() 和 isDeprecated(),分别用于判断是否需要更新,以及当前数据是否应该丢弃。

另外,为使 CacheHolder 能适用于任意类型数据/对象,这里使用了“泛型”类型定义。

缓存池

接下来我们需要设计一个缓冲池 ```CachePool```,用于保存同类型的 ```CacheHolder```:

import 'package:object_key/object_key.dart' show Time;
import 'holder.dart';class CachePair <V> {CachePair(this.value, this.holder);final V? value;final CacheHolder<V> holder;}/// Pool for cache holders with keys
class CachePool <K, V> {final Map<K, CacheHolder<V>> _holderMap = {};Iterable<K> get keys => _holderMap.keys;/// update cache holder for keyCacheHolder<V> update(K key, CacheHolder<V> holder) {_holderMap[key] = holder;return holder;}/// update cache value for key with timestamp in secondsCacheHolder<V> updateValue(K key, V? value, double life, {double? now}) =>update(key, CacheHolder(value, life, now: now));/// erase cache for keyCachePair<V>? erase(K key, {double? now}) {CachePair<V>? old;if (now != null) {// get exists value before erasingold = fetch(key, now: now);}_holderMap.remove(key);return old;}/// fetch cache value & its holderCachePair<V>? fetch(K key, {double? now}) {CacheHolder<V>? holder = _holderMap[key];if (holder == null) {// holder not foundreturn null;} else if (holder.isAlive(now: now)) {return CachePair(holder.value, holder);} else {// holder expiredreturn CachePair(null, holder);}}/// clear expired cache holdersint purge({double? now}) {now ??= Time.currentTimestamp;int count = 0;Iterable allKeys = keys;CacheHolder? holder;for (K key in allKeys) {holder = _holderMap[key];if (holder == null || holder.isDeprecated(now: now)) {// remove expired holders_holderMap.remove(key);++count;}}return count;}}

该缓冲池提供了 3 个接口给应用层使用:

  1. 更新缓存信息;
  2. 删除缓存信息;
  3. 获取缓存信息;

另外还提供一个 purge() 函数给缓存管理器调用,以清除已失效的 CacheHolder。

缓存管理器

最后,我们还需要设计一个缓存管理器 ```CacheManager```,去统一管理所有不同类型的 ```CachePool```:

import 'package:object_key/object_key.dart' show Time;
import 'pool.dart';class CacheManager {factory CacheManager() => _instance;static final CacheManager _instance = CacheManager._internal();CacheManager._internal();final Map<String, dynamic> _poolMap = {};///  Get pool with name////// @param name - pool name/// @param <K>  - key type/// @param <V>  - value type/// @return CachePoolCachePool<K, V> getPool<K, V>(String name) {CachePool<K, V>? pool = _poolMap[name];if (pool == null) {pool = CachePool();_poolMap[name] = pool;}return pool;}///  Purge all pools////// @param now - current timeint purge(double? now) {now ??= Time.currentTimestamp;int count = 0;CachePool? pool;Iterable allKeys = _poolMap.keys;for (var key in allKeys) {pool = _poolMap[key];if (pool != null) {count += pool.purge(now: now);}}return count;}}

我们这个缓存管理包括两个接口:

  1. 一个工厂方法 getPool(),用于获取/创建缓存池;
  2. 一个清除接口 purge(),供系统在适当的时候(例如系统内存不足时)调用以释放缓存空间。

至此,一个简单高效的本地缓存管理模块就写好了,下面我们来看看怎么用。

应用示例

假设我们有一个类 MetaTable,其作用是从数据库或者网络中获取 meta 信息,考虑到 I/O 的时间,以及数据解析为对象所消耗的 CPU 时间等,如果该类信息访问十分频繁,我们就需要为它加上一层缓存管理。

先来看看代码:


class MetaTable implements MetaDBI {@overrideFuture<Meta?> getMeta(ID entity) async {// 从数据库中获取 meta 信息}@overrideFuture<bool> saveMeta(Meta meta, ID entity) async {// 保存 meta 信息到数据库}}class MetaCache extends MetaTable {final CachePool<ID, Meta> _cache = CacheManager().getPool('meta');@overrideFuture<Meta?> getMeta(ID entity) async {CachePair<Meta>? pair;CacheHolder<Meta>? holder;Meta? value;double now = Time.currentTimeSeconds;await lock();try {// 1. check memory cachepair = _cache.fetch(entity, now: now);holder = pair?.holder;value = pair?.value;if (value == null) {if (holder == null) {// not load yet, wait to load} else if (holder.isAlive(now: now)) {// value not existsreturn null;} else {// cache expired, wait to reloadholder.renewal(128, now: now);}// 2. load from databasevalue = await super.getMeta(entity);// update cache_cache.updateValue(entity, value, 36000, now: now);}} finally {unlock();}// OK, return cache nowreturn value;}@overrideFuture<bool> saveMeta(Meta meta, ID entity) async {_cache.updateValue(entity, meta, 36000, now: Time.currentTimeSeconds);return await super.saveMeta(meta, entity);}}

带缓存读数据

当需要读取数据时,先通过 ```_cache.fetch()``` 检查当前缓存池中是否存在有效的值:

如果 (值存在),则 {

    直接返回该值;

}

否则检查 holder;

如果 (holder 存在且未过期),则 {

    说明确实不存在该数据,返回空值;

}

否则调用父类接口获取最新数据;

然后再更新本地缓存。

带缓存写数据

写数据就简单了,只需要在调用父类接口写数据库的同时刷新一下缓存即可。

代码引用

由于我已将这部分代码提交到了 pub.dev,所以在实际应用中,你只需要在项目工程文件 ```pubspec.yaml``` 中添加:

dependencies:

    object_key: ^0.1.1

然后在需要使用的 dart 文件头引入即可:

import 'package:object_key/object_key.dart';

全部源码

/* license: https://mit-license.org**  ObjectKey : Object & Key kits**                               Written in 2023 by Moky <albert.moky@gmail.com>** =============================================================================* The MIT License (MIT)** Copyright (c) 2023 Albert Moky** Permission is hereby granted, free of charge, to any person obtaining a copy* of this software and associated documentation files (the "Software"), to deal* in the Software without restriction, including without limitation the rights* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell* copies of the Software, and to permit persons to whom the Software is* furnished to do so, subject to the following conditions:** The above copyright notice and this permission notice shall be included in all* copies or substantial portions of the Software.** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE* SOFTWARE.* =============================================================================*/
import 'package:object_key/object_key.dart' show Time;/// Holder for cache value with times in seconds
class CacheHolder <V> {CacheHolder(V? cacheValue, double cacheLifeSpan, {double? now}): _value = cacheValue, _life = cacheLifeSpan {now ??= Time.currentTimestamp;_expired = now + cacheLifeSpan;_deprecated = now + cacheLifeSpan * 2;}V? _value;final double _life;      // life span (in seconds)double _expired = 0;     // time to expireddouble _deprecated = 0;  // time to deprecatedV? get value => _value;/// update cache value with current time in secondsvoid update(V? newValue, {double? now}) {_value = newValue;now ??= Time.currentTimestamp;_expired = now + _life;_deprecated = now + _life * 2;}/// check whether cache is alive with current time in secondsbool isAlive({double? now}) {now ??= Time.currentTimestamp;return now < _expired;}/// check whether cache is deprecated with current time in secondsbool isDeprecated({double? now}) {now ??= Time.currentTimestamp;return now > _deprecated;}/// renewal cache with a temporary life span and current time in secondsvoid renewal(double? duration, {double? now}) {duration ??= 120;now ??= Time.currentTimestamp;_expired = now + duration;_deprecated = now + _life * 2;}}class CachePair <V> {CachePair(this.value, this.holder);final V? value;final CacheHolder<V> holder;}/// Pool for cache holders with keys
class CachePool <K, V> {final Map<K, CacheHolder<V>> _holderMap = {};Iterable<K> get keys => _holderMap.keys;/// update cache holder for keyCacheHolder<V> update(K key, CacheHolder<V> holder) {_holderMap[key] = holder;return holder;}/// update cache value for key with timestamp in secondsCacheHolder<V> updateValue(K key, V? value, double life, {double? now}) =>update(key, CacheHolder(value, life, now: now));/// erase cache for keyCachePair<V>? erase(K key, {double? now}) {CachePair<V>? old;if (now != null) {// get exists value before erasingold = fetch(key, now: now);}_holderMap.remove(key);return old;}/// fetch cache value & its holderCachePair<V>? fetch(K key, {double? now}) {CacheHolder<V>? holder = _holderMap[key];if (holder == null) {// holder not foundreturn null;} else if (holder.isAlive(now: now)) {return CachePair(holder.value, holder);} else {// holder expiredreturn CachePair(null, holder);}}/// clear expired cache holdersint purge({double? now}) {now ??= Time.currentTimestamp;int count = 0;Iterable allKeys = keys;CacheHolder? holder;for (K key in allKeys) {holder = _holderMap[key];if (holder == null || holder.isDeprecated(now: now)) {// remove expired holders_holderMap.remove(key);++count;}}return count;}}class CacheManager {factory CacheManager() => _instance;static final CacheManager _instance = CacheManager._internal();CacheManager._internal();final Map<String, dynamic> _poolMap = {};///  Get pool with name////// @param name - pool name/// @param <K>  - key type/// @param <V>  - value type/// @return CachePoolCachePool<K, V> getPool<K, V>(String name) {CachePool<K, V>? pool = _poolMap[name];if (pool == null) {pool = CachePool();_poolMap[name] = pool;}return pool;}///  Purge all pools////// @param now - current timeint purge(double? now) {now ??= Time.currentTimestamp;int count = 0;CachePool? pool;Iterable allKeys = _poolMap.keys;for (var key in allKeys) {pool = _poolMap[key];if (pool != null) {count += pool.purge(now: now);}}return count;}}

GitHub 地址:

https://github.com/moky/ObjectKey/tree/main/object_key/lib/src/mem

结语

这里向大家展示了一个简单高效的本地缓存管理模块,该模块能有效避免重复创建相同对象,同时也可避免内存泄漏等问题。

合理使用该模块,可以令你的应用程序访问数据的平均速度大幅提升,特别是在重复滚动展示大量数据的列表时,能让你的应用体验更加丝滑。

如有其他问题,可以下载登录 Tarsier​​​​​​​ 与我交流(默认通讯录i找 Albert Moky)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/31993.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

uniapp 打包 H5 实现在 uniapp 打包 APP 的 webview 通信

一、前言 遇到 uniapp 打包的 APP 在 webview 内嵌入 uniapp 打包的 H5 页面的需求&#xff0c;并实现通信。本篇主要总结了如何实现并总结遇到的问题&#xff0c;希望可以帮助大家减少负担。 实现需求主要有三个地方需要处理&#xff1a; index.html 的打包配置导入 uni.we…

IOS Swift 从入门到精通:数组,集合,元组,对比,字典,枚举

目录 数组 集合 元组 Arrays vs sets vs tuples 字典 字典默认值 创建空集合 枚举 枚举关联值 枚举原始值 复杂类型&#xff1a;总结 数组 数组是存储为单个值的值的集合。例如&#xff0c;John、Paul、George 和 Ringo 是姓名&#xff0c;但数组可让您将它们分组为…

外贸真相:外贸依然属于高薪行业

转自&#xff1a;外贸人Vicky 大家好&#xff0c;我是你们的外贸博主&#xff0c;一个拥有12年外贸经验的外贸人。今天&#xff0c;我要跟大家深入聊聊如何在这个充满挑战和机遇的行业中实现高薪梦想 首先&#xff0c;让我们从我初入外贸行业的故事开始。那时候&#xff0c;我…

富格林:严格阻挠防备欺诈陷阱

富格林悉知&#xff0c;在投资领域中&#xff0c;现货黄金交易因其保值属性和全球流动性而备受青睐。然而&#xff0c;要想在这一市场中取得成功&#xff0c;投资者不仅需要了解基本的市场知识&#xff0c;更要学会如何阻挠防备欺诈陷阱。以下是一些有助于大家阻挠防备欺诈陷阱…

书生·浦语大模型LagentAgentLego智能体应用搭建 第二期

文章目录 智能体概述智能体的定义智能体组成智能体范式 环境配置Lagent&#xff1a;轻量级智能体框架实战Lagent Web Demo用 Lagent 自定义工具 AgentLego&#xff1a;组装智能体“乐高”直接使用AgentLego作为智能体工具使用 用 AgentLego 自定义工具 智能体概述 智能体的定义…

网络编程之XDP技术的基础eBPF

一、XDP和TC的技术支撑 在前面分析了XDP和TC技术&#xff0c;从它们的细节里可以看出&#xff0c;它们都在调用eBPF的钩子函数。那么eBPF是什么呢&#xff1f;在2021年曾经写过一篇《eBPF介绍》的初级文章&#xff0c;对eBPF做了一个入门级的普及。但是未曾在技术层面上进行展…

win制作git局域网仓库,克隆

仓库目录制作成共享文件 共享目录\USER-20230725LO 然后win使用git克隆\USER-20230725LO\git\wbrj

发布微信小程序需要icp证吗?

微信小程序需要办理ICP许可证吗&#xff1f; 微信小程序需不需要办理ICP许可证&#xff0c;具体要看你的小程序类目是什么&#xff0c;还要看你的小程序具体是做什么的&#xff1f; 根据《互联网信息服务管理办法》 第四条 国家对经营性互联网信息服务实行许可制度&#xff1b…

ffmpeg windows系统详细教程

视频做预览时黑屏&#xff0c;但有声音问题解决方案。 需要将 .mp4编成H.264格式的.mp4 一般上传视频的站点&#xff0c;如YouTube、Vimeo 等&#xff0c;通常会在用户上传视频时自动对视频进行转码&#xff0c;以确保视频能够在各种设备和网络条件下流畅播放。这些网站通常…

【深度学习】python之人工智能应用篇——图像生成

图像生成是计算机视觉和计算机图形学领域的一个重要研究方向&#xff0c;它指的是通过计算机算法和技术生成或合成图像的过程。随着深度学习、生成模型等技术的发展&#xff0c;图像生成领域取得了显著的进步&#xff0c;并在多个应用场景中发挥着重要作用。 概述 图像生成技…

以10位明星为你献上的祝福视频为标题的科普介绍文章-华媒舍

祝福视频的意义和影响 祝福视频是指明星通过录制的视频&#xff0c;向观众表达美好的祝愿和关怀。这种视频以真挚的情感和鼓舞人心的话语&#xff0c;给人们带来了不同寻常的温暖和感动。由于明星的影响力和号召力&#xff0c;他们的祝福视频可以更广泛地传播&#xff0c;让更…

Android进阶:Bitmap的高效加载和Cache

二、Android中的缓存策略 缓存策略在Android中应用广泛。使用缓存可以节省流量、提高效率。 加载图片时&#xff0c;一般会从网络加载&#xff0c;然后缓存在存储设备上&#xff0c;这样下次就不用请求网络了。并且通常也会缓存一份到内存中&#xff0c;这样下次可以直接取内存…

C# 中的隐式和显式类型转换

当你需要转换值的类型时&#xff0c;可以使用类型转换。只有当你要转换的两种类型兼容时&#xff0c;才有可能。 当你收到错误“无法隐式将一种类型转换为另一种类型”时&#xff0c;说明你转换的两种类型不兼容。 int integer; // 声明一个名为 integer 的整型变量 integer …

项目启动 | 盘古信息助力鼎阳科技开启智能制造升级新征程

在全球数字化浪潮不断涌动的背景下&#xff0c;电子信息行业正迎来转型升级的关键阶段。近日&#xff0c;盘古信息与深圳市鼎阳科技股份有限公司&#xff08;简称“鼎阳科技”&#xff0c;股票代码&#xff1a;688112&#xff09;正式启动了IMS数字化智能制造工厂项目&#xff…

OpenFeign服务调用与负载均衡

目录 介绍使用高级特性超时控制重试机制默认HttpClient修改请求/响应报文压缩日志打印功能 相关文献 介绍 官网说明&#xff1a; Feign 是一个声明式 Web 服务客户端。它使编写 Web 服务客户端变得更加容易。要使用 Feign&#xff0c;请创建一个接口并对其进行注释。它具有可…

MacOS|Linux 下搭建LVGL仿真器

0.前置条件&#xff0c;保证电脑已经安装cmake&#xff0c;sdl2. #ubuntu下的SDL2完整所有工具包sudo apt-get install libsdl2-2.0 sudo apt-get install libsdl2-dev apt-get install libsdl2-mixer-dev sudo apt-get install libsdl2-image-dev sudo apt-get install libs…

C++——unordered_map讲解

文章目录 unordered_map讲解1. 引入头文件2. 基本概念3. 声明和初始化4. 基本操作插入元素访问元素删除元素查找元素迭代器 5. 注意事项6. 总结 unordered_map讲解 <unordered_map> 是 C 标准库中的一个头文件&#xff0c;提供了哈希表的实现&#xff0c;即无序关联容器…

超参数调优-通用深度学习篇(上)

文章目录 深度学习超参数调优网格搜索示例一&#xff1a;网格搜索回归模型超参数示例二&#xff1a;Keras网格搜索 随机搜索贝叶斯搜索 超参数调优框架Optuna深度学习超参数优化框架nvidia nemo大模型超参数优化框架 参数调整理论&#xff1a; 黑盒优化&#xff1a;超参数优化…

【Unity导航系统】Navigation组件的概念及其使用示例

Unity中的Navigation组件是一套用于创建和控制导航网格&#xff08;NavMesh&#xff09;的工具&#xff0c;允许游戏对象&#xff08;特别是AI代理&#xff0c;如NavMeshAgent&#xff09;在复杂的3D环境中进行自动寻路。Navigation组件主要包括以下几个方面&#xff1a; Navi…

CSDN低质量分文章自动化获取

1. 背景 最近粉丝终于达到了5K&#xff0c;可是仍然无法通过优质作者申请&#xff0c;原来是平均质量分较低&#xff0c;优化了一些文章后分数提高仍然较慢&#xff0c;所以需要批量获取低质量文章&#xff0c;重点优化 2. 目标效果 3. 核心代码 其中的Cookie可以根据浏览器…