美文网首页
NSArray与NSSet,equal与hash

NSArray与NSSet,equal与hash

作者: 一叶__知秋 | 来源:发表于2017-03-22 11:43 被阅读445次

    NSArray和NSSet都属于集合用于存储对象,不同的是NSArray是有序的,有序的意思是它内存中存储位置是连续的。

    NSSet是无序的,在内存中存储方式是不连续的。

    NSSet其实是一个hash table,当需要检索它内部的某个对象的时候,根据对象的hash值可以在hash table中快速检索出这个对象对应的值,而在NSArray中想要检索某个值得话需要遍历它内部的所有对象,这样比较nsset的效率就要比NSArray高了,NSSet内部存储的值必须是不同的。

    NSArray和NSSet都是类,所以他们只能添加对象,如果需要加入基本数据类型就需要首先把这些基本数据类型封装成对象。

    NSSet查询某个元素的速度会快,但是因为它是无序的所以它无法执行按index取值或者是按index插入的操作。

    我们日常项目中经常会需要比较两个对象是否相等,这个时候就要用到isEqual方法,对于基本类型==运算符比较的是两个值是否相等,而对于对象来说==比较的是这两个对象的内存地址是否相同。

    但是对于我们自定义的类来说,isEqual方法比较的是两个对象的内存地址,让我们看以下代码:

    #import@interface Person : NSObject

    @property (nonatomic,copy)NSString *name;

    @property (nonatomic,copy)NSString *age;

    -(instancetype)personWithName:(NSString *)name age:(NSString*)age;

    @end

    #import "Person.h"

    @implementation Person

    -(instancetype)personWithName:(NSString *)name age:(NSString*)age{

    if(self == [super init]){

    self.name=name;

    self.age=age;

    }

    return self;

    }

    我们自定义一个Person类,它有两个属性name和age,然后我们创建两个person类的实例对象p1和p2来比较他们是否相等。

    Person *p1=[[Person alloc]personWithName:@"james" age:@"11"];

    Person *p2=[[Person alloc]personWithName:@"james" age:@"11"];

    NSLog(@"----------%d-------------",[p1 isEqual:p2]);

    我们看到这两个实例的名字都是james,年龄都是11,控制台输出:

    ----------0-------------

    因为这两个对象的地址不同所以isEqual比较的结果是不同,但是我们日常工作中可能实际需要就是如果两个对象内部的属性相等我们就想让这两个对象相等,那么我们可以通过重写isEqual方法来实现我们的这个需求。

    Person.m

    - (BOOL)isEqual:(Person *)other {

    BOOL isMyClass    = [other isKindOfClass:self.class];

    BOOL isEqualToName = [other.name isEqualToString:self.name];

    BOOL isEqualToAge  = [other.age isEqualToString:other.age];

    if(isMyClass && isEqualToName && isEqualToAge) {

    returnYES;

    }

    returnNO;

    }

    重写完以后我们再执行刚才的比较方法:

    ----------1-------------

    目前来看似乎是实现了我们的需要了,两个值一样的对象判断确实相等了,大功告成,收工。

    等等,这样真的就万事大吉了吗,我们都知道在字典中如果存储两个key值一样的键值对,那么后面的这个一定会覆盖掉之前的键值对的值,我们再做一个实验

    Person *p1=[[Person alloc]personWithName:@"james" age:@"11"];

    Person *p2=[[Person alloc]personWithName:@"james" age:@"11"];

    Person *p3=[[Person alloc]personWithName:@"kobe" age:@"11"];

    NSMutableDictionary *dic1 = [[NSMutableDictionary alloc]init];

    [dic1 setValue:@"a" forKey:p1];

    [dic1 setValue:@"b" forKey:p2];

    [dic1 setValue:@"c" forKey:p3];

    NSLog(@"person1----%@", [dict objectForKey:p1]);

    NSLog(@"person2----%@", [dict objectForKey:p2]);

    NSLog(@"person3----%@", [dict objectForKey:p3]);

    NSLog(@"dict count: %ld", dic1.count);

    这里穿插一个技术点,由于一对键值存入字典中之后,key是不能随意改变的,这样会造成value的丢失,所以一个自定义对象作为key存入字典,必定要深拷贝,所以作为key值得对象要遵守nscopying协议,重写copywithzone方法,否则上面这一段代码会报p1没有copywithzone方法。

    - (Person *)copyWithZone:(NSZone *)zone {

    Person *person = [[Person allocWithZone:zone] init];

    person.name = [self.name copyWithZone:zone];

    person.age = [self.age copyWithZone:zone];

    return person;

    }

    浅拷贝与深拷贝 

    理论上来说我们添加了两个一样key值得对象应该返回的是2,但是实际上打印结果是随机的,有时是2,有时是3,三个对象对应的value也不相同,非常神奇,究竟是什么原因造成了这种灵异事件呢?

    编程中没有灵异事件,编程中没有灵异事件,编程中没有灵异事件,
    重要的事情说三遍。

    原来在对象存入key时,编译器每次都要进行hash/equal验证,如果为相同对象,则不增加键值对数量,直接覆盖掉之前的键值对,我们虽然重写了isEqual方法,但是我们没有重写hash方法,因为p1和p2的hash code是不同的,所以会被认为不是重复的key值,会继续插入,造成了能存不能查的情况。同理再向一个nsset内插入值的时候也会执行对象判等的操作,p1和p2尽管值相同但是还是会被插入同一个nsset。

    解决的办法就是同样重写hash方法,如果我们只是单纯的返回[super hash],这个时候p1和p2由于内存地址不同所有还是可以同时插入nsset中,那么应该怎么重写hash方法呢

    大神Mattt ThompsonEquality中给出的结论就是

    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.age hash];

    }

    这时候在运行代码,不论是字典还是NSSet中我们发现p1和p2都会被当做相同的对象来操作。

    以上。

    参考资料1

    参考资料2

    参考资料3 

    相关文章

      网友评论

          本文标题:NSArray与NSSet,equal与hash

          本文链接:https://www.haomeiwen.com/subject/jutonttx.html