• YYKit源码阅读


    前言

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

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

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

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

    YYMemoryCache

    YYMemoryCache是存储键值对的快速内存缓存,区别于NSDictionary,其键是retain而非copy(原因是因为使用的CFDictionary存储)。它的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

    未完待续 

  • 相关阅读:
    操作系统之磁盘结构笔记
    Linux 操作系统位数(32or64)识别
    手把手教你mysql(十)索引
    Linux命令 — 设置或查看网络配置命令ifconfig
    字符数组的排列
    android 加载图片防止内存溢出
    eCos系统CPU负载测量
    模糊控制——理论基础(4模糊推理)
    模糊控制——理论基础(3模糊关系及其运算)
    模糊控制——理论基础(2隶属函数)
  • 原文地址:https://www.cnblogs.com/kaisi/p/9871342.html
Copyright © 2020-2023  润新知