磁盘缓存和内存缓存谈及YYCache

801 阅读5分钟

disk cache(磁盘缓存) 和 memory cache(内存缓存)

内存缓存

1.快速读取:内存缓存会将编译解析后的文件,直接存入该进程的内存中,占据该进程一定的内存资源,以方便下次运行使用时的快速读取

2.时效性:一旦该进程关闭,则该进程的内存则会清空。

磁盘缓存

硬盘缓存则是直接将缓存写入硬盘文件中,读取缓存需要对该缓存存放的硬盘文件进行I/O操作,然后重新解析该缓存内容,读取复杂,速度比内存缓存慢。

例如:在浏览器中,浏览器会在js和图片等文件解析执行后直接存入内存缓存中,那么当刷新页面时只需直接从内存缓存中读取(from memory cache);而css文件则会存入硬盘文件中,所以每次渲染页面都需要从硬盘读取缓存(from disk cache)

三级缓存原理:

->先去内存看,如果有,直接加载

->如果内存没有,择取硬盘获取,如果有直接加载

->如果硬盘也没有,那么就进行网络请求

->加载到的资源缓存到硬盘和内存

例如:访问图片-> 200 -> 退出浏览器

再进来-> 200(from disk cache) -> 刷新 -> 200(from memory cache)

接下来我们谈一谈iOS中使用到的缓存,其实我们最常用的就是ibireme开发的YYKit中的YYCache,这里我们来谈一谈!

1.YYCache的设计思路

首先内存缓存,比较了一些官方以及第三方的库,例如苹果的NSCache,这里可以浅谈一下NSCache,毕竟在AFNetworking以及SDWebImage中都是使用的它.

简单说明:NSCache

    (1)一旦调用了 removeAllObjects,就无法给 cache 添加对象。

    (2)关于 NSCache 的内存管理,交给他自己就行!

    (3)SDWebImage 中存在同样的问题,一旦内存警告,清理了内存之后,之后所有的图片都是从沙盒加载的。

    (4)NSCache 是线程安全的,在多线程操作中,不需要对 Cache 加锁。

    (5)NSCache 的 Key 只是做强引用,与可变字典不同,缓存对象不会对键名做 copy 操作,不需要实现 NSCopying协议

  YYMemoryCache 是作者开发的一个内存缓存,相对于 PINMemoryCache 来说,去掉了异步访问的接口,尽量优化了同步访问的性能,用 OSSpinLock 来保证线程安全。另外,缓存内部用双向链表和 NSDictionary 实现了 LRU 淘汰算法,相对于其他的几个库而言,写入和读取的速度都有提升。

其次磁盘缓存:通过观察其他的开源库,PINDiskCache,NSURLCache等,技术实现分为三种:基于文件读写,基于mmap文件内存映射,基于数据库。通过对比很明显基于数据库的明显要优于其他的性能。

  YYDiskCache也是使用的SQLite配合文件的存储方式,单条数据大于20K的时候,采用的文件的方式,而文件较小的时候就采用的是sqlite的方式读取

2.结构图

YYCache:提供了最外层的接口,调用了YYMemoryCache与YYDiskCache的相关方法。

  YYMemoryCache:负责处理容量小,相对高速的内存缓存。线程安全,支持自动和手动清理缓存等功能。

    _YYLinkedMap:YYMemoryCache使用的双向链表类。

**YYLinkedMapNode:是YYLinkedMap使用的节点类。

YYDiskCache:负责处理容量大,相对低速的磁盘缓存。线程安全,支持异步操作,自动和手动清理缓存等功能。

YYKVStorage:YYDiskCache的底层实现类,用于管理磁盘缓存。

YYKVStorageItem:内置在YYKVStorage中,是YYKVStorage内部用于封装某个缓存的类

3.代码讲解

  (1)YYCache中,永远都是先访问内存缓存,然后再访问磁盘缓存(包括了写入,读取,查询,删除缓存的操作)

    - (void)setObject:(id)object forKey:(NSString * )key {

//先写入内存缓存,后写入磁盘缓存

[ _memoryCache setObject:object forKey:key];

[ _diskCache setObject:object forKey:key];

}

   - (void)removeObjectForKey:(NSString * )key {

//先移除内存缓存,后移除磁盘缓存

[ _memoryCache removeObjectForKey:key];

[ _diskCache removeObjectForKey:key];

}

  (2)在读取缓存的操作中,如果在内存缓存中无法获取对应的缓存,则会去磁盘缓存中寻找。如果在磁盘缓存中找到了对应的缓存,则会将该对象再次写入内存缓存中,保证在下一次尝试获取同一缓存时能够在内存中就能返回,提高速度。

   - (id)objectForKey:(NSString * )key {

    

    //首先尝试获取内存缓存,然后获取磁盘缓存

    id object = [_memoryCache objectForKey:key];

    

    //如果内存缓存不存在,就会去磁盘缓存里面找:如果找到了,则再次写入内存缓存中;如果没找到,就返回nil

    if (!object) {

        object = [_diskCache objectForKey:key];

        if (object) {

            [_memoryCache setObject:object forKey:key];

        }

    }

    return object;

    }

  (3)YYMemoryCache内部使用到了LRU算法以及缓存清理策略

     缓存淘汰算法:使用LRU(least-recently-used) 算法来淘汰(清理)使用频率较低的缓存。具体的实现是采用的双向链表

     缓存清理策略:使用三个维度来标记,分别是count(缓存数量),cost(开销),age(距上一次的访问时间)。YYMemoryCache提供了分别针对这三个维度的清理缓存的接口。用户可以根据不同的需求(策略)来清理在某一维度超标的缓存

           大家如果有兴趣,可以去看一下源码,了解一下作者的双向链表是如何实现这个淘汰算法的。

参考资料:

blog.ibireme.com/2015/10/26/…

juejin.cn/post/684490…