YYCache的整体结构是分为两部分:内存缓存和磁盘缓存。
内存缓存提供容量小但高速的存取功能,磁盘缓存提供大容量但低速的持久化存储。
为什么需要缓存?
内存的读写速度远大于磁盘的读写速度,将频繁使用的数据存在内存中,再次用到的时候直接从内存中读取,从而提高性能。
设计内存缓存的几个要点
1. 缓存算法:根据场景选择合适的缓存算法,才能使缓存有较高的命中率。常见的缓存算法有:LRU(最近最少使用)、LRU-2(类似LRU,准入门槛变成2次)、LFU(最不经常使用)、FIFO(先进先出)等等。
2. 读写性能:相同情况下,存储数据和读取数据的速度。
3. 线程安全:考虑到可能会在多个线程中读写缓存。
LRU算法
LRU算法(least recently used)顾名思义,淘汰掉最近最少使用的数据。
1. 访问的数据未命中缓存,如果此时缓存未满的时候。直接将新数据插到最前面(这也决定了基础结构不能用数组,只能用链表,数组插入头部时间复杂度为O(n),链表插入头部时间复杂度为O(1))。
2. 访问的数据命中缓存,将访问的数据插到链表的最前面。
3. 访问的数据未命中数据,但是此时缓存已经满了,此时需要先淘汰掉链表的尾部,然后再见新数据插到最前面。
YYModel内存缓存的设计
YYModel的内存缓存部分是YYMemoryCache
这个类实现的。YYMemoryCache使用了LRU缓存算法,由hashMap+双链表实现。因为hashMap的读操作是O(1),用于保证读取速度。双链表的删除和插入操作都是O(1),很好的保证了写操作的速度。
为什么不用单链表?
1. 单链表的删除操作需要读到前一个节点,而读取前一个节点需要从头遍历。
2. 有人会说,也不是吧,可以将下一个节点的数据赋给当前节点,然后删除下一个节点,这样就相当于删除了当前节点(删除了当前节点的数据)。首先这样有一遍拷贝数据的操作,如果删除的是最后一个,这种做法就没有了。而缓存算法的淘汰机制刚好删除的都是最后一个。
YYModel结构
hashmap作用在于快速读取。双链表用于维护顺序,实现淘汰机制。
LRU机制对应的伪代码:
1. 访问的数据未命中缓存,如果此时缓存未满的时候。直接将新数据插到最前面
if (!hashmap[@"key"]) {
Node *node = [[Node alloc] init];
node.data = data;
hashmap[@"key"] = node;
if (self.linkedList.head) {
node.next = self.linkedList.head;
node.prev = nil;
self.linkedList.head.prev = node;
self.linkedList.head = node;
} else {
node.prev = nil;
node.next = nil;
self.linkedList.head = node;
self.linkedList.tail = node;
}
}复制代码
2. 访问的数据命中缓存,将访问的数据插到链表的最前面。
if (hashmap[@"key"]) {
Node *node = hashmap[@"key"];
if (node == self.linkedList.head) {
return;
} else if (node == self.linkedList.tail) {
node.prev.next = nil;
self.linkedList.tail = node.prev;
node.next = self.linkedList.head;
self.linkedList.head.prev = node;
self.linkedList.head = node;
} else {
node.prev.next = node.next;
node.next.prev = node.prev;
node.next = self.linkedList.head;
self.linkedList.head.prev = node;
self.linkedList.head = node;
}
}复制代码
3. 访问的数据未命中数据,但是此时缓存已经满了,此时需要先淘汰掉链表的尾部,然后再见新数据插到最前面。
Node *node = self.linkedList.tail;
node.prev.next = nil;
self.linkedList.tail = node.prev;
[hashmap removeObjectForKey:@"key"];
free(node);复制代码
线程安全
1. 读写的时候加锁保证线程安全
2. 删除释放节点在异步线程,提高性能。
if (_lru->_totalCost > _costLimit) {
dispatch_async(_queue, ^{
[self trimToCost:_costLimit];
});
}复制代码