YYCache 源码浅析

文章目录
  1. 1. 一、基本结构
  2. 2. 二、内存缓存
    1. 2.1. 数据结构
    2. 2.2. 添加逻辑
    3. 2.3. 删除逻辑
    4. 2.4. 按照时间删除
    5. 2.5. 按照成本删除
    6. 2.6. YYCache没有使用NSCache的原因
  3. 3. 三、磁盘缓存
    1. 3.1. 磁盘存储结构
    2. 3.2. 添加
    3. 3.3. 删除
    4. 3.4. 删除大小大于size的记录
    5. 3.5. 删除时间早于time的对象
    6. 3.6. 四、YYCache中使用的所有的SQL语句
    7. 3.7. 五、学习到的SQL知识

学习YYCache开源库

一、基本结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
        -------YYCache------
| |
| |
YYMemoryCache YYDiskCache(磁盘缓存的接口层)
|
|
YYKVStorage实现层(小数据SQLite存储、大数据文件存储)
|
|
------------------------
| |
| |
小数据SQLite存储 大数据文件存储,SQLite辅助存储卫星数据,
包括修改时间、访问时间、key等数据,方便查找

二、内存缓存

数据结构

使用双向链表实现了LRU缓存策略,数据结构采用自顶向下的方式说明:

1
2
3
4
5
6
7
@implementation YYMemoryCache {
pthread_mutex_t _lock;
_YYLinkedMap *_lru;
dispatch_queue_t _queue;
}

其中_lru就是LRU的具体结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@interface _YYLinkedMap : NSObject {
@package
CFMutableDictionaryRef _dic; // do not set object directly
NSUInteger _totalCost;
NSUInteger _totalCount;
_YYLinkedMapNode *_head; // MRU, do not change it directly
_YYLinkedMapNode *_tail; // LRU, do not change it directly
BOOL _releaseOnMainThread;
BOOL _releaseAsynchronously;
}

分为两个结构:

一个是hash表_dic用作快速查找,以「Key:_YYLinkedMapNode」的形式存储
一个是双向链表,用_head_tail存储头尾节点,为了实现LRU缓存淘汰算法,头结点插入,尾结点删除
1
2
3
4
5
6
7
8
9
10
11
@interface _YYLinkedMapNode : NSObject {
@package
__unsafe_unretained _YYLinkedMapNode *_prev; // retained by dic
__unsafe_unretained _YYLinkedMapNode *_next; // retained by dic
id _key;
id _value;
NSUInteger _cost;
NSTimeInterval _time;
}

这个是具体的节点信息

添加逻辑

  1. 从_dic中找出节点
  2. 如果节点已经存在,移到链表头(最新访问)
  3. 如果_dic中没有,创建新节点,头插
  4. 最后,如果超过容量,循环删除尾结点

删除逻辑

在_dic中通过key找到节点(_YYLinkedMapNode),在_dic和链表中同时删除

按照时间删除

循环的取出尾结点,如果早于指定时间,删除,直到尾节点时间晚于指定时间。

按照成本删除

如果总成本大于指定的cost,循环的取出尾结点,删除。直到总成本小于cost

YYCache没有使用NSCache的原因

  1. It uses LRU (least-recently-used) to remove objects; NSCache’s eviction method is non-deterministic.
  2. it can be controlled by cost, count and age; NSCache’s limits are imprecise.
  3. It can be configured to automatically evict objects when receive memory warning or app enter background.

三、磁盘缓存

磁盘存储结构

1
2
3
4
5
6
7
8
9
/path/
/manifest.sqlite 存储记录的相关信息
/manifest.sqlite-shm 共享内存使用的?
/manifest.sqlite-wal wal模式恢复数据库使用
/data/ 大数据实际存储位置
/e10adc3949ba59abbe56e057f20f883e
/e10adc3949ba59abbe56e057f20f883e
/trash/ 删除的时候会首先移动到这里
/unused_file_or_folder

缓存对象表结构

