IOS 底层(一)-- SideTable
了解HashTable(哈希表)
哈希表(hash table)又叫散列表
和二叉树、链表这一类一样。它是一种数据结构,设计出来用来存放数据
需要哪些基础知识
- 指针和数组
- 链表
- 模运算
哈希表的构建方法
| 数组 | 指针 | 指针 | 指针 | 指针 | |||
|---|---|---|---|---|---|---|---|
| 下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
为什么叫 Hash表呢,因为有一个哈希函数
关键字: x -> f(x) -> 下标
哈希表的keys是一个数组,根据key 获取一个哈希函数f(key),再得到下标,再获取下标里面的value
哈希函数
哈希函数是根据关键字设计的,有很多种函数,主要的原理就是根据数组的大小求模运算。
(关键字) % (数组大小)
例如: 20048157%7 (结果在 0-16)
数组的大小一般设计位质数,因为需要均匀的散布
遇到冲突怎么办?
链表式解决
写结构体的时候加入next指针(和链表一样)
数据 | next -> 数据 | Next
遇到冲突的时候,把数据写到next位置
eg:
数组大小 7
哈希函数: 下标 = 关键字 mod 7
| 下标 | 数据 | 链表解决 |
|---|---|---|
| 0 | ||
| 1 | 15 | 22(链表while循环查询) |
| 2 | 16 | |
| 3 | 24 | |
| 4 | ||
| 5 | ||
| 6 |
开放地址(这块需要自己找)
不用next指针,把其他下标的位置都对外开放
开放地址的方法:
- 线性探测法
- 平方探测
- 双哈希
为什么设计哈希表
因为哈希表查找的性能快,它比搜索二叉树的速度还快
哈希表的缺点
表越满,冲突会多,性能越差.
SideTable
SideTable就是一个哈希表
SideTable 作为内存管理信息保存的容器,在系统中存储多个。系统通过实例对象的hash值与SideTable进行绑定。
一个实例对象对应一个SideTable, 而一个SideTable可以供多个实例对象共用
源码结构
struct SideTable {
/** 自旋锁 */
spinlock_t slock;
// 引用计数
RefcountMap refcnts;
// 弱引用表,hash表
weak_table_t weak_table;
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}
~SideTable() {
// 不能删除SideTable
_objc_fatal("Do not delete SideTable.");
}
// 锁住
void lock() { slock.lock(); }
void unlock() { slock.unlock(); }
// 重新设置锁
void forceReset() { slock.forceReset(); }
// Address-ordered lock discipline for a pair of side tables.
template<HaveOld, HaveNew>
static void lockTwo(SideTable *lock1, SideTable *lock2);
template<HaveOld, HaveNew>
static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
// 返回内存对齐后的SideTable指针
alignas(StripedMap<SideTable>) static uint8_t
SideTableBuf[sizeof(StripedMap<SideTable>)];
// 初始化SideTable
static void SideTableInit() {
new (SideTableBuf) StripedMap<SideTable>();
}
// 获取SideTable数组
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
其中说明,由于libc在C++初始化对象之前就调用了SideTable,且没有使用全局指针。故使用了这种方式初始化。
根据源码可以看到,SideTables是模板类StripedMap使用SideTable结构体初始化的类实例的指针。StripedMap是一个使用分离锁实现的类,内部成员是一个数组,使用hash算法进行索引存储。其算法如下:
enum { StripeCount = 8 };
static unsigned int indexForPointer(const void *p) {
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
}
故在生成SideTable时,系统使用实例对象的地址,将其进行hash计算后,得到index索引,最终从数组中取出SideTable对象: