开始之前,先了解一下哈希表这个东西会很有帮助,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,源码部分其实就是利用开放寻址法解决冲突的)
