NSDictionary的实质和实现原理

一、NSDictionary使用原理

    1.NSDictionary(字典)是使用 hash表来实现keyvalue之间的映射和存储的, hash函数设计的好坏影响着数据的查找访问效率。

    - (void)setObject:(id)anObject forKey:(id <NSCopying>)aKey;

   2.Objective-C中的字典 NSDictionary 底层其实是一个哈希表,实际上绝大多数语言中字典都通过哈希表实现,


二、哈希的原理

   哈希概念:哈希表的本质是一个数组,数组中每一个元素称为一个箱子(bin),箱子中存放的是键值对。

    

三、哈希表的存储过程:

    1. 根据 key计算出它的哈希值 h

    2.假设箱子的个数为 n,那么这个键值对应该放在第 (h % n) 个箱子中。

    3.如果该箱子中已经有了键值对,就使用开放寻址法或者拉链法解决冲突。

    在使用拉链法解决哈希冲突时,每个箱子其实是一个链表,属于同一个箱子的所有键值对都会排列在链表中。

    哈希表还有一个重要的属性:负载因子(load factor),它用来衡量哈希表的 / 程度,一定程度上也可以体现查询的效率,计算公式为:

   负载因子 =总键值对数 /箱子个数

   负载因子越大,意味着哈希表越满,越容易导致冲突,性能也就越低。因此,一般来说,当负载因子大于某个常数(可能是1,或者0.75)时,哈希表将自动扩容。


重哈希概念:    

   哈希表在自动扩容时,一般会创建两倍于原来个数的箱子,因此即使 key的哈希值不变,对箱子个数取余的结果也会发生改变,因此所有键值对的存放位置都有可能发生改变,这个过程也称为重哈希(rehash)

    

    哈希表的扩容并不总是能够有效解决负载因子过大的问题。假设所有 key的哈希值都一样,那么即使扩容以后他们的位置也不会变化。虽然负载因子会降低,但实际存储在每个箱子中的链表长度并不发生改变,因此也就不能提高哈希表的查询性能。

    

四、总结,细心的读者可能会发现哈希表的两个问题:

   1.如果哈希表中本来箱子就比较多,扩容时需要重新哈希并移动数据,性能影响较大。

   2.如果哈希函数设计不合理,哈希表在极端情况下会变成线性表,性能极低。



下面:

   NSDictionary(字典)是使用 hash表来实现key和value之间的映射和存储的, hash函数设计的好坏影响着数据的查找访问效率。数据在hash表中分布的越均匀,其访问效率越高。而在Objective-C中,通常都是利用NSString 来作为键值,其内部使用的hash函数也是通过使用 NSString对象作为键值来保证数据的各个节点在hash表中均匀分布。

见NSDictionary中最常用的一个方法原型:

[objc] view plain copy
  1. - (void)setObject:(id)anObject forKey:(id <NSCopying>)aKey;  

从这个方法中可以知道, 要作为 Key 值,必须遵循 NSCopying 协议。也就是说在NSDictionary内部,会对 aKey 对象 copy 一份新的。而  anObject 对象在其内部是作为强引用(retain或strong)。所以在MRC下,向该方法发送消息之后,我们会向anObject发送 release 消息进行释放。

既然知道了作为 key 值,必须遵循 NSCopying 协议,说明除了 NSString 对象之外,我们还可以使用其他类型对象来作为 NSDictionary 的 key值。不过这还不够,作为 key 值,该类型还必须继承于 NSObject 并且要重载一下两个方法:

[objc] view plain copy
  1. - (NSUInteger)hash;  

[objc] view plain copy
  1. - (BOOL)isEqual:(id)object;  

其中,hash 方法是用来计算该对象的 hash 值,最终的 hash 值决定了该对象在 hash 表中存储的位置。所以同样,如果想重写该方法,我们尽量设计一个能让数据分布均匀的 hash 函数。

isEqual 方法是为了通过 hash 值来找到 对象 在hash 表中的位置。

下面,我根据一个小Demo来自定义一个作为 NSDictionary 的 key 值的类。见代码:

KeyObject.h文件:

