1. IOS 字典研究

一: Hash 算法

1. hash算法

hash称为散列表,其实本质上还是一个数组。主要是通过 hash(key) 生成一个index 去从数组中取出值。但是如果产生hash(key1) == hash(key2) 就是hash冲突。

2. 冲突解决

为了解决冲突,有 开放寻址法(open addressing)和 链表法(chaining)

链表法(chaining)

这个比较简单,类似一个二维数组,就是一个一维数组,存储 链表 的表头,链表根据 hash函数,记录key,并一个一个链接下去。 这里就需要记录指针,导致了相对来说存储空间的增加。

如图,假设hash函数是对6 取余数,那么{0,6,7,2} 结果如下图:


image

开放寻址法(open addressing)

开放寻址方法,比如我们一般的hash方法, p = Hash(key),假设得到的p值有冲突,那么就,p = Hash(p + di),di 称为增量序列取值为【1,2,....】 ; 而且性能依赖于装填因子 α=N/M,α 超过0.72的时候,会严重影响性能。

1.线性探测

很容易产生数据集中的问题

 //对m 取余数的,m一般是hash表的长度
 Hash(key) {
    return key%m 
 }
 // 获取index值
 p = Hash(key) 
 // 如果冲突 了,就 +di 往下探测,直到不冲突
 p = Hash(Hash(key)+1)
 p = Hash(Hash(key)+2)
 p = Hash(Hash(key)+3)
 p = Hash(Hash(key)+4)
 ...
2.二次探测

为了让数据分布均匀一点,让数据更加离散,就出现了二次探测,p=Hash(Hash(key)+di) di取值可能为1,-1,4,-4,9,-9,16,-16,...k2,-k2(k<=m/2)其中m为表长,di为增量序列

 // 如果冲突 了,就 +/- di^2 往下探测,直到不冲突
 p = Hash(Hash(key)+1)
 p = Hash(Hash(key)-1)
 p = Hash(Hash(key)+4)
 p = Hash(Hash(key)-4)
 ...

二: NSDictory内部实现

NSDictionary是可以通过CFDictionary toll-free bridged 过来的。而且我们是可以查看CFDictionary.

用CFDictionary研究和使用

1. 首先我们查看下一下,用CFDictionary 创建

