YYKit源码阅读 - YYCache

前言

本篇用来记录下阅读YYKit中YYCache的一些理解和收获,着重解决两个问题:

  1.YYCache在内存和磁盘各自存取方式。

  2.YYCache使用怎样的数据结构来进行数据的存储。怎样确保多线程下的数据操作的安全性。

首先我们来看一下YYCache包含的文件。在本篇中我们并不像之前那样每个属性,每个方法都展示出来解释,主要还是针对问题。

YYMemoryCache

YYMemoryCache是存储键值对的快速内存缓存,区别于NSDictionary,其键是retain而非copy。它的API与NSCache相似并且都是线程安全的。但它也与NSCache有一些不同点:

  • YYMemoryCache使用LRU也就是最近最少使用来移除objects;NSCache是不确切的。
  • YYMemoryCache可以通过对象数量,对象空间总量以及失效时间来控制;NSCache在此也是不确切的。
  • YYMemoryCache可以配置在当收到内存警告或者app进入后台是来自动移除掉objects。

同时YYMemoryCache的访问方法的时间复杂度均为O(1)。

在.m文件中我们发现有一个_YYLinkedMap对象

其内包含了一个可变字典,一个head节点,一个tail节点以及其他属性。这里的可变字典使用的是CoreFoundation对象,更加底层,性能更好。字典内保存的是_YYLinkedMapNode对象

包含一个前驱节点,一个后继节点。看到这两个名词就知道了使用的链表结构而且是双向链表。但是使用双向链表的目的是什么呢?我们来看下

_YYLinkedMap中定义的方法

这里我们主要来看下insertNodeAtHead和bringNodeToHead两个方法的实现

- (void)insertNodeAtHead:(_YYLinkedMapNode *)node {
    //将node存入字典中
    CFDictionarySetValue(_dic, (__bridge const void *)(node->_key), (__bridge const void *)(node));
    //空间总量增加
    _totalCost += node->_cost;
    //对象数量增加
    _totalCount++;

    //如果已经存在head
    if (_head) {
        //将新添加的node作为head
        node->_next = _head;
        _head->_prev = node;
        _head = node;
    } else {
        //将新添加的node同时作为head和tail
        _head = _tail = node;
    }
}

- (void)bringNodeToHead:(_YYLinkedMapNode *)node {
    //node为head就不操作
    if (_head == node) return;

    //分别处理node为tail和node为中间节点的情况
    if (_tail == node) {
        _tail = node->_prev;
        _tail->_next = nil;
    } else {
        node->_next->_prev = node->_prev;
        node->_prev->_next = node->_next;
    }
    //node变更为head
    node->_next = _head;
    node->_prev = nil;
    _head->_prev = node;
    _head = node;
}

其中head是作为MRU的,而tail是作为LRU的。每次从字典中取出缓存时就将此次所取node移到链表的头部,而尾部就是最不常用的node。

我们从YYMemoryCache的成员变量中可以看到有一个pthread_mutex_t,这是一个互斥锁。互斥锁常用的几个方法大概有下面几个

  • pthread_mutex_init,锁的初始化
  • pthread_mutex_lock,加锁
  • pthread_mutex_unlock,解锁
  • pthread_mutex_trylock,pthread_mutex_lock的非阻塞版本

基本上YYMemoryCache所有暴露在外的属性以及隐藏在内的dictionary的操作均存在锁操作。举个例子

- (void)_trimToCost:(NSUInteger)costLimit {
    BOOL finish = NO;
    pthread_mutex_lock(&_lock);
    if (costLimit == 0) {
        [_lru removeAll];
        finish = YES;
    } else if (_lru->_totalCost <= costLimit) {
        finish = YES;
    }
    pthread_mutex_unlock(&_lock);
    if (finish) return;

    NSMutableArray *holder = [NSMutableArray new];
    while (!finish) {
        if (pthread_mutex_trylock(&_lock) == 0) {
            if (_lru->_totalCost > costLimit) {
                _YYLinkedMapNode *node = [_lru removeTailNode];
                if (node) [holder addObject:node];
            } else {
                finish = YES;
            }
            pthread_mutex_unlock(&_lock);
        } else {
            usleep(10 * 1000); //10 ms
        }
    }
    if (holder.count) {
        dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
        dispatch_async(queue, ^{
            [holder count]; // release in queue
        });
    }
}

该方法的目的是根据设置的最大cost来删除缓存。方法里面有两个值得注意的地方一个是使用了usleep函数。看到usleep一定会想到sleep,区别在于usleep一般以ns作为计量单位而当休眠时间达到s的量级会使用sleep。还有就是将holder数组捕获到queue的block内用于在置顶queue中释放holder。