[objc] view plain copy
  1. //  
  2. //  KeyObject.h  
  3. //  KeyDemo  
  4. //  
  5. //  Created by linshaolie on 14-11-26.  
  6. //  Copyright (c) 2014年 lin. All rights reserved.  
  7. //  
  8.   
  9. #import <Foundation/Foundation.h>  
  10.   
  11. @interface KeyObject : NSObject<NSCopying>  //实现Copying协议  
  12.   
  13. //作为hash值  
  14. @property(nonatomicreadonly) NSUInteger hashValue;  
  15.   
  16. - (instancetype)initWithHashI:(NSUInteger)i;  
  17.   
  18. @end  
KeyObject.m文件:

[objc] view plain copy
  1. //  
  2. //  KeyObject.m  
  3. //  KeyDemo  
  4. //  
  5. //  Created by linshaolie on 14-11-26.  
  6. //  Copyright (c) 2014年 lin. All rights reserved.  
  7. //  
  8.   
  9. #import "KeyObject.h"  
  10.   
  11. @interface KeyObject ()  
  12.   
  13. //作为hash值  
  14. @property(nonatomic) NSUInteger hashValue;  
  15.   
  16. @end  
  17.   
  18. @implementation KeyObject  
  19.   
  20.   
  21. - (instancetype)initWithHashValue:(NSUInteger)hashValue  
  22. {  
  23.     self = [super init];  
  24.     if ( self )  
  25.     {  
  26.         _hashValue = hashValue;  
  27.     }  
  28.     return self;  
  29. }  
  30.   
  31. #pragma mark -overload methor  
  32. - (BOOL)isEqual:(id)object  
  33. {  
  34.     //根据hash值判断是否是同一个键值  
  35.     return ([self hashKeyValue] == [(typeof(self))object hashKeyValue]);  
  36. }  
  37.   
  38. - (NSUInteger)hash  
  39. {  
  40.     return [self hashKeyValue];     //返回哈希值  
  41. }  
  42.   
  43. #pragma mark -NSCopying  
  44. - (id)copyWithZone:(NSZone *)zone  
  45. {  
  46.     KeyObject *obj = [KeyObject allocWithZone:zone];  
  47.     obj.hashValue = self.hashValue;  
  48.     return obj;  
  49. }  
  50.   
  51. #pragma mark -private methor  
  52. //哈希函数  
  53. - (NSUInteger)hashKeyValue  
  54. {  
  55.     return _hashValue % 7;      //这里哈希函数用了简单的对7取余  
  56. }  
  57.   
  58. @end  

ViewController.m文件:(测试)

[objc] view plain copy
  1. //  
  2. //  ViewController.m  
  3. //  KeyDemo  
  4. //  
  5. //  Created by lin on 14-11-26.  
  6. //  Copyright (c) 2014年 linshaolie. All rights reserved.  
  7. //  
  8.   
  9. #import "ViewController.h"  
  10. #import "KeyObject.h"  
  11.   
  12. @interface ViewController ()  
  13. {  
  14.     CGPoint _oldPoint;  
  15.     CGMutablePathRef _pathRef;  
  16.     UIImageView *_imageView;  
  17. }  
  18. @end  
  19.   
  20. @implementation ViewController  
  21.   
  22. - (void)viewDidLoad  
  23. {  
  24.     [super viewDidLoad];  
  25.       
  26.     //Test...  
  27.     KeyObject *key1 = [[KeyObject alloc] initWithHashValue:1];  
  28.     NSMutableDictionary *dic = [[NSMutableDictionary alloc] init];  
  29.     [dic setObject:@"AAA" forKey:key1];  
  30.       
  31.     KeyObject *key2 = [[KeyObject alloc] initWithHashValue:8];  
  32.     [dic setObject:@"BBB" forKey:key2];  
  33.       
  34.     KeyObject *key3 = [[KeyObject alloc] initWithHashValue:3];  
  35.     [dic setObject:@"CCC" forKey:key3];  
  36.       
  37.       
  38.     NSLog(@"%@", [dic objectForKey:key2]);  
  39.     NSLog(@"%@", [dic allKeys]);  
  40.     NSLog(@"%@", [dic allValues]);  
  41. }  
  42. @end  

输出结果:


在调用 setObject: forKey:  后,内部会去调用 key 对象的 hash 方法确定 object 在hash表内的入口位置,然后会调用 isEqual 来确定该值是否已经存在于 NSDictionary中。


猜你喜欢

转载自blog.csdn.net/u011374880/article/details/80407465
今日推荐