Glide内存优化之GroupedLinkedMap
文章目录
概述
相信很多人看到GroupedLinkedMap
这个数据结构,觉得一脸懵,因为很少甚至都没有见到过这个数据结构。其实这个数据结构是 Glide
在实现 Bitmap
缓存池时,自己定义的一个数据结构,功能类似我们常用的 HashMap
,但是又和 HashMap
不太一样,个哦能上做了一些修改。
这个数据结构虽然简单,但是它的实现思路和背后设计的原因还是很值得我们学习和研究的,所以今天我们就来看下这个结构。
本文基于
Glide 4.13.1
源码版本学习(后续会持续更新《深入学习Glide的专栏》)
原理
先看这个类的定义,内部有两个变量,一个是双链表的头对象 LinkedEntry
,一个保存数据用的 HashMap
,这里也说明这个数据结构大致的功能和 HashMap
肯定是很像的,如下:
|
|
put
方法
我们看下它的put
方法,通过这个方法来学习它是如何保存数据的。方法内容如下:
|
|
上面的 put
方法的逻辑是:
- 并不是直接把
key-value
存放到HashMap
中,而是把key-LinkedEntry
类型保存到HashMap
中,而这里的LinkedEntry
类是一个对 key 和 value 的封装类型 - 存放数据时,如果具有相同 key,不会替换
HashMap
中的数据,而是取出已经存在的数据,然后把value
值添加到对应的LinkedEntry
类中。这个就是和HashMap
最大的区别,HashMap
每次遇到相同 key 的数据,会替换并返回旧值。
所以接下来,我们看下 LinkedEntry
类的实现
|
|
从上面可以看到 LinkedEntry
的一些特性
- 内部有 next 和 pre 变量,相当于 GroupedLinkedMap 自己维护了一个双链表,按照访问顺序维护着链表,并没有使用 LinkHashMap 来进行实现 LRU 的逻辑。
- 通过看
add
方法,可以知道相同key
的数据,都会用一个数组集合ArrayList
进行保存,不会替换
所以综上,GroupedLinkedMap
保存数据的格式如下图所示:
get
方法
我们再来看下它的get
方法,看他是如何获取数据的。
|
|
get
的逻辑就比较简单了,主要是:
- 到
HashMap
中获取对应 key 的数据,如果没有,就先创建一个记录保存,只是这个记录LinkedEntry
只有key
,没有value
值而已 - 把找到或者生成的
LinkedEntry
放入链表的头部位置 - 返回
LinkedEntry
中数组集合数据的最后一个,如果没有则返回 null
和 LinkedHashMap
的差异
从 GroupedLinkedMap
的实现来看,和我们常用的LinkedHashMap
类很像,但是又有一些差异,具体表现为:
- 内部确实是利用了
HashMap
来实现key-value
的保存功能,但是对于相同 key 的数据处理不一样,GroupedLinkedMap
会保存相同 key 的数据,都存在一个ArrayList
的集合中,但是LinkedHashMap/HashMap
会进行替换 GroupedLinkedMap
具有 LRU 的功能,但是并不是和我们熟知的那样,利用LinkedHashMap
来实现的,而是自己内部实现了一个类似LinkedHashMap
的功能
为什么要自己实现这个数据结构
个人觉得这里之所以没有使用LinkedHashMap
来实现,是因为和它的key有关。我们来看下在Glide图片缓存中的key是什么。因为这个类主要用在Bitmap缓存池中,我们看下其中一个策略的实现,SizeConfigStrategy#put
方法:
|
|
从上面可以知道,Glide缓存Bitmap
,是利用Bitmap
对象的内存sizhe
和config
作为判断依据,如果两者相等,则说明Key
相等。而在我们项目的实际开发中,一般都有一套规范,存在大量尺寸相等,配置相等的图,如果我们利用LinkedHashMap
来实现,那么每种Key只能保存一个,也就是大小配置相等的Bitmap只能保存一份,这是不利于缓存的。如果命中不了缓存的Bitmap
就无法复用,就要重新创建Bitmap对象。
而且在Android 4.4以下,Bitmap
能复用的条件就是宽度
和高度
一样,inSimpleSize
为1,这种情况下如果使用LinkedHashMap
一样有上面说的问题。
所以综上面可能的原因,Glide自己实现了一个LRU的库。
文章作者 卒子行
上次更新 2023-08-05