Glide 缓存核心 GroupedLinkedMap 相关笔记

12 阅读4分钟

GroupedLinkedMap 是 Glide 为实现其 BitmapPool 中 LRU(最近最少使用)逻辑而专门设计的高效双层哈希链表数据结构。它巧妙地解决了两个核心问题:按Bitmap规格快速分组查找组内高效的LRU管理

🧱 核心结构设计

GroupedLinkedMap 的内部结构是一个经典的 “哈希表+双链表” 组合:

// 概念结构示意(非实际代码)
public class GroupedLinkedMap<K, V> {
    // 第一层:哈希映射,用于快速查找某个规格对应的Bitmap链表
    private final Map<K, LinkedEntry<K, V>> keyToEntry = new HashMap<>();

    // 第二层:双链表,用于维护所有组的全局LRU顺序
    private final LinkedEntry<K, V> head = new LinkedEntry<>();
}
  • keyToEntry (HashMap):作为索引层。键(Key)是 SizeConfigStrategy 为Bitmap规格(大小、格式)生成的标识符(例如“4096x4096_ARGB_8888”),值(Value)是一个 LinkedEntry 节点,代表一组相同规格的Bitmap集合。
  • head (双链表头节点):作为顺序层。所有 LinkedEntry 节点通过 prevnext 指针,串联成一个按访问时间排序的双向循环链表

🔄 工作流程:存取如何发生

理解它如何运作的最佳方式是跟踪一次完整的Bitmap存取过程。

1. 存入一个Bitmap(put 方法)

假设我们要将一个 100x100 的 ARGB_8888 Bitmap 放入池中。

  • 步骤A:生成KeySizeConfigStrategy 会为这个Bitmap计算出其对应的Key(例如,规格标识)。
  • 步骤B:查找或创建组GroupedLinkedMap 用这个Key去 keyToEntry 哈希表中查找。如果找到,则定位到对应的 LinkedEntry 节点;如果没找到,则新建一个 LinkedEntry 节点,并放入哈希表。
  • 步骤C:插入并更新顺序。将这个Bitmap添加到对应 LinkedEntry 的内部列表(List<V>)的末尾。更重要的是,在双链表中,将这个 LinkedEntry 节点移动到整个链表的尾部(head.prev。链表尾部代表最近被使用过
  • 总结put 操作的核心是 “定位到组,数据入组,组节点提到最新”

2. 取出一个Bitmap(get 方法)

当需要一个 100x100 的 ARGB_8888 Bitmap 时:

  • 步骤A:生成Key。用同样的逻辑生成请求的Key。
  • 步骤B:查找组。用Key在 keyToEntry 中快速找到对应的 LinkedEntry 节点。
  • 步骤C:从组内获取并更新顺序。从该节点内部列表的末尾(List.size()-1)取出最后一个Bitmap(后进先出,LIFO,效率高)。同样,将这个 LinkedEntry 节点移动到双链表尾部,标记为最近使用。
  • 步骤D:清理空组。如果这个 LinkedEntry 节点内部的列表被取空了,就将这个节点从双链表和哈希表中都移除
  • 总结get 操作的核心是 “快速定位组,组内取最新,提组到最新,空组则清理”

🎯 设计精髓与优势

这种设计有几个精妙之处,恰好满足了 BitmapPool 的需求:

  1. 高效的LRU驱逐:当 BitmapPool 需要释放内存时,LruBitmapPool 会从 GroupedLinkedMap 的双链表头部(head.next 开始移除 LinkedEntry 节点及其内部的Bitmap。链表头部对应的就是最久未被访问的规格组,这完美实现了LRU策略。
  2. 精确的分组管理:通过哈希表按规格分组,避免了遍历所有Bitmap来寻找匹配项,查找和插入的平均时间复杂度接近 O(1)
  3. 内存友好的操作:组内使用 ArrayList 存储,取用末尾元素无需移动数据;只有在组变空时才会移除节点,减少了频繁的哈希表与链表操作。

简单来说,你可以把 GroupedLinkedMap 想象成一个多层的储物柜第一层(哈希表) 告诉你“哪种大小的衣服放哪个区域”,第二层(双链表) 记录了“哪个区域的衣服最久没被拿过”。当你需要找一件衣服时,能快速定位到区域(Key),并从该区域最新放入的那堆里拿一件(LIFO),同时把这个区域标记为“刚用过”。当储物柜满了要清空间时,就去找那个“最久没被用过的区域”整个清掉。

希望这个解释能帮你清晰地理解 GroupedLinkedMap 的巧妙之处。如果你想进一步探讨 SizeConfigStrategy 如何生成Key,或者 LruBitmapPool 如何与它协同完成整个内存管理,我可以继续为你展开。