缓存-cache

563 阅读3分钟

开始之前,先了解一下哈希表这个东西会很有帮助,iOS开发中以下情况就有哈希表这个数据结构参与

1.关联属性---采用开放寻址法解决冲突

2.分类

3.通知中心

4.关于方法缓存命中---苹果采用开放寻址法解决冲突

5.字典---采用开放寻址法解决冲突

6.weak---采用开放寻址法解决冲突

7.kvo

8.数字签名

哈希表的底层实现其实是数组,数组这样一个容器里面的元素被称为bin,可以就叫箱子,这个bin又是以链表的数据结构实现的,链表用来存放键值对

哈希表的哈希步骤分为三步

1,对指定key通过哈希函数计算得出一个值,假设为h,该值就叫哈希值

2,假设有n个箱子,那么该key-value应该被存放在h%n的位置

3,对于已经存在的键值对,采用开放寻址法和拉链法解决冲突

哈希表有一个重要的属性,叫作负载因子,用来衡量哈希表的空/满程度 负载因子 = 总键值对数/箱子个数 负载因子越大证明哈希表越接近于满存储状态,带来的问题就是越容易出现冲突,性能越低 一般来说,当负载因子大于等于1或者0.75的时候会自动扩容—rehash

rehash:自动扩容后的空间一般是扩容之前的二倍,也就是原来的箱子个数会翻倍,这样就会影响h%n的值

哈希表的两个问题:

1.如果哈希表中原本箱子就多,rehash后,需要移动箱子的位置,性能影响比较大

2.如果哈希函数设计不合理,哈希表在极端情况下(所有h一样)会变成线性表(哈希值一致的情况下不是直接后者覆盖前者,而是另取内存进行存储,具体见下图或者objc4-723版本源码)

截取部分代码如下

cache_t进去

typedef uintptr_t cache_key_t;
struct bucket_t {
private:
    cache_key_t _key;
    IMP _imp;
public:
    inline cache_key_t key() const { return _key; }
    inline IMP imp() const { return (IMP)_imp; }
    inline void setKey(cache_key_t newKey) { _key = newKey; }
    inline void setImp(IMP newImp) { _imp = newImp; }
    void set(cache_key_t newKey, IMP newImp);
};
struct cache_t {//通过哈希表实现的
    struct bucket_t *_buckets;用来缓存方法的容器——哈希表/散列表
    mask_t _mask;哈希表长度-1
    mask_t _occupied;已经缓存的方法数量

public:
    struct bucket_t *buckets();
    mask_t mask();
    mask_t occupied();
    void incrementOccupied();
    void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
    void initializeToEmpty();
    mask_t capacity();
    bool isConstantEmptyCache();
    bool canBeFreed();
    static size_t bytesForCapacity(uint32_t cap);
    static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap);
    void expand();//扩容 当前缓存空间不足(达到75%的时候),会自动扩容 扩容空间为旧空间的二倍,这都是哈希表的内容
    void reallocate(mask_t oldCapacity, mask_t newCapacity);
    struct bucket_t * find(cache_key_t key, id receiver);
    static void bad_cache(id receiver, SEL sel, Class isa) __attribute__((noreturn));
};

整理一下大概就是下图所示内容

(图中第二个判断部分 index -=1,源码部分其实就是利用开放寻址法解决冲突的)