Flutter动态缓存管理插件dart_dyncache的使用
Flutter 动态缓存管理插件 dart_dyncache 的使用
DynCache 是一个用于在 Dart 中处理所有缓存需求的动态和灵活库。
DynCache 旨在存储资源或时间密集型方法(如数据库查询)的结果。当缓存中的元素被移除时,它们会立即被垃圾回收。例如,当调用 set
方法且 length
等于 cacheCapacity
时。
特性
- 有序存储(加权键值对)
- 支持驱逐回调、自定义条目权重和辅助键的缓存实现
- 实现了 FIFO、LRU 和 LFU 缓存
辅助键
在缓存对象时,你可能希望使用两个不同的键来查询同一个对象。在初始化缓存时,你可以选择添加 <code>AuxiliaryKeyManager</code>
,这是一种将一个键映射到另一个键的方法。
给定的 <code>AuxiliaryKeyManager</code>
所有可能的辅助键集不应与所有可能的主要键集相交。主要键和辅助键之间应该有一一对应的关系。根据辅助键和值,应该可以恢复主要键,反之亦然。
使用
基本缓存使用
final cache = FIFODynamicCache<int, String>(10,
storageGenerator: <K, V>(entryWeight) =>
OrderedMap(entryWeight: entryWeight));
cache.set(1, 'val1');
cache.get(1); // 'val1'
cache.get(2); // null
for (int i = 10; i < 20; i++) {
cache.set(i, 'val$i');
}
cache.get(1); // null
一个 LFU 缓存实现
final cache = FIFODynamicCache<int, String>(10,
entryWeight: (key, value, accessWeight, accessesSinceLastHit) => accessWeight - (accessesSinceLastHit / 1000),
storageGenerator: <K, V>(entryWeight) =>
OrderedMap(entryWeight: entryWeight));
辅助键映射
final manager = AuxiliaryKeyManager<String, int, String>(
generateAuxiliaryKeyFromMainKeyAndValue: (mainKey, value) =>
'$mainKey',
generateMainKeyFromAuxiliaryKeyAndValue: (auxiliaryKey, value) =>
int.parse(auxiliaryKey));
final cache = FIFODynamicCache<int, String>(10,
storageGenerator: <K, V>(entryWeight) =>
OrderedMap(entryWeight: entryWeight),
auxiliaryKeyManagers: [manager]);
cache.set(1, 'val1');
cache.get(1); // 'val1'
cache.get('1'); // 'val1'
cache.set('2', 'val2');
cache.get(2); // 'val2'
cache.get('2'); // 'val2'
驱逐回调
void callback(int key, String value) {
print('$key:$value');
}
final cache = FIFODynamicCache<int, String>(10,
storageGenerator: <K, V>(entryWeight) =>
OrderedMap(entryWeight: entryWeight),
onEvict: callback);
cache.set(1, 'val1');
cache.remove(1); // '1:val1'
cache.set(2, 'val2');
for (int i = 3; i < 11; i++) {
cache.set(i, '');
}
// '2:val2'
完整示例
以下是一个完整的示例,展示了如何使用 dart_dyncache 插件。
import 'dart:math';
import 'package:dart_dyncache/dart_dyncache.dart';
void main() async {
// 一个标准的 LFU 缓存
final lfuCache = LFUDynamicCache<int, int>(10,
storageGenerator: <K, V>(p0) => OrderedMap(entryWeight: p0),
onEvict: (p0, p1) {
if (p0 == 7 || p0 == -1) print('removed $p0');
});
// 填充垃圾数据
for (int i = 0; i < 10; i++) {
lfuCache.set(-i, 0);
}
print('0: ${lfuCache.values}');
// 并且它会被替换,被驱逐的项目会调用 `onEvict` 方法(如果提供了的话)
for (int i = 0; i < 10; i++) {
lfuCache.set(i, i);
}
// 进行一些访问
lfuCache.get(5);
lfuCache.get(5);
lfuCache.get(4);
lfuCache.get(4);
lfuCache.get(4);
lfuCache.get(5);
// 移除一个值,这会调用 `onEvict` 回调(如果提供了的话)
lfuCache.remove(7);
print('1: ${lfuCache.values}');
// 被访问过的键值对将继续被缓存,因为它们现在权重更高
for (int i = 0; i < 10; i++) {
lfuCache.set(i + 10, 0);
}
print('2: ${lfuCache.values}');
// 如果我们想要,我们也可以基于元素上次被访问的时间为其添加权重
final lfruCache = SimpleDynamicCache<int, int>(10,
entryWeight: (key, value, accessWeight, accessesSinceLastHit) =>
accessWeight - (accessesSinceLastHit / 10),
storageGenerator: <K, V>(p0) => OrderedMap(entryWeight: p0));
for (int i = 0; i < 10; i++) {
lfruCache.set(i, i);
}
lfruCache.get(5);
lfruCache.get(5);
lfruCache.get(4);
lfruCache.get(4);
lfruCache.get(4);
lfruCache.get(5);
// 如果插入更多键...
for (int i = 0; i < 10; i++) {
lfruCache.set(-i, 0);
}
// 被访问过的键值对仍然存在
print('3: ${lfruCache.values}');
// 但如果继续...
for (int i = 0; i < 20; i++) {
lfruCache.set(-i, 0);
}
// 你可以看到它们的权重降低了
print('4: ${lfruCache.values}');
for (int i = 0; i < 10; i++) {
lfruCache.set(-i, 0);
}
// 经过一段时间后,所有的键值对都消失了
print('5: ${lfruCache.values}');
// 你也可以用 `AsyncBasicDynamicCache` 包装一个 `BasicDynamicCache` 来支持内置的未来支持。
// 你应该只使用 `getAsync` 与 `AsyncBasicDynamicCache`。
// `get` 将会错过所有关联了未来的键。
// `getAsync` 会等待该未来的完成。
final fifoCache = FIFODynamicCache<int, int>(10,
storageGenerator: <K, V>(e) => OrderedMap(entryWeight: e));
final asyncFifoCache = AsyncBasicDynamicCache(fifoCache);
asyncFifoCache.setAsync(1, () async {
await Future.delayed(Duration(milliseconds: 2));
return 1;
}());
// 一个 get 会错过
print('6: ${asyncFifoCache.get(1, throwOnUnsafe: false)}');
// 但一个 asyncGet 会等待结果
print('7: ${await asyncFifoCache.getAsync(1)}');
// 你还可以向一个 `BasicDynamicCache` 添加一个 `AuxiliaryKeyManager`。
// 一个 `AuxiliaryKeyManager` 为每个添加的主要键创建一个辅助键,反之亦然。
// 主要键和辅助键之间应该有一一对应的关系。辅助键集合不应相交。
// 这个管理器将类型为 `int` 的主要键转换为其字符串表示形式。
// 它通过解析字符串来将辅助键转换为主要键。
final manager = AuxiliaryKeyManager<String, int, String>(
generateAuxiliaryKeyFromMainKeyAndValue: (mainKey, value) => '$mainKey',
generateMainKeyFromAuxiliaryKeyAndValue: (auxiliaryKey, value) =>
int.parse(auxiliaryKey));
final lruCache = LRUDynamicCache(10,
storageGenerator: <K, V>(e) => OrderedMap(entryWeight: e),
auxiliaryKeyManagers: [manager]);
// 设置一个主要键...
lruCache.set(1, 'val1');
// 可以通过辅助键查询
print('8: ${lruCache.get('1')}');
// 并设置一个辅助键...
lruCache.set('2', 'val2');
// 可以通过相关的主键查询。
print('9: ${lruCache.get(2)}');
// 一个无效的辅助键仍然会失败。同样,不支持的键类型也会失败。
print('10: ${lruCache.get('3')}, ${lruCache.get(Object())}');
// 不支持的键类型会抛出异常。
// 这包括未处理的类型。
try {
lruCache.set(Object(), 'objectVal');
} catch (e) {
print(e);
}
// 解析错误的键也会抛出异常。
try {
lruCache.set('not a int', 'bad parse');
} catch (e) {
print(e);
}
// 默认情况下,辅助键冲突会抛出错误,但如果你的方法是安全的,你可能希望将 `checkAuxiliaryKeys` 设置为 `false` 以避免不必要的计算
final badManager1 = AuxiliaryKeyManager<int, int, String>(
generateAuxiliaryKeyFromMainKeyAndValue: (mainKey, value) => mainKey,
generateMainKeyFromAuxiliaryKeyAndValue: (auxiliaryKey, value) =>
auxiliaryKey,
id: 'aux keys and main keys are the same set');
final badManager2 = AuxiliaryKeyManager<String, int, String>(
generateAuxiliaryKeyFromMainKeyAndValue: (mainKey, value) => '$mainKey',
generateMainKeyFromAuxiliaryKeyAndValue: (k, v) => int.parse(k),
id: 'int to string 1');
final badManager3 = AuxiliaryKeyManager<String, int, String>(
generateAuxiliaryKeyFromMainKeyAndValue: (mainKey, value) => '$mainKey',
generateMainKeyFromAuxiliaryKeyAndValue: (k, v) => int.parse(k),
id: 'int to string 2');
final badCache1 = LRUDynamicCache(10,
storageGenerator: <K, V>(e) => OrderedMap(entryWeight: e),
auxiliaryKeyManagers: [badManager1]);
final badCache2 = LRUDynamicCache(10,
storageGenerator: <K, V>(e) => OrderedMap(entryWeight: e),
auxiliaryKeyManagers: [badManager2, badManager3]);
try {
badCache1.set(1, 'aux conflicts with main');
} catch (e) {
print(e);
}
try {
badCache2.set('1', 'aux conflicts with aux');
} catch (e) {
print(e);
}
// !!! 注意关于使用辅助键与异步缓存的重要事项 !!!
// 辅助键通常按如下方式工作:
// |----------> +-------+ >--------V |----------> +-------+ >--------V
// [AUXILIARY KEY] | VALUE | [MAIN KEY] | VALUE | [AUXILIARY KEY]
// ^----------< +-------+ <--------| ^----------< +-------+ <--------|
//
// 对于一个辅助键关联其他辅助键,它必须首先转换为主键。
//
// 问题在于,由于有未来,值直到未来完成才知道;当调用 `asyncGet` 时,它只会等待传入的键是否与 `asyncSet` 传入的键相同,否则即使它们是相关的主键或辅助键也会错过。
//
// 通过 `AuxiliaryKeyManager` 构造函数的 `valueNeeded` 参数可以解决此问题。如果管理器转换函数不依赖于值,可以将其设置为 `false`。
final manager1 = AuxiliaryKeyManager<String, int, int>(
generateAuxiliaryKeyFromMainKeyAndValue: (mainKey, value) => '$mainKey',
generateMainKeyFromAuxiliaryKeyAndValue: (auxiliaryKey, value) =>
int.parse(auxiliaryKey),
valueNeeded: false);
final manager2 = AuxiliaryKeyManager<double, int, int>(
generateAuxiliaryKeyFromMainKeyAndValue: (mainKey, value) =>
mainKey + value! / (pow(10, '${value}'.length)),
generateMainKeyFromAuxiliaryKeyAndValue: (auxiliaryKey, value) =>
auxiliaryKey ~/ 1);
final cache = LRUDynamicCache<int, int>(10,
storageGenerator: <K, V>(e) => OrderedMap(entryWeight: e),
auxiliaryKeyManagers: [manager1, manager2]);
final asyncCache = AsyncBasicDynamicCache(cache);
// 这将值 1 设置为主要键 100。
// manager1 将创建一个辅助键 '100' 并不需要值。
// manager2 将创建一个辅助键 101 并需要值。
asyncCache.setAsync(100, () async {
await Future.delayed(Duration(milliseconds: 5));
return 1;
}());
final futures1 = [
asyncCache.getAsync(100),
asyncCache.getAsync('100'),
asyncCache.getAsync(100.1)
];
final results1 = await Future.wait(futures1);
// 主键命中,'100' 键命中因为它不需要值。
// 因为 manager2 需要值,101 错过因为我们无法计算它在不知道值的情况下。
print('11: $results1');
asyncCache.setAsync(200.1, () async {
await Future.delayed(Duration(milliseconds: 5));
return 1;
}());
final futures2 = [
asyncCache.getAsync(200),
asyncCache.getAsync('200'),
asyncCache.getAsync(200.1)
];
// 因为 manager2 需要值,我们无法推导主键。
// 因为我们无法推导主键,我们无法推导 manager1 的辅助键;只有 manager2 的键在知道值之前等待值。
final results2 = await Future.wait(futures2);
print('12: $results2');
// 当然,在未来完成之后,所有键现在都是关联的。
final futures3 = [
asyncCache.getAsync(200),
asyncCache.getAsync('200'),
asyncCache.getAsync(200.1)
];
final results3 = await Future.wait(futures3);
print('13: $results3');
}
// kralverde (c) 2023
更多关于Flutter动态缓存管理插件dart_dyncache的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter动态缓存管理插件dart_dyncache的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,下面是一个关于如何使用 dart_dyncache
插件进行 Flutter 动态缓存管理的代码示例。dart_dyncache
是一个用于在 Flutter 应用中实现动态缓存管理的 Dart 库。它提供了缓存的存储、获取、失效等功能。
首先,确保你已经在 pubspec.yaml
文件中添加了 dart_dyncache
依赖:
dependencies:
flutter:
sdk: flutter
dart_dyncache: ^最新版本号
然后运行 flutter pub get
以获取依赖。
接下来,我们来看一个具体的代码示例,展示如何使用 dart_dyncache
:
import 'package:flutter/material.dart';
import 'package:dart_dyncache/dart_dyncache.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final Dyncache _cache = Dyncache();
@override
void initState() {
super.initState();
// 设置全局缓存配置
_cache.config = CacheConfig(
maxCount: 100, // 最大缓存条数
maxAge: 60, // 缓存有效时间(秒)
);
// 示例:从缓存中获取数据(如果缓存不存在,则模拟一个网络请求)
_fetchData();
}
Future<void> _fetchData() async {
String key = 'example_key';
var cachedData = await _cache.get(key);
if (cachedData == null) {
// 模拟网络请求
String newData = await _simulateNetworkRequest();
// 将数据存入缓存
await _cache.set(key, newData);
// 更新UI(这里简单打印输出)
print('Fetched new data: $newData');
} else {
// 从缓存中获取数据
print('Fetched cached data: $cachedData');
}
}
Future<String> _simulateNetworkRequest() async {
// 模拟网络请求延迟
await Future.delayed(Duration(seconds: 2));
return 'This is fetched data from network';
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Dynamic Cache Management'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
// 每次点击按钮时尝试获取数据
_fetchData();
},
child: Text('Fetch Data'),
),
),
);
}
}
在这个示例中,我们做了以下几件事:
- 配置缓存:在
initState
方法中,我们配置了全局缓存参数,包括最大缓存条数和缓存有效时间。 - 获取数据:在
_fetchData
方法中,我们尝试从缓存中获取数据。如果缓存中没有数据,则模拟一个网络请求并将获取的数据存入缓存。 - 模拟网络请求:
_simulateNetworkRequest
方法模拟了一个网络请求,这里只是简单地使用Future.delayed
来模拟延迟。 - UI更新:在按钮点击事件中调用
_fetchData
方法来尝试获取数据,并打印输出。
这样,通过使用 dart_dyncache
插件,你可以轻松地在 Flutter 应用中实现动态缓存管理,提高数据获取的效率。