CFStringRef key1 = CFStringCreateWithCString(CFAllocatorGetDefault(), "name", kCFStringEncodingUTF8);
    CFStringRef key2 = CFStringCreateWithCString(CFAllocatorGetDefault(), "age", kCFStringEncodingUTF8);
    CFStringRef keys[] = {key1, key2};
    
    CFStringRef values[] = {CFSTR("leeDev"), CFSTR("27")};
    int count = sizeof(values)/sizeof(values[0]);
    CFDictionaryRef dic = CFDictionaryCreate(CFAllocatorGetDefault(), (const void**)keys, (const void**)values,count, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    
    NSLog(@"count = %ld,value = %@",(long)CFDictionaryGetCount(dic),CFDictionaryGetValue(dic, key1));
    
    // 可变字典
    CFMutableDictionaryRef mutDic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    CFDictionaryAddValue(mutDic, keys[0], values[0]);
    CFDictionarySetValue(mutDic, keys[0], CFSTR("lisi"));
    NSLog(@"mutDic = %@",mutDic);

我们找到 CFDictionaryCreate 中 方法,传入4个值

  • keys
  • values
  • count
  • kCFTypeDictionaryKeyCallBacks:
  • kCFTypeDictionaryValueCallBacks

2. 我们通过查看CF-855.17中的创建源码,并删除一些异常判断的代码

CFHashRef CFDictionaryCreate(CFAllocatorRef allocator, const_any_pointer_t *klist, const_any_pointer_t *vlist, CFIndex numValues, const CFDictionaryKeyCallBacks *keyCallBacks, const CFDictionaryValueCallBacks *valueCallBacks) {
   
    CFTypeID typeID = CFDictionaryGetTypeID();
    
    //1.创建hash表
    CFBasicHashRef ht = __CFDictionaryCreateGeneric(allocator, keyCallBacks, valueCallBacks, CFDictionary);
    
    //2.给hash表设置容量
    if (0 < numValues) CFBasicHashSetCapacity(ht, numValues);
    
    //3. 给hash表设置 keys 和 values
    for (CFIndex idx = 0; idx < numValues; idx++) {
        CFBasicHashAddValue(ht, (uintptr_t)klist[idx], (uintptr_t)vlist[idx]);
    }
    
    //4.设置不可变的hash表
    CFBasicHashMakeImmutable(ht);
    _CFRuntimeSetInstanceTypeIDAndIsa(ht, typeID);

    return (CFHashRef)ht;
}

2.1 在给hash表的添加值的时候有个方法是 CFBasicHashAddValue
static void __CFBasicHashAddValue(CFBasicHashRef ht, CFIndex bkt_idx, uintptr_t stack_key, uintptr_t stack_value) {
    //1.记录操作数
    ht->bits.mutations++;
    //2. 如果我们的hash表的容量 不够,需要Rehash
    if (CFBasicHashGetCapacity(ht) < ht->bits.used_buckets + 1) {
        // 2.1.1 下面会有容量数组的介绍。
        __CFBasicHashRehash(ht, 1);
        //2.1.2 就是通过 开发地址方法来找hash的index
        bkt_idx = __CFBasicHashFindBucket_NoCollision(ht, stack_key, 0);
    } else if (__CFBasicHashIsDeleted(ht, bkt_idx)) {
        ht->bits.deleted--;
    }
    //2.1.3 key_hash
    uintptr_t key_hash = 0;
    if (__CFBasicHashHasHashCache(ht)) {
        key_hash = __CFBasicHashHashKey(ht, stack_key);
    }
    // 2.1.4 通过对value就行retain操作
    stack_value = __CFBasicHashImportValue(ht, stack_value);
    
    if (ht->bits.keys_offset) {
       // 2.1.5 对key 进行retain操作
        stack_key = __CFBasicHashImportKey(ht, stack_key);
    }
    //2.1.6 hash 表里面存入的是 CFBasicHashValue的对象
    //就是把 stack_value 和 stack_key 存入到 CFBasicHashValue
    __CFBasicHashSetValue(ht, bkt_idx, stack_value, false, false);
    if (ht->bits.keys_offset) {
        __CFBasicHashSetKey(ht, bkt_idx, stack_key, false, false);
    }
    if (ht->bits.counts_offset) {
        __CFBasicHashIncSlotCount(ht, bkt_idx);
    }
    if (__CFBasicHashHasHashCache(ht)) {
        __CFBasicHashGetHashes(ht)[bkt_idx] = key_hash;
    }
    ht->bits.used_buckets++;
}

2.1.1 容量数组,用于字典容量的申请 ,就是说,__CFBasicHashRehash(ht, 1);执行后,假设开始的容量是0,那么Rehash后就是3; 如果是3,那么Rehash后就是6
static const uintptr_t __CFBasicHashTableCapacities[64] = {
    0, 3, 6, 11, 19, 32, 52, 85, 118, 155, 237, 390, 672, 1065,
    1732, 2795, 4543, 7391, 12019, 19302, 31324, 50629, 81956,
    132580, 214215, 346784, 561026, 907847, 1468567, 2376414,
    3844982, 6221390, 10066379, 16287773, 26354132, 42641916,
    68996399, 111638327, 180634415, 292272755,
#if __LP64__
    472907503UL, 765180257UL, 1238087439UL, 2003267722UL, 3241355160UL,
#if 0
    5244622578UL, 8485977737UL, 13730600347UL, 22216578100UL,
    35947178453UL, 58163756541UL, 94110935011UL, 152274691274UL,
    246385626296UL, 398660317578UL, 645045943559UL, 1043706261135UL,
    1688752204693UL, 2732458465840UL, 4421210670552UL,
    7153669136706UL, 11574879807265UL, 18728548943682UL
#endif
#endif
};
2.1.2 通过开发地址 来处理,并返回当前的index
CF_INLINE CFIndex __CFBasicHashFindBucket_NoCollision(CFConstBasicHashRef ht, uintptr_t stack_key, uintptr_t key_hash) {
    if (0 == ht->bits.num_buckets_idx) {
        return kCFNotFound;
    }
    if (ht->bits.indirect_keys) {
        switch (ht->bits.hash_style) {
        case __kCFBasicHashLinearHashingValue: return ___CFBasicHashFindBucket_Linear_Indirect_NoCollision(ht, stack_key, key_hash);
        case __kCFBasicHashDoubleHashingValue: return ___CFBasicHashFindBucket_Double_Indirect_NoCollision(ht, stack_key, key_hash);
        case __kCFBasicHashExponentialHashingValue: return ___CFBasicHashFindBucket_Exponential_Indirect_NoCollision(ht, stack_key, key_hash);
        }
    } else {
        switch (ht->bits.hash_style) {
        case __kCFBasicHashLinearHashingValue: return ___CFBasicHashFindBucket_Linear_NoCollision(ht, stack_key, key_hash);
        case __kCFBasicHashDoubleHashingValue: return ___CFBasicHashFindBucket_Double_NoCollision(ht, stack_key, key_hash);
        case __kCFBasicHashExponentialHashingValue: return ___CFBasicHashFindBucket_Exponential_NoCollision(ht, stack_key, key_hash);
        }
    }
    HALT;
    return kCFNotFound;
}
2.2 CFBasicHashCallbacks 结构体是干嘛的?

首先 是一个struct __CFBasicHashCallbacks 结构体,里面包含的是 函数指针,前面的 kCFTypeDictionaryKeyCallBackskCFTypeDictionaryValueCallBacks 其实主要是用来对 key 或者 value 来进行 retain等操作。

struct __CFBasicHashCallbacks {
    uintptr_t (*retainValue)(CFAllocatorRef alloc, uintptr_t stack_value);  // Return 2nd arg or new value
    uintptr_t (*retainKey)(CFAllocatorRef alloc, uintptr_t stack_key);  // Return 2nd arg or new key
    void (*releaseValue)(CFAllocatorRef alloc, uintptr_t stack_value);
    void (*releaseKey)(CFAllocatorRef alloc, uintptr_t stack_key);
    Boolean (*equateValues)(uintptr_t coll_value1, uintptr_t stack_value2); // 1st arg is in-collection value, 2nd arg is probe parameter OR in-collection value for a second collection
    Boolean (*equateKeys)(uintptr_t coll_key1, uintptr_t stack_key2); // 1st arg is in-collection key, 2nd arg is probe parameter
    CFHashCode (*hashKey)(uintptr_t stack_key);
    uintptr_t (*getIndirectKey)(uintptr_t coll_value);  // Return key; 1st arg is in-collection value
    CFStringRef (*copyValueDescription)(uintptr_t stack_value);
    CFStringRef (*copyKeyDescription)(uintptr_t stack_key);
};
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容