Glide中GroupedLinkedMap数据结构

39 阅读3分钟

它要解决什么问题

  • 普通 LinkedHashMap 的 access order针对单个元素;Glide 需要的是针对“同一规格的一组对象(一个桶)”做 LRU:例如同尺寸/同 Config 的 Bitmap 一起算一组,整组按“最近被访问过”在 LRU 链里前移,便于从最久未用的那组里回收对象。源码注释就说明了这是“类似 LinkedHashMap,但按而非单个对象计 LRU;get(size) 也算一次访问”。

结构设计(混合结构)

  • Hash 索引:Map<K, LinkedEntry<K,V>> keyToEntry,O(1) 按 Key 找到那一组。

  • 双向链表(按组) :所有组(LinkedEntry)挂在一个哨兵节点 head的环形双向链表上;最新访问的组放到表头,最久未用的组在表尾

  • 组内容器:每个 LinkedEntry 里有 List values,存该 Key 对应的多个可复用对象。

一句话:Map 做定位,双向链做 LRU,Entry 里装一堆同 Key 的对象。

三个核心操作

  1. put(K, V)

    • 若没有该 Key 的组就建一个并挂到链尾(作为“最不常用”起点);已有则复用 Key;随后把 V 加进该组。
  2. get(K)

    • 按 Key 找到组,将该组挪到表头(标记“刚被访问”),并从该组里弹出一个对象返回;如果原先不存在这个 Key,也会先创建空组再移动到表头(方便后续)。
  3. removeLast() (全局淘汰)

    • 表尾往前找第一 个非空的组,弹出其最后一个 V 并返回;如果某组已经空了,就把该组节点从链表中移除并回收 Key(key.offer()) ,继续向前找,直至找到或返回 null。这是跨组的 LRU 淘汰点

链表移动/挂接由 makeHead/makeTail/removeEntry/updateEntry 这几个小函数完成,都是常数时间。

复杂度与特性

  • 查找/插入/移动:均为 O(1) 均摊(Hash 定位 + 双向链指针操作)。

  • 按组 LRU:相比“对每个对象都进 LRU”,减少链表节点数量与指针抖动;淘汰时能直接命中“最久未用的那一组”。

  • 对象回收意识:Key 要实现 Poolable,当组被清理会把 Key 送回对象池(offer()),降低 Key 的 GC 压力。

它在 Glide 里的位置

  • GroupedLinkedMap 不是直接对外的缓存;它封装在策略类里(如 SizeConfigStrategy/AttributeStrategy),这些策略被 LruBitmapPool 使用:把 Bitmap 依规格分桶,并“从最久未用的桶”逐步回收以控制池大小”。LruBitmapPool 的类注释也写明“使用 LruPoolStrategy 分桶,并按最久未用桶做 LRU 淘汰”。

为什么不用LinkedHashMap<K, Deque>?

  • 本质很像,但 Glide 自己实现有三点收益:

    1. 按组访问语义更直观(get(key) 即把整组前移);

    2. 更小的常数开销(自管节点与哨兵,避免 LinkedHashMap 的额外逻辑);

    3. Key 池化(Poolable) 无缝衔接,组空时顺便归还 Key。

小结(记忆版)

  • 目的:为 Bitmap/ObjectPool 提供“分桶 + 组级 LRU”。

  • 结构:Map<Key, Entry> + “Entry 双向链” + “Entry 内 List”。

  • 淘汰:removeLast() 从链尾找第一桶可复用对象,弹出并返回;空桶则移除并回收 Key

  • 场景:被 LruBitmapPool 的策略层使用,用于同规格对象复用与 LRU 回收