1
2
3
4
5
6
7
8
9
10
create table if not exists manifest (
key text,
filename text,
size integer,
inline_data blob,
modification_time integer,
last_access_time integer,
extended_data blob,
primary key(key)
);
1
2
3
4
5
6
7
8
YYKVStorage
|
|-----数据的size小于阈值存储与SQLite中,也就是value直接存储在表中
|
|-----没有限制:SQLite和文件都存储
|
|-----大于阈值:使用文件存储Value,表中只存储卫星数据(修改时间、访问时间、key等),
便于查找、删除

添加

  1. 如果存储方式是文件,将数据写入文件,进入第三步,更新SQLite中的卫星数据
  2. 如果存储类型是SQLite,进入第三步存储数据
  3. 使用SQL语句存储:insert or replace into manifest (key, filename, size, inline_data, modification_time, last_access_time, extended_data) values (?1, ?2, ?3, ?4, ?5, ?6, ?7);如果是存储卫星数据inline_data为NULL。

删除

  1. 如果存储方式是文件,将数据从文件删除,进入第三步删除SQLite中的卫星数据
  2. 如果存储类型是SQLite,进入第三步直接删除数据
  3. 使用SQL语句存储:insert or replace into manifest (key, filename, size, inline_data, modification_time, last_access_time, extended_data) values (?1, ?2, ?3, ?4, ?5, ?6, ?7);如果是存储卫星数据inline_data为NULL。

删除大小大于size的记录

  1. 使用SQL找出条目的名称 select filename from manifest where size > ?1 and filename is not null;
  2. 删除文件
  3. 删除条目 delete from manifest where size > ?1;

删除时间早于time的对象

  1. 数据库中找出时间(time)前修改的对象
    select filename from manifest where last_access_time < ?1 and filename is not null;
  2. 删除文件
  3. 数据库中删除记录 delete from manifest where last_access_time < ?1;

四、YYCache中使用的所有的SQL语句

  1. 建表:
1
pragma journal_mode = wal; pragma synchronous = normal; create table if not exists manifest (key text, filename text, size integer, inline_data blob, modification_time integer, last_access_time integer, extended_data blob, primary key(key)); create index if not exists last_access_time_idx on manifest(last_access_time);
  1. 新增一条记录
1
insert or replace into manifest (key, filename, size, inline_data, modification_time, last_access_time, extended_data) values (?1, ?2, ?3, ?4, ?5, ?6, ?7);
  1. 更新一条记录的修改时间
1
update manifest set last_access_time = ?1 where key = ?2;
  1. 删除记录为key的一个对象:
1
delete from manifest where key = ?1;
  1. 删除多个对象
1
delete from manifest where key in (%@);
  1. 删除szie大于某个值的记录
1
delete from manifest where size > ?1;
  1. 删除时间久的对象
1
delete from manifest where last_access_time < ?1;
  1. 查找某个对象
1
select key, filename, size, modification_time, last_access_time, extended_data from manifest where key = ?1;
  1. 查找大于size的记录的filename:
1
select filename from manifest where size > ?1 and filename is not null;
  1. 查找修改时间久的指定个数对象:
1
select key, filename, size from manifest order by last_access_time asc limit ?1;
  1. 获取存储的对象的总大小:
1
select sum(size) from manifest;
  1. 获取存储数量:
1
select count(*) from manifest;

五、学习到的SQL知识

sqlite3_stmt 的理解,它就是prepared statement。表示编译过的SQL语句,SQL语句编译过才能执行(SQL语句类比为应用程序,将程序的代码编译后,才能执行)

sqlite3_stmt的生命周期:

  1. sqlite3_prepare_v2 创建 prepared statement
  2. 使用sqlite3_bind_* 向参数[parameters] 绑定值
  3. 使用sqlite3_step() 一次或多次执行SQL语句
  4. 使用 sqlite3_reset() 重置到第二步
  5. 使用sqlite3_finalize()销毁对象