NSDictionary是基于key - value 方式,把key映射到一個hash表中實現的
key
需要支持NSCopying協議,實際上不支持也可以作為key,但在swift中就必須要支持,支持NSCopying的原因在于,NSDicitionary是NSCopying的,在copy一個dictionary的時候需要key是NSCopying的(不信你用一個UIImage做key也是可以正常運行的,只要不copy)
添加key-value的時候不會對key做深復制,以下代碼打印的內容是一樣的
NSString *key = @"key";
NSLog(@"%p",key);
NSDictionary *dic = @{key:@(1)};
NSLog(@"%p",dic.allKeys.firstObject);
所以添加非NSCopying的對象也是可以的,因為對象作為key的必要條件是對象實現了:
- (NSUInteger)hash;
2018.8.3更新
上面這個說法僅限于創建NSDictionary的時候,如果是在NSMutableDictionary調用setObject方法,系統會強制復制一份,也就是會調用key的copyWithZone方法創建一個新的對象,setObject的key沒支持NSCopying的話就會崩,同理setValue的key也需要支持NSCopying
方法,而NSObject本身就已經實現了這個方法,返回的是它本身的地址
如果你這樣打印:
NSObject *obj = NSObject.new;
NSLog(@"%@",obj.hash);
你會發現打印出來的內容跟
NSLog(@"%@",obj);
是一樣的,就側面證明了NSObject的默認-hash打印的就是它本身的地址
而如果這樣打印一個NSString的話會直接崩掉。。。說明NSString改寫了hash方法返回了基于string計算的hash,所以只要你傳入了內容一樣的字符串都能拿到相應的value
并且NSString是經過特別優化的,會經可能的均勻hash的平均長度,使hash表盡可能的小
如果你要通過key查找value,需要key實現了:
- (BOOL)isEqual:(id)object;
同樣NSObject也實現了,默認是通過對比自己的地址
value沒什么好講的,只要是個NSObject子類都行
hash表的實現
NSDictionary生成hash表使用的是拉鏈法,可以理解為“鏈表的數組”即:
根結構為數組,每個元素為鏈表
添加key的時候,會把key的hash對根結構的長度取余,結果作為根結構的下標,再把key插入到下標對應的鏈表元素中
一般不會在這個時候排序插入,而是直接插在鏈表頭部以提高性能,當鏈表元素過多時才排序轉換成平衡二叉樹(該處理來自http://ios.jobbole.com/87716/,里面提到NSDictionary和java的HashMap實現類似,而HashMap是轉換成紅黑樹)
解決hash沖突
hash沖突即存在多個hash一樣的key
一般來說拉鏈法需要涉及到解決hash沖突,但巧就巧在,ObjC中一般對象的hash就是它本身的地址,所以幾乎是不可能沖突的,對于NSString這類重寫了hash方法的,會同時要求重寫isEqual方法。當真的遇到hash沖突的話,NSDictionary插入時會無視沖突,而在取數據時,在找到hash后會多一步通過isEqual對比是不是需要的key,如果不是就繼續往下找,一般來說出現hash沖突的key都會在同一個鏈表的相鄰位置,所以查找的消耗會非常的低
同時NSHashTable,NSSet,NSMapTable的實現都是基于拉鏈法生成的hash表