IsEqual與Hash我的理解

IsEqual與Hash我的理解

isEqual

NSObject類的實例方法: - (BOOL)isEqual:(id)object 主要是根據對象的內存地址來判斷兩個對象是否相等,這裏與 ==效果相同。code

  • isEqualToStringorm

    (BOOL)isEqualToString:(NSString *)aString 是NSString類的實例方法,它主要用於比較兩個字符串中的內容是否相同,而非比較兩個字符串所在內存地址。該方法經常使用。對象

  • 自定義類的isEqualip

    在開發需求中,若是有比較兩個類對象是否具備等同性時,一般會根據需求來對父類的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屬性。string

可是由於不一樣對象的Hash值會有衝突的可能性(相同的對象Hash值必定相同,這裏的相同指的是內存地址相同),因此最後若是哈希值發生衝突的話,則會調用類中的isEqual方法進行等同性判斷。hash

既然都會調用isEqual方法,那麼爲何不直接按順序遍歷set容器,依次調用isEqual方法?it

答:首先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方法都是根據對象的內存地址獲得的結果。
相關文章
相關標籤/搜索