YYDiskCache

未完待续

原文地址:https://www.cnblogs.com/kaisi/p/9871342.html

时间: 2024-11-09 01:39:40

YYKit源码阅读 - YYCache的相关文章

CI框架源码阅读笔记3 全局函数Common.php

从本篇开始,将深入CI框架的内部,一步步去探索这个框架的实现.结构和设计. Common.php文件定义了一系列的全局函数(一般来说,全局函数具有最高的加载优先权,因此大多数的框架中BootStrap引导文件都会最先引入全局函数,以便于之后的处理工作). 打开Common.php中,第一行代码就非常诡异: if ( ! defined('BASEPATH')) exit('No direct script access allowed'); 上一篇(CI框架源码阅读笔记2 一切的入口 index

淘宝数据库OceanBase SQL编译器部分 源码阅读--生成逻辑计划

body, td { font-family: tahoma; font-size: 10pt; } 淘宝数据库OceanBase SQL编译器部分 源码阅读--生成逻辑计划 SQL编译解析三部曲分为:构建语法树,生成逻辑计划,指定物理执行计划.第一步骤,在我的上一篇博客淘宝数据库OceanBase SQL编译器部分 源码阅读--解析SQL语法树里做了介绍,这篇博客主要研究第二步,生成逻辑计划. 一. 什么是逻辑计划?我们已经知道,语法树就是一个树状的结构组织,每个节点代表一种类型的语法含义.如

JDK部分源码阅读与理解

本文为博主原创,允许转载,但请声明原文地址:http://www.coselding.cn/article/2016/05/31/JDK部分源码阅读与理解/ 不喜欢重复造轮子,不喜欢贴各种东西.JDK代码什么的,让整篇文章很乱...JDK源码谁都有,没什么好贴的...如果你没看过JDK源码,建议打开Eclipse边看源码边看这篇文章,看过的可以把这篇文章当成是知识点备忘录... JDK容器类中有大量的空指针.数组越界.状态异常等异常处理,这些不是重点,我们关注的应该是它的一些底层的具体实现,这篇

如何阅读Java源码 阅读java的真实体会

刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心. 说到技术基础,我打个比方吧,如果你从来没有学过Java,或是任何一门编程语言如C++,一开始去啃<Core Java>,你是很难从中吸收到营养的,特别是<深入Java虚拟机>这类书,别人觉得好,未必适合现在的你. 虽然Tomcat的源码很漂亮,但我绝不建议你一开始就读它.我文中会专门谈到这个,暂时不展开. 强烈

Memcache-Java-Client-Release源码阅读(之七)

一.主要内容 本章节的主要内容是介绍Memcache Client的Native,Old_Compat,New_Compat三个Hash算法的应用及实现. 二.准备工作 1.服务器启动192.168.0.106:11211,192.168.0.106:11212两个服务端实例. 2.示例代码: String[] servers = { "192.168.0.106:11211", "192.168.0.106:11212" }; SockIOPool pool =

源码阅读笔记 - 1 MSVC2015中的std::sort

大约寒假开始的时候我就已经把std::sort的源码阅读完毕并理解其中的做法了,到了寒假结尾,姑且把它写出来 这是我的第一篇源码阅读笔记,以后会发更多的,包括算法和库实现,源码会按照我自己的代码风格格式化,去掉或者展开用于条件编译或者debug检查的宏,依重要程度重新排序函数,但是不会改变命名方式(虽然MSVC的STL命名实在是我不能接受的那种),对于代码块的解释会在代码块前(上面)用注释标明. template<class _RanIt, class _Diff, class _Pr> in

JDK 源码 阅读 - 2 - 设计模式 - 创建型模式

A.创建型模式 抽象工厂(Abstract Factory) javax.xml.parsers.DocumentBuilderFactory DocumentBuilderFactory通过FactoryFinder实例化具体的Factory. 使用例子: DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = docBuilder

CI源码阅读

CodeIgniter源码分析 http://calixwu.com/2014/11/codeigniter-yuanmafenxi.html CI框架源码阅读笔记 http://www.cnblogs.com/ohmygirl/p/4052686.html

《java.util.concurrent 包源码阅读》13 线程池系列之ThreadPoolExecutor 第三部分

这一部分来说说线程池如何进行状态控制,即线程池的开启和关闭. 先来说说线程池的开启,这部分来看ThreadPoolExecutor构造方法: public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecut