你应该知道的Hash和isEqual的深层次解析(一)—— 两个方法剖析以及他们之间的关系

版本记录

版本号 时间
V1.0 2017.09.15

前言

+ (NSUInteger)hash;- (BOOL)isEqual:(id)object;都是NSObject类中的方法,也就是说所有的类都可以调用这两个方法来实现相应的功能,他们两个的功能和深层次原理是什么呢?下面我们开始一起研究。

isEqual方法

1. isEqual方法的基本作用

这个大概大家都知道的作用就是用来比较是否相等,但是具体比较什么相等呢?还有就是和==有什么区别呢?这里有几点需要注意:

  • ==对于基本数据类型,比较的是值是否相等;而对于对象类型,==运算符比较的是对象的地址,也就是说判断是否是一个对象。
  • - (BOOL)isEqual:(id)object;方法比较的是对象是否相等,不是指地址。并且非对象不能使用此方法。

下面我们看一个简单的例子。

- (void)demoEqual
{
    UIColor *color1 = [UIColor colorWithRed:0.5 green:0.5 blue:0.5 alpha:1.0];
    UIColor *color2 = [UIColor colorWithRed:0.5 green:0.5 blue:0.5 alpha:1.0];
    
    NSInteger number1 = 20;
    NSInteger number2 = 20;
    
    if (color1 == color2) {
        NSLog(@"== YES");
    }
    else {
        NSLog(@"== NO");
    }
    
    if (number1 == number2) {
        NSLog(@"== YES");
    }
    else {
        NSLog(@"== NO");
    }
    
    if ([color1 isEqual:color2]) {
        NSLog(@"isEqual YES");
    }
    else {
       NSLog(@"isEqual NO");
    }

}

从上面可以看出来:

  • 由于color1和color2不是一个对象,地址不一样。
  • ==判断的是是否是同一个地址,所以color1 == color2会输出NO,而[color1 isEqual:color2]判断的是对象是否相等,这里显然是相等的。

2. 几种常见的isEqual方法

  • NSString isEqualToString
  • NSDate isEqualToDate
  • NSArray isEqualToArray
  • NSDictionary isEqualToDictionary
  • NSSet isEqualToSet
  • NSAttributedString -isEqualToAttributedString:
  • NSData -isEqualToData:
  • NSHashTable -isEqualToHashTable:
  • NSIndexSet -isEqualToIndexSet:
  • NSNumber -isEqualToNumber:
  • NSOrderedSet -isEqualToOrderedSet:
  • NSTimeZone -isEqualToTimeZone:
  • NSValue -isEqualToValue:

这里需要注意:

  • 当比较上面任意类型的两个对象时,建议尽量使用上面的方法,而不建议使用isEqual方法。

3. NSArray的isEqual方法的内部实现

下面我们就是猜测一下NSArray对象调用isEqual方法的内部可能实现,实际上可能会更复杂,还是直接看代码。

@implementation NSArray (Approximate)

- (BOOL)isEqualToArray:(NSArray *)array 
{
  if (!array || [self count] != [array count]) {
    return NO;
  }

  for (NSUInteger idx = 0; idx < [array count]; idx++) {
      if (![self[idx] isEqual:array[idx]]) {
          return NO;
      }
  }

  return YES;
}

- (BOOL)isEqual:(id)object 
{
  if (self == object) {
    return YES;
  }

  if (![object isKindOfClass:[NSArray class]]) {
    return NO;
  }

  return [self isEqualToArray:(NSArray *)object];
}
@end

4. 自定义对象的isEqual内部实现

这个简单的例子是借鉴别人的,我这里只给出源码,就不自己去验证了。感兴趣的可以自己进行验证。

@interface Person : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSDate *birthday;

@end
- (BOOL)isEqual:(id)object 
{
    if (self == object) {
        return YES;
    }

    if (![object isKindOfClass:[Person class]]) {
        return NO;
    }

    return [self isEqualToPerson:(Person *)object];
}

- (BOOL)isEqualToPerson:(Person *)person 
{
    if (!person) {
        return NO;
    }

    BOOL haveEqualNames = (!self.name && !person.name) || [self.name isEqualToString:person.name];
    BOOL haveEqualBirthdays = (!self.birthday && !person.birthday) || [self.birthday isEqualToDate:person.birthday];

    return haveEqualNames && haveEqualBirthdays;
}

上面是一个简单的例子,猜测的内部实现,实际上可能会比这复杂很多。

5. 其实你可以定义任何对象是否相等

这里说明的是,如果你重写isEqual方法的时候,在哪个类中重写,就用那个类调用isEqual方法,就可以随意实现是否相等。

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

#pragma mark -  Override Base Function

- (void)viewDidLoad
{
    [super viewDidLoad];

    [self demoObject];
}

- (BOOL)isEqual:(id)object
{
    return YES;
}

#pragma mark -  Object Private Function

- (void)demoObject
{
    UIFont *font = [UIFont systemFontOfSize:30.0];
    
    if ([self isEqual:font]) {
        NSLog(@"isEqual YES");
    }
    else {
        NSLog(@"isEqual NO");
    }
}

@end

下面看输出结果

2017-09-15 12:05:34.119 qswwqes[1996:713751] isEqual YES

这里我重写了isEqual方法,所以控制器VC类调用这个方法,就可以和UIFont对象相等了,还是很好玩的,当然我们比并不建议这么做,这里只是玩玩而已,目的是加深大家对这个方法的理解和使用。


Hash

该方法仍然是NSObject中的方法,是一个类方法,也就是说任何类都可以调用这个方法,返回的结果是一个NSInteger值。

1. Hash Table

Hash表其实是一种数据结构,我们平时在数组中查找成员的时候,就是遍历,这是一个解决方案,但是,如果数组的规模如果很大的时候,那么查找起来将会很浪费时间,有没有更好的方法呢?有,那就是借助哈希表。

哈希表的工作原理就是当成员加入到哈希表中时,会给他分配一个哈希值,标识改成员在集合中的位置,通过位置标识可以将查找的时间复杂度降低到很低的一个水平。分配的Hash值,就是用+ (NSUInteger)hash这个类方法计算得到的,并且改方法返回的Hash值最好是唯一的。

Hash Table不是顺序地存储元素(0,1,...,n-1),哈希表在存储器中分配n个位置,并且使用函数来计算该范围内的位置。 哈希函数是确定性的,并且良好的散列函数以相对均匀的分布生成值,而不会太计算昂贵。 当两个不同的对象计算相同的哈希值时,发生哈希冲突。 当这种情况发生时,散列表将从碰撞点寻找,并将新对象放置在第一个可用的位置。 随着哈希表变得更加拥挤,碰撞的可能性增加,这导致花费更多的时间来寻找可用空间(这就是具有均匀分布的哈希函数的好处)。

关于实现自定义哈希函数的最常见的误解之一就是确认结果,认为散列值必须是不同的。

下面看一下基于哈希表查找某个成员的过程。

  • 通过hash值直接找到查找目标的位置。
  • 如果目标位置上有多个相同hash值得成员,在按照数组模式进行查找。

2. hash方法的调用时机

我们可以重写hash方法。

- (NSUInteger)hash
{
    NSUInteger hash = [super hash];
    NSLog(@"hash = %ld", hash);
    return hash;
}

通过重写该方法进行验证,可以发现只有NSSetNSDictionary添加新的对象的时候会调用。NSSetNSDictionary根据hash值查找成员来提高查找效率。

In reality, a simple XOR over the hash values of critical properties is sufficient 99% of the time(对关键属性的hash值进行位或运算作为hash值)。

所以对上面Person类的hash方法可以如下重写实现。

- (NSUInteger)hash 
{
    return [self.name hash] ^ [self.birthday hash];
}

3. hash方法和isEqual的关系

hash方法和isEqual到底是什么关系呢?简单来说就是hash值是对象判等的必要非充分条件。扩展来说,如果两个对象相等,那么他们的hash值一定相等,但是,如果两个对象的哈希值相等是不能一定推出来这两个对象是相等的。

为了优化NSSetNSDictionary判断成员相等的效率,一般会如下:

  • 先判断集合成员的hash值是否和目标的hash值是否相等,如果不等直接就判断不相等,如果相等进入下面步骤。
  • hash值相同的情况下,再对对象进行判等,作为最终的判断结果。

参考文章

[1. Equality Written by Mattt Thompson — August 26th, 2013]
2. iOS开发 之 不要告诉我你真的懂isEqual与hash!

后记

未完,待续~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容