IOS 底层(一)-- SideTable

2,078 阅读3分钟

IOS 底层(一)-- SideTable

了解HashTable(哈希表)

哈希表(hash table)又叫散列表

和二叉树、链表这一类一样。它是一种数据结构,设计出来用来存放数据

需要哪些基础知识

  • 指针和数组
  • 链表
  • 模运算

哈希表的构建方法

数组指针指针指针指针
下标0123456

为什么叫 Hash表呢,因为有一个哈希函数

关键字: x -> f(x) -> 下标

哈希表的keys是一个数组,根据key 获取一个哈希函数f(key),再得到下标,再获取下标里面的value

哈希函数

哈希函数是根据关键字设计的,有很多种函数,主要的原理就是根据数组的大小求模运算。

(关键字) % (数组大小)

例如: 20048157%7 (结果在 0-16)

数组的大小一般设计位质数,因为需要均匀的散布

遇到冲突怎么办?

链表式解决

写结构体的时候加入next指针(和链表一样)

​ 数据 | next -> 数据 | Next

遇到冲突的时候,把数据写到next位置

eg:

数组大小 7

哈希函数: 下标 = 关键字 mod 7

下标数据链表解决
0
11522(链表while循环查询)
216
324
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对象: