• IsEqual与Hash个人理解


    IsEqual与Hash个人理解

    isEqual

    NSObject类的实例方法: - (BOOL)isEqual:(id)object 主要是根据对象的内存地址来判断两个对象是否相等,这里与 ==效果相同。

    • isEqualToString

      (BOOL)isEqualToString:(NSString *)aString 是NSString类的实例方法,它主要用于比较两个字符串中的内容是否相同,而非比较两个字符串所在内存地址。该方法常用。

    • 自定义类的isEqual

      在开发需求中,如果有比较两个类对象是否具有等同性时,通常会根据需求来对父类的isEqual进行改写,这里的做法通常是:

      1. 先创建自定义的等同性判断方法,代码如下:

        -(BOOL)isEqualToStudent:(Student *)otherStudent{
            if(self == otherStudent)
                return YES;
            if(![_name isEqualToString:otherStudent.name])
                return NO;
            if(_age != otherStudent.age)
                return NO;
            return YES;
        }
        
      2. 在编写完判定方法后,应改写类中的isEqual实例方法。如果传入的对象为同一个类时,采用刚刚写的自定义方法来判定,否则就交给父类判断:

        -(BOOL)isEqual:(id)object{
            if([self class] == [object class])
                return [self isEqualToStudent:(Student *)object];
            else
                return [super isEqual:object];
        }
        

    Hash

    Hash主要是用于NSMutableSet中的判断添加的新对象是否已经存在了容器当中,如果不存在,则将新对象放入Set容器中,而判断的依据就是根据实例对象的Hash属性。

    但是因为不同对象的Hash值会有冲突的可能性(相同的对象Hash值一定相同,这里的相同指的是内存地址相同),所以最后如果哈希值发生冲突的话,则会调用类中的isEqual方法进行等同性判断。

    既然都会调用isEqual方法,那么为什么不直接按顺序遍历set容器,依次调用isEqual方法?

    答:首先set容器是非顺序容器,它的内存空间结构不是按顺序存储的,如果按插入的顺序遍历,开支很大(当然set的插入是无顺序的)。其次除非是大量数据的存储才会发生冲突的可能,在少量数据的情况下基本上不会出现依次遍历冲突对象的isEqual方法的冲突情况。这样看来以O(1)的时间复杂度就可以完成的任务,当然就选择是它了。

    在NSObject对象中,Hash值是根据对象所在的内存空间来进行计算的。所以在修改了isEqual方法后,如果没有修改Hash的setter方法,系统在执行set容器操作该对象时,仍然会以内存地址为准来判断两个对象是否相同。所以为了防止出现这种情况,在修改等同性判断方法的时候应顺便修改Hash的setter方法

    • 自定义Hash方法

      当自定义完isEqual后,一般为了防止后面的开发用到有关set容器出现意外,所以一般都会对Hash的Setter方法进行修改。代码如下:

      -(NSUInteger)hash{
          //这里的字符串只要能体现出类对象的name和age即可(依需求而定)
          NSString* stringToHash=[NSString stringWithFormat:@"%@ %i",_name,_age];
          return [stringToHash hash];
      }
      

      但这里有个弊端,因为在这里新建一个字符串的开销很大(与返回一个属性值相比较)。所以一般采取下面这种策略:

      -(NSUInteger)hash{
          NSUInteger nameHash=[_name hash];
          NSUInteger ageHash=_age;
          return nameHash^ageHash;
      }
      

      这种方法既考虑了开销也考虑了属性的相关性和随机性。---------Effective OC

    • Hash的调用顺序

      例子如下:

      //Student.m
      -(BOOL)isEqualToStudent:(Student *)otherStudent{
          if(self == otherStudent){
              NSLog(@"(%@:%i)与(%@:%i)冲突了",_name,_age,otherStudent.name,otherStudent.age);
              return YES;
          }
          if(![_name isEqualToString:otherStudent.name])
              return NO;
          if(_age != otherStudent.age)
              return NO;
          NSLog(@"[%@:%i]与[%@:%i]冲突了",_name,_age,otherStudent.name,otherStudent.age);
          return YES;
      }
      
      -(BOOL)isEqual:(id)object{
          if([self class] == [object class])
              return [self isEqualToStudent:(Student *)object];
          else
              return [super isEqual:object];
      }
      
      -(NSUInteger)hash{
          return _age;
      }
      
      -(NSString *)description{
          return [NSString stringWithFormat:@"%@:%i",_name,_age ];
      }
      
      //main.m
      Student* stu1 = [[Student alloc] initWithName:@"小明" age:10];
      Student* stu2 = [[Student alloc] initWithName:@"小明" age:10];
      Student* stu3 = [[Student alloc] initWithName:@"小明" age:0];
      
      NSMutableSet* set = [NSMutableSet set];
          
      [set addObject:stu1]; //步骤1
      [set addObject:stu2]; //步骤2
      [set addObject:stu3]; //步骤3
      NSLog(@"%@",set);
      

      输出结果:

      2020-05-08 22:50:12.172207+0800 effective-OC-test[83370:76838657] [小明:10]与[小明:10]冲突了
      2020-05-08 22:50:12.172542+0800 effective-OC-test[83370:76838657] {(
          小明:0,
          小明:10
      )}
      Program ended with exit code: 0
      
      1. 当执行步骤1时,会先去调用hash方法得到对象的哈希值,因为set容器为空,所以直接根据hash值将对象存入内存当中。stu1成功存入容器中;

      2. 当执行步骤2时,同样会先去调用hash方法得到哈希值,因为这里的hash值与age有关,stu1.age==stu2.age,所以两个对象的哈希值相同,这时候就回去调用isEqual方法去判断是否两个对象是等同的。又因为我这里设置的当姓名和年龄一致时,即判断为同一对象,所以这里会打印出冲突。并且不会将stu2存入容器中;

      3. 当执行步骤3时,调用完hash方法后,因为age不同,所以得到的hash值不同(很小几率会出现相同),从而直接通过的到的hash值去分配内存空间。stu3成功存入容器中。

      4. 如果这里将isEqualToStudent的判断条件修改一下,将年龄处修改为:

        if(_age == otherStudent.age)
                return NO;
        

        这时候,再执行一遍,输出如下:

        2020-05-08 23:02:13.177534+0800 effective-OC-test[84327:76859194] {(
            小明:0,
            小明:10,
            小明:10
        )}
        

        这时候就会发现stu2已经成功存入了容器中,因为在hash值冲突后调用isEqualToStudent方法后,因为两个年龄一致,返回了NO,说明两个对象这时候已经被当成了不同的两个对象。

    总结

    1. isEqual方法是用来判断类对象的等同性。在自定义等同性判断时,如果判断对象与该对象同属一个类时,就调用自己编写的判定方法,否则就交给父类的isEqual来解决。
    2. Hash值一般与容器相关,特别是Set容器。set容器通过对象的哈希值来分配内存地址,当遇到hash冲突后时会调用isEqual来进行二次验证。
    3. 原生NSObject类的Hash值和isEqual方法都是根据对象的内存地址得到的结果。
  • 相关阅读:
    Linux iptables 配置规则
    Java之品优购课程讲义_day06(7)
    TCP的三次握手与四次挥手
    区块链挖矿演变史,一键挖矿逐渐成主流
    Java KeyStore 用命令生成keystore文件
    SpringBoot | 第六章:常用注解介绍及简单使用
    PHP开发模式之-单例模式
    Ansible笔记
    CentOS7中搭建cobbler自动装机服务
    号称“新至强,可拓展,赢当下”的Xeon可拓展处理器有多逆天?
  • 原文地址:https://www.cnblogs.com/Solist/p/12920680.html
Copyright © 2020-2023  润新知