MemCacheStore
class MemCacheStore extends CacheStore {
final _LruMap _cache;
/// [maxSize]: Total allowed size in bytes (7MB by default).
///
/// [maxEntrySize]: Allowed size per entry in bytes (500KB by default).
///
/// To prevent making this store useless, be sure to
/// respect the following lower-limit rule: maxEntrySize * 5 <= maxSize.
///
MemCacheStore({
int maxSize = 7340032,
int maxEntrySize = 512000,
}) : _cache = _LruMap(maxSize: maxSize, maxEntrySize: maxEntrySize);
}
MemCacheStore 是一个将响应保存在专用内存 LRU(Map) 中的缓存存储器类。
它是 CacheStore 的一个子类,用于实现缓存的存储和检索。缓存存储器用于保存网络请求的响应,以便后续使用。MemCacheStore 将缓存存储在内存中的 LRU(Map) 中,LRU 表示"最近最少使用",它会保持最近使用的缓存项,并在达到一定容量后,淘汰最少使用的缓存项。
这个类的主要属性和方法如下:
_cache: 一个_LruMap类的实例,用于存储缓存项。LRU Map 是一个最近最少使用的缓存策略,它会保持最近使用的缓存项,并在达到一定容量后,淘汰最少使用的缓存项。MemCacheStore()构造函数:可以接收两个可选参数maxSize和maxEntrySize,用于指定缓存的总大小和每个缓存项的最大大小。默认情况下,maxSize是 7340032 字节(7MB),maxEntrySize是 512000 字节(500KB)。
在这个存储器中,缓存项的存储和检索都是非常快速的,因为它是直接存储在内存中的。但是,需要注意的是,内存是有限的,如果缓存的数据量很大,可能会导致内存占用过多。
clean
@override
Future<void> clean({
CachePriority priorityOrBelow = CachePriority.high,
bool staleOnly = false,
}) {
final keys = <String>[];
_cache.entries.forEach((key, resp) {
var shouldRemove = resp.value.priority.index <= priorityOrBelow.index;
shouldRemove &= (staleOnly && resp.value.isStaled()) || !staleOnly;
if (shouldRemove) {
keys.add(key);
}
});
for (var key in keys) {
_cache.remove(key);
}
return Future.value();
}
这是 MemCacheStore 类中的一个方法 clean,用于清理缓存项。
该方法接收两个可选参数:
priorityOrBelow:缓存项的优先级,所有低于等于指定优先级的缓存项都会被清理,默认为CachePriority.high,即清理所有高于指定优先级的缓存项。staleOnly:一个布尔值,表示是否只清理已过期的缓存项,默认为false,即清理所有缓存项。
方法的执行步骤如下:
- 首先,定义一个空列表
keys,用于存储待删除的缓存项的键。 - 然后,遍历
_cache中的每一个缓存项(以键值对的形式)。 - 对于每一个缓存项,检查它的优先级是否低于等于
priorityOrBelow的优先级,以及它是否已过期(如果staleOnly为true),如果满足条件,则将其键加入到keys列表中。 - 最后,遍历
keys列表,将对应的缓存项从_cache中移除,完成清理操作。 - 方法返回一个
Future<void>,表示清理操作的异步完成状态。
delete
@override
Future<void> delete(String key, {bool staleOnly = false}) {
final resp = _cache.entries[key];
if (resp == null) return Future.value();
if (staleOnly && !resp.value.isStaled()) {
return Future.value();
}
_cache.remove(key);
return Future.value();
}
这是 MemCacheStore 类中的一个方法 delete,用于删除指定键的缓存项。
该方法接收两个可选参数:
staleOnly:一个布尔值,表示是否只删除已过期的缓存项,默认为false,即删除指定键的缓存项,不管它是否过期。
方法的执行步骤如下:
-
首先,通过指定的键
key在_cache中查找对应的缓存项。 -
如果找不到该键对应的缓存项,直接返回一个已完成的
Future,表示删除操作完成(实际上什么都没做)。 -
如果找到了缓存项,则根据
staleOnly参数的值来决定是否需要进一步处理:- 如果
staleOnly为true,即只删除已过期的缓存项,并且当前缓存项未过期,直接返回一个已完成的Future,表示删除操作完成(实际上什么都没做)。 - 如果
staleOnly为false,或者当前缓存项已过期,将该键对应的缓存项从_cache中移除,完成删除操作。
- 如果
-
方法返回一个
Future<void>,表示删除操作的异步完成状态。
deleteFromPath
@override
Future<void> deleteFromPath(
RegExp pathPattern, {
Map<String, String?>? queryParams,
}) async {
final responses = await getFromPath(
pathPattern,
queryParams: queryParams,
);
for (final response in responses) {
_cache.remove(response.key);
}
}
这是 MemCacheStore 类中的另一个方法 deleteFromPath,用于根据路径模式和查询参数删除符合条件的缓存项。
该方法接收两个参数:
pathPattern:一个正则表达式,用于匹配缓存项的路径。queryParams:一个可选的查询参数字典,用于进一步过滤匹配的缓存项。
方法的执行步骤如下:
- 首先,通过调用
getFromPath方法来检索符合条件的缓存项。getFromPath方法会返回一个包含匹配缓存项的列表。 - 然后,遍历返回的缓存项列表,逐个从
_cache中移除匹配的缓存项。
exists
@override
Future<bool> exists(String key) {
return Future.value(_cache.entries.containsKey(key));
}
这是 MemCacheStore 类中的 exists 方法,用于检查给定的缓存键是否存在于缓存中。
该方法接收一个参数:
key:要检查的缓存键。
方法的执行步骤如下:
- 首先,通过
_cache.entries属性获取当前缓存中所有的键值对。_cache.entries返回一个Map,其中键是缓存键,值是对应的缓存响应(CacheResponse对象)。 - 然后,通过
containsKey方法检查给定的key是否存在于缓存中。containsKey方法会返回一个布尔值,指示缓存是否包含指定的键。 - 最后,将步骤 2 中的结果包装在
Future.value中,并返回。
get
@override
Future<CacheResponse?> get(String key) async {
return _cache[key];
}
这是 MemCacheStore 类中的 get 方法,用于从缓存中获取指定缓存键对应的缓存响应(CacheResponse 对象)。
该方法接收一个参数:
key:要获取缓存响应的缓存键。
方法的执行步骤如下:
- 首先,通过
_cache[key]表达式从缓存中获取指定缓存键对应的缓存响应。 - 然后,直接将缓存响应返回。
getFromPath
@override
Future<List<CacheResponse>> getFromPath(
RegExp pathPattern, {
Map<String, String?>? queryParams,
}) async {
final responses = <CacheResponse>[];
for (final entry in _cache.entries.entries) {
final resp = entry.value.value;
if (pathExists(resp.url, pathPattern, queryParams: queryParams)) {
responses.add(resp);
}
}
return responses;
}
这是 MemCacheStore 类中的 getFromPath 方法,用于根据路径模式和查询参数从缓存中获取匹配的缓存响应列表。
该方法接收两个参数:
pathPattern:路径匹配的正则表达式。queryParams:查询参数,用于进一步过滤匹配的缓存响应(可选参数)。
方法的执行步骤如下:
- 首先,创建一个空的
List<CacheResponse>类型的列表responses,用于存储匹配的缓存响应。 - 然后,遍历
_cache.entries中的所有缓存项。 - 对于每个缓存项,从中获取缓存响应对象
resp。 - 接着,调用
pathExists方法,判断resp.url是否与指定的路径模式和查询参数匹配。 - 如果匹配成功,则将该缓存响应对象
resp添加到responses列表中。 - 最后,将
responses列表返回。
set
@override
Future<void> set(CacheResponse response) {
_cache.remove(response.key);
_cache[response.key] = response;
return Future.value();
}
这是 MemCacheStore 类中的 set 方法,用于将缓存响应存储到缓存中或更新现有的缓存响应。
该方法接收一个 CacheResponse 对象作为参数,表示要存储或更新的缓存响应。
方法的执行步骤如下:
- 首先,根据传入的缓存响应对象
response的key属性从_cache中移除可能已经存在的同名缓存项。这样可以确保每个缓存响应的key是唯一的。 - 然后,将传入的缓存响应对象
response存储到_cache中,以response.key作为键。 - 最后,返回一个
Future对象,表示缓存操作完成。
close
@override
Future<void> close() {
_cache.clear();
return Future.value();
}
这是 MemCacheStore 类中的 close 方法,用于释放底层资源(如果有的话)并清空缓存。
该方法没有接收任何参数,也没有返回值。它的主要作用是清空 _cache 中的所有缓存项,以便在不再需要缓存数据时释放内存。
方法的执行步骤如下:
- 调用
_cache的clear方法,将所有缓存项从缓存中移除,从而释放内存。 - 返回一个
Future对象,表示操作已完成。
_LruMap
class _LruMap {
_Link? _head;
_Link? _tail;
final entries = <String, _Link>{};
int _currentSize = 0;
final int maxSize;
final int maxEntrySize;
_LruMap({required this.maxSize, required this.maxEntrySize}) {
assert(maxEntrySize != maxSize);
assert(maxEntrySize * 5 <= maxSize);
}
}
这是 _LruMap 类的实现,它是 MemCacheStore 类中用于缓存数据的内部私有类。它实现了一个 LRU(最近最少使用)缓存算法,用于存储缓存项,并在缓存容量达到上限时自动淘汰最不常用的缓存项。
属性和构造函数的解释如下:
_head和_tail:分别表示链表的头和尾节点。链表是用于实现 LRU 算法的数据结构,通过将最近使用的缓存项移动到链表头,实现对最不常用的缓存项的淘汰。entries:一个映射(Map)数据结构,用于存储缓存项的键值对。键是缓存项的键(通常是请求的 URL),值是一个_Link对象,表示缓存项在链表中的位置。_currentSize:当前缓存的大小,以字节为单位。初始值为 0,随着缓存项的添加和淘汰,该值会动态更新。maxSize:缓存的最大容量,以字节为单位。当缓存大小超过该值时,会触发淘汰策略。maxEntrySize:单个缓存项的最大大小,以字节为单位。当单个缓存项的大小超过该值时,会触发淘汰策略。
构造函数接收 maxSize 和 maxEntrySize 两个参数,并进行了一些断言校验,确保 maxEntrySize 不等于 maxSize,并且满足条件 maxEntrySize * 5 <= maxSize,以避免在设置不合理的缓存大小时出现问题。
_LruMap 类实现了一个 LRU 链表结构,并提供了一些方法用于操作缓存项,例如添加、获取、删除等。在 MemCacheStore 中,_LruMap 用于存储缓存项,并在需要淘汰缓存时,按照 LRU 算法的原则,自动删除最不常用的缓存项,以确保缓存大小不超过设定的上限。
CacheResponse? operator [](String key)
/// []:用于访问列表或映射中的元素。
CacheResponse? operator [](String key) {
final entry = entries[key];
if (entry == null) return null;
_moveToHead(entry);
return entry.value;
}
这是 _LruMap 类中的 operator [] 方法的实现。该方法重载了 [] 运算符,使得可以像访问列表或映射一样,通过键值 key 来访问 _LruMap 中缓存项的值 CacheResponse。
方法解释如下:
CacheResponse? operator [](String key) {...}:这里定义了[]运算符的重载方法,接收一个String类型的key作为参数,表示要访问的缓存项的键。final entry = entries[key];:根据给定的key从entries映射中获取缓存项的_Link对象。如果找不到对应的键值,则返回null。if (entry == null) return null;:如果没有找到对应的缓存项,直接返回null,表示缓存中没有该项。_moveToHead(entry);:如果找到了对应的缓存项,调用_moveToHead方法将该项移动到链表头部。这一步是为了实现 LRU 算法,将最近使用的缓存项放在链表头部,以便后续访问时能够更快地找到。return entry.value;:返回缓存项的值CacheResponse。
在 operator [] 方法中,首先查找给定 key 对应的缓存项是否存在。如果存在,将该缓存项移动到链表头部,并返回缓存项的值。如果不存在,则返回 null 表示缓存中没有该项。通过这种方式,可以方便地通过键值来访问缓存项,类似于访问列表或映射的方式。
operator []=(String key, CacheResponse resp)
/// []=:用于设置列表或映射中的元素。
void operator []=(String key, CacheResponse resp) {
final entrySize = _computeSize(resp);
// Entry too heavy, skip it
if (entrySize > maxEntrySize) return;
final entry = _Link(key, resp, entrySize);
entries[key] = entry;
_currentSize += entry.size;
_moveToHead(entry);
while (_currentSize > maxSize) {
assert(_tail != null);
remove(_tail!.key);
}
}
这是 _LruMap 类中的 operator []= 方法的实现。该方法重载了 []= 运算符,用于向 _LruMap 中设置列表或映射的元素,即缓存项 CacheResponse。
方法解释如下:
void operator []=(String key, CacheResponse resp) {...}:这里定义了[]=运算符的重载方法,接收一个String类型的key和一个CacheResponse类型的resp作为参数,表示要设置的缓存项的键和值。final entrySize = _computeSize(resp);:计算要设置的缓存项resp的大小。if (entrySize > maxEntrySize) return;:如果要设置的缓存项的大小超过了maxEntrySize,则跳过不设置该项。这是为了避免将过大的缓存项添加到_LruMap中,保证缓存项大小不会超过设置的最大值。final entry = _Link(key, resp, entrySize);:根据给定的key、resp和计算出的大小entrySize创建一个_Link对象,表示要添加的缓存项。entries[key] = entry;:将新创建的缓存项_Link添加到entries映射中,以key作为键。_currentSize += entry.size;:将当前缓存大小_currentSize增加新缓存项的大小entry.size。_moveToHead(entry);:将新缓存项移动到链表头部,表示该项是最近访问的项,实现 LRU 算法。while (_currentSize > maxSize) {...}:如果当前缓存大小_currentSize超过了maxSize,则进行缓存清理,以保证缓存大小不会超过设置的最大值。remove(_tail!.key);:在进行缓存清理时,移除链表尾部的缓存项,直到当前缓存大小_currentSize不再超过maxSize。
operator []= 方法用于设置缓存项,首先计算要设置的缓存项的大小,如果大小超过了 maxEntrySize,则跳过不设置该项。否则,创建一个新的 _Link 对象表示缓存项,并将该项添加到 entries 映射中。然后,将当前缓存大小 _currentSize 增加新缓存项的大小,并将新缓存项移动到链表头部。最后,如果当前缓存大小 _currentSize 超过了 maxSize,则进行缓存清理,移除链表尾部的缓存项,直到当前缓存大小不再超过 maxSize。通过这种方式,可以有效地管理缓存项,并保持缓存大小在一定范围内。
clear
void clear() {
entries.clear();
_head = null;
_tail = null;
_currentSize = 0;
}
clear() 方法用于清空 _LruMap 中的所有缓存项,即清空 entries 映射,并将链表头部 _head 和尾部 _tail 设置为 null,同时将当前缓存大小 _currentSize 设置为 0。
remove
CacheResponse? remove(String key) {
final entry = entries[key];
if (entry == null) return null;
_currentSize -= entry.size;
entries.remove(key);
if (entry == _tail) {
_tail = entry.next;
_tail?.previous = null;
}
if (entry == _head) {
_head = entry.previous;
_head?.next = null;
}
return entry.value;
}
remove(String key) 方法用于从 _LruMap 中移除指定键的缓存项,并返回被移除的缓存项的值 CacheResponse?。
方法首先检查指定键是否存在于 entries 映射中,如果不存在,则返回 null。如果指定键存在,就从 entries 映射中移除该缓存项,并将当前缓存大小 _currentSize 减去被移除项的大小 entry.size。然后,根据被移除项在链表中的位置,更新链表的头部 _head 和尾部 _tail。如果被移除项是链表的尾部,则将 _tail 指向其下一个节点 entry.next,并将下一个节点的前一个节点指针 previous 设置为 null。如果被移除项是链表的头部,则将 _head 指向其前一个节点 entry.previous,并将前一个节点的下一个节点指针 next 设置为 null。
最后,方法返回被移除缓存项的值 entry.value。这样,通过调用 remove() 方法,可以从 _LruMap 中移除指定键的缓存项,并且链表的头部和尾部也会相应地更新,以保持链表的正确性和缓存项的顺序。
_moveToHead
void _moveToHead(_Link link) {
if (link == _head) return;
if (link == _tail) {
_tail = link.next;
}
if (link.previous != null) {
link.previous!.next = link.next;
}
if (link.next != null) {
link.next!.previous = link.previous;
}
_head?.next = link;
link.previous = _head;
_head = link;
_tail ??= link;
link.next = null;
}
_moveToHead(_Link link) 方法用于将指定的链表节点 _Link 移动到链表头部。这是 LRU (Least Recently Used) 缓存淘汰策略的一部分,用于在访问或更新链表节点时,将最近访问的节点移动到链表头部,以保持缓存项的访问顺序。
方法首先检查指定的链表节点 link 是否已经是链表的头部 _head,如果是,则直接返回,不进行移动操作。
如果指定的链表节点 link 是链表的尾部 _tail,则需要更新 _tail 为 link 的下一个节点 link.next。因为 _tail 存储的是链表中最少访问的节点,而如果将 link 移动到链表头部,它就成为了最近访问的节点,所以 _tail 需要指向其下一个节点。
然后,方法会将 link 从链表中断开,并将其移动到链表头部。具体操作如下:
- 如果
link有前一个节点link.previous,则将前一个节点的下一个节点指针next指向link的下一个节点link.next。这样,就将link从链表中断开,它的前一个节点不再指向它。 - 如果
link有下一个节点link.next,则将下一个节点的前一个节点指针previous指向link的前一个节点link.previous。这样,就将link从链表中断开,它的下一个节点不再指向它。 - 将链表头部
_head的下一个节点指针next指向link,将link的前一个节点指针previous指向当前的链表头部_head。这样,link成为了新的链表头部。 - 更新链表头部
_head为link,并将链表尾部_tail初始化为link(如果当前_tail为空,则_tail会被设置为link)。由于link已经在链表头部,所以它的下一个节点指针next设置为null,表示它是链表的最前面一个节点。
通过调用 _moveToHead() 方法,可以将指定的链表节点移动到链表头部,从而实现 LRU 缓存淘汰策略,保持最近访问的缓存项在链表头部。这样,在缓存项超过最大限制时,可以优先淘汰链表尾部的节点,即最少访问的节点。
_computeSize
int _computeSize(CacheResponse resp) {
var size = resp.content?.length ?? 0;
size += resp.headers?.length ?? 0;
return size;
}
_computeSize(CacheResponse resp) 方法用于计算缓存响应的大小,即响应的内容大小和头部大小的总和。
方法首先定义一个变量 size,并将其初始化为响应的内容大小 resp.content?.length。如果响应的内容不为 null,则返回其长度,否则返回 0。
接下来,将头部的大小 resp.headers?.length 加到 size 上。如果响应的头部不为 null,则返回其长度,否则返回 0。
最后,返回 size,即响应的内容大小和头部大小的总和。
_Link
class _Link implements MapEntry<String, CacheResponse> {
_Link? next;
_Link? previous;
final int size;
@override
final String key;
@override
final CacheResponse value;
_Link(this.key, this.value, this.size);
}
_Link 类实现了 MapEntry<String, CacheResponse> 接口,用于表示一个链表节点,每个节点都包含一个键值对以及链表中的前后节点。
_Link 类包含了四个成员变量:
next:指向链表中下一个节点的指针。previous:指向链表中上一个节点的指针。size:表示该节点对应缓存响应的大小,即缓存响应内容和头部的总大小。key:缓存的键,用于在缓存中唯一标识该节点。value:缓存的响应,即缓存中实际存储的值。