【iOS】—— Foundation框架(三)- 集合(NSSet与NSMutableSet)

7.6 集合(NSSet与NSMutableSet)

NSSet类似于一个罐子,一旦把对象“丢进NSSet”集合,集合里多个对象之间没有明显的顺序。NSSet集合不允许包含相同的元素如果试图把两个相同的元素放在同一个NSSet集合中,则只会保留一个元素。


7.6.1 NSSet的功能与用法

NSSet按Hash算法来存储集合中的元素,因此具有很好的存取和查找性能

NSSet不能保证元素的添加顺序,顺序有可能发生变化。与NSArray相比,NSSet最大的区别是元素没有索引,因此不能根据索引来操作元素,前面介绍NSArray时介绍的所有有关索引的方法都不适用于NSSet。

实际上,NSArray与NSSet依然有大量的相似之处,NSSet与NSArray在如下方面的调用机制都非常相似:
  • 都可通过count方法获取集合元素的数量。
  • 都可通过快速枚举来遍历集合元素。
  • 通过objectEnumerator方法获取NSEnumerator枚举器对集合元素进行遍历。由于NSSet集合本身就是无序的,因此,提供反向迭代器没有意义。
  • 都提供了makeObjectsPerformSelector:、makeObjectsPerformSelector:withObject:方法对集合元素整体调用某个方法,以及enumerateObjectsUsingBlock:、enumerateObjectsWithOptions:usingBlock对集合整体或部分元素迭代执行代码块。
  • 都提供了valueForKey:和setValue:forKey:方法对集合元素整体进行KVC编程。
  • 都提供了集合的所有元素和部分元素进行KVO编程的方法。

从功能上来看,NSArray相当于NSSet的子类——虽然Objective-C在语法中并没有提供这种支持。
NSArray与NSSet的区别:
NSSet代表集合元素无索引且不允许重复的集合
NSArray则代表集合元素有索引且允许重复的集合
因此,可认为NSSet代表通用的集合,而NSArray则在NSSet基础上扩展了功能:主要是让集合元素有索引,因此,NSArray允许通过索引来操作集合元素。

NSSet的基本用法:

NSSet同样提供了类方法和实例方法来初始化NSSet集合,其中以set开头的方法是类方法,以init开头的方法是实例方法

获取NSSet对象后,接下来就可以调用NSSet的方法来访问集合元素、遍历集合元素和筛选集合元素。

除了前面介绍的与NSArray相似的方法之外,NSSet包含如下常用的方法:
  • setByAddingObject::向集合中添加一个新元素,返回添加元素后的新集合。
  • setByAddingObjectsFromSet::使用NSSet集合向集合中添加多个新元素,返回添加元素后的新集合。
  • setByAddingObjectsFromArray::使用NSArray集合向集合中添加多个新元素,返回添加元素后的新集合。
  • allObjects:返回该集合中所有元素组成的NSArray。
  • anyObject:返回该集合中的某个元素。该方法返回的元素是不确定的,但该方法并不保证随机返回集合元素。

虽然该方法每次返回的元素是不确定的,但是不是随机的。只要一个NSSet没有发生改变,无论多少次调用该方法,返回的总是同一个集合元素。

  • containsObject::判断集合是否包含指定元素。
  • member::判断该集合是否包含与该参数相等的元素,如果包含,返回相等的元素;否则返回nil。
  • objectsPassingTest::需要传入一个代码块对集合元素进行过滤,满足该代码块条件的集合元素被保留下来并组成一个新的NSSet集合作为返回值。
  • objectsWithOptions:passingTest::与前一个方法的功能基本相似,只是可以额外地传入一个NSEnumerationOptions迭代选项参数。
NSSet集合的基本用法:
//定义一个函数,该函数用于把NSSet集合转换为字符串
NSString* NSCollectionToString(id collection) {
    NSMutableString* result = [NSMutableString
                               stringWithString:@"["];
    for (id object in collection) {
        [result appendString:[object description]];
        [result appendString:@","];
    }
    NSUInteger len = [result length];   //获取字符串长度
    //去掉字符串最后的一个字符
    [result deleteCharactersInRange:NSMakeRange(len - 1, 1)];
    [result appendString:@"]"];
    return result;
}

int main(int argc, char* argv[]) {
    @autoreleasepool {
        //用4个元素初始化NSSet集合
        //故意传入两个相等的元素,NSSet集合只会保留一个元素
        NSSet* set1 = [NSSet setWithObjects:@"疯狂iOS讲义", @"疯狂Android讲义",
                       @"疯狂Ajax讲义", @"疯狂iOS讲义", nil];
        //程序输出set1集合中元素的个数为3
        NSLog(@"set1集合中的元素个数为:%ld", [set1 count]);
        NSLog(@"set1集合:%@", NSCollectionToString(set1));
        NSSet* set2 = [NSSet setWithObjects:@"孙悟空", @"疯狂Android讲义",
                       @"猪八戒", nil];
        NSLog(@"set2集合:%@", NSCollectionToString(set2));
        //向set1集合中添加单个元素,将添加元素后生成的新集合赋给set1
        set1 = [set1 setByAddingObject:@"Struts 2.1权威指南"];
        NSLog(@"添加一个元素后:%@", NSCollectionToString(set1));
        //使用NSSet集合向set1集合中添加多个元素,相当于计算两个集合的并集
        NSSet* s = [set1 setByAddingObjectsFromSet:set2];
        NSLog(@"set1与set2的并集:%@", NSCollectionToString(s));
        //计算两个NSSet集合是否有交集
        BOOL b = [set1 intersectsSet:set2];
        //将输出代表YES的1
        NSLog(@"set1与set2是否有交集:%d", b);
        //判断set2是否是set1的子集
        BOOL bo = [set2 isSubsetOfSet:set1];
        //将输出代表NO的0
        NSLog(@"set2是否为set1的子集:%d", bo);
        //判断NSSet集合是否包含指定元素
        BOOL bb = [set1 containsObject:@"疯狂Ajax讲义"];
        //输出代表YES的1
        NSLog(@"set1是否包含“疯狂Ajax讲义”:%d", bb);
        //下面两行代码将取出相同的元素,但取出那个元素是不确定的
        NSLog(@"set1取出一个元素:%@", [set1 anyObject]);
        NSLog(@"set1取出一个元素:%@", [set1 anyObject]);
        //使用代码块对集合元素进行过滤
        NSSet* filteredSet = [set1 objectsPassingTest:
                              ^BOOL(id obj, BOOL* stop)
        {
            return (BOOL)([obj length] > 8);
        }];
        NSLog(@"set1中元素的长度大于8的集合元素有:%@",
              NSCollectionToString(filteredSet));
    }
    return 0;
}

NSArray也提供了isEqualToArray:方法用于判断两个NSArray包含的集合元素是否相等。由于在前面介绍时没有给出方法列表,故此处集中列出。

输出结果为:
352

:因为NSSet类似于一个罐子,所以放进去的元素是没有一定顺序的,所以其输出时也是没有顺序的,但元素还是当时录入的元素。


7.6.2 NSSet判断集合元素重复的标准

当向NSSet集合中存入一个元素时,NSSet会调用该对象的Hash方法来得到该对象的hashCode值,然后根据该HashCode值决定该对象在底层Hash表中的系统位置,如果根据hashCode计算出来该元素在底层Hash表中的存储位置已经不相同,那么系统自然将它们存在不同的位置。

如果两个元素的hashCode相同,接下来就要通过isEqual:方法判断两个元素是否相等,如果有两个元素通过isEqual:方法比较返回NO,NSSet依然认为它们不相等,NSSet会把他们都存在底层Hash表的同一个位置,只是将在这个位置形成链;如果它们通过isEqual:比较返回YES,那么NSSet认为两个元素相等,后面的元素添加失败。

HashSet集合判断两个元素相等的标准如下:
  • 两个对象通过isEqual:方法比较返回YES。
  • 两个对象的hash方法返回值也相等。
测试NSSet集合判断两元素相等:
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface FKUser : NSObject
@property (nonatomic, copy) NSString* name;
@property (nonatomic, copy) NSString* pass;
- (id) initWithName: (NSString*) aName pass: (NSString*) aPass;
- (void) say: (NSString*) content;
@end

NS_ASSUME_NONNULL_END

#import "FKUser.h"

@implementation FKUser
@synthesize name;
@synthesize pass;
- (id) initWithName:(NSString *) aName pass:(NSString *) aPass {
    if (self = [super init]) {
        self.name = aName;
        self.pass = aPass;
    }
    return self;
}
- (void) say:(NSString *) content {
    NSLog(@"%@说:%@", self.name, content);
}
//重写isEqual:方法,重写该方法的比较标准是,
//如果两个FKUser的name、pass相等,即可认为它们相等
- (BOOL) isEqual: (id) other {
    if (self == other) {
        return  YES;
    }
    //如果两个的类是相同的话就进入
    if ([other class] == FKUser.class) {
        FKUser* target = (FKUser*)other;
        return [self.name isEqualToString:target.name]
        && [self.pass isEqualToString:target.pass];
    }
    return  NO;
}
//重写description方法,可以直接看到FKUser对象的状态
- (NSString*) description {
    return [NSString stringWithFormat:
            @"<FKUser[name = %@, pass = %@]>"
            , self.name, self.pass];
}
@end


//定义一个函数,该函数用于把NSSet集合转换为字符串
NSString* NSCollectionToString(id collection) {
    NSMutableString* result = [NSMutableString
                               stringWithString:@"["];
    for (id object in collection) {
        [result appendString:[object description]];
        [result appendString:@","];
    }
    NSUInteger len = [result length];   //获取字符串长度
    //去掉字符串最后的一个字符
    [result deleteCharactersInRange:NSMakeRange(len - 1, 1)];
    [result appendString:@"]"];
    return result;
}

int main(int argc, char* argv[]) {
    @autoreleasepool {
        NSSet* set = [NSSet setWithObjects:
                      [[FKUser alloc] initWithName:@"sun" pass:@"123"],
                      [[FKUser alloc] initWithName:@"bai" pass:@"345"],
                      [[FKUser alloc] initWithName:@"sun" pass:@"123"],
                      [[FKUser alloc] initWithName:@"tang" pass:@"178"],
                      [[FKUser alloc] initWithName:@"niu" pass:@"155"], nil];
        NSLog(@"set集合元素的个数:%ld", [set count]);
        NSLog(@"%@", NSCollectionToString(set));
    }
    return 0;
}

输出结果为:
324

上面的输出结果中,虽然第一个和第三个的name、pass相等,但NSSet依然将它们当成两个元素。这是因为虽它们两个的isEqual:比较会返回YES,但由于程序并没有重写它们的Hash方法,因此这两个FKUser的hashCode值依然不相等,NSSet依然认为它们不相等,NSSet会同时存储两个元素。

重写Hash方法,重写Hash方法时根据name、pass两个属性的值进行计算,代码如下:
//重写Hash方法,重写该方法的比较标准是,
//如果两个FKUser的name、pass相等,两个FKUser的Hash方法返回值相等
- (NSUInteger) hash {
    NSLog(@"===hash===");
    NSUInteger nameHash = name == nil ? 0 : [name hash];
    NSUInteger passHash = pass == nil ? 0 : [pass hash];
    return nameHash * 31 + passHash;
}

NSString已经重写了Hash方法,只要两个字符串所包含的字符序列相同,那么两个NSString的Hash方法返回值就相等。

输出结果如下:
345

从上面的输出可以看到,程序输出“=== hash ===”5次,这表明NSSet每次添加一个集合元素,总会先调用该元素的Hash方法——所有的对象都继承了NSObject类,因此,所有的对象都有Hash方法。
注意如果需要把一个对象放入NSSet中,如果重写该对象对应类的isEqual:方法,也应该重写其Hash方法,其 规则 是:如果两个对象通过isEqual:方法比较返回YES时,这两个对象的Hash方法返回值也应该相同。

如果需要某个类的对象保存到NSSet集合中,重写这个类的isEqual:方法和Hash方法时,应该尽量保证两个对象通过isEqual:比较返回YES时,它们的Hash方法返回值也相等。

重写Hash方法的基本规则:
  • 程序运行过程中,同一个对象多次调用Hash应该返回相同的值。
  • 当两个对象通过isEqual:方法比较返回YES时,这两个对象的Hash应返回相等的值。
  • 对象中作为isEqual:比较标准的实例变量,都应该用来计算hashCode值。
重写Hash方法的一般步骤:
  • 1. 把对象内每个有意义的实例变量(即每个用作isEqual:比较标准的实例变量)计算出一个int类型的hashCode值。
  • 2. 用第一步计算出来的多个hashCode组合计算出一个hashCode值返回。例如如下代码:

return [f1 hash] + [f2 hash];

  • 如果为了避免直接相加产生偶然相等(两个对象f1、f2实例变量并不相等,但它们的hashCode和恰好相等),可以通过为各实例变量的hashCode值乘以任意一个质数后再相加,例如如下代码:

return [f1 hash] * 31 + [f2 hash];


7.6.3NSMutableSet的功能与用法

NSMutableSet继承了NSSet,它代表一个集合元素可变的NSSet集合。由于NSMutableSet可以动态添加集合元素,因此,创建NSSet集合时可指定底层Hash表的初始容量。

NSMutableSet在NSSet基础上增加了添加元素、删除元素的方法,并增加了对集合计算交集、并集、差集的方法:
  • addObject::向集合中添加单个元素。
  • removeObject::从集合中删除单个元素。
  • removeAllObjects:删除集合中的所有元素。
  • addObjectsFromArray::使用NSArray数组作为参数,向NSSet集合中添加参数数组中的所有元素。
  • unionSet::计算两个NSSet集合的并集。
  • minusSet::计算两个NSSet集合的差集。
  • intersectSet::计算两个NSSet集合的交集。
  • setSet::用后一个集合的元素替换已有集合中的所有元素。
NSMutableSet集合的用法:
//定义一个函数,该函数用于把NSSet集合转换为字符串
NSString* NSCollectionToString(id collection) {
    NSMutableString* result = [NSMutableString
                               stringWithString:@"["];
    for (id object in collection) {
        [result appendString:[object description]];
        [result appendString:@","];
    }
    NSUInteger len = [result length];   //获取字符串长度
    //去掉字符串最后的一个字符
    [result deleteCharactersInRange:NSMakeRange(len - 1, 1)];
    [result appendString:@"]"];
    return result;
}

int main(int argc, char* argv[]) {
    @autoreleasepool {
        //创建一个初始容量为10的Set集合
        NSMutableSet* set = [NSMutableSet setWithCapacity:10];
        [set addObject:@"疯狂iOS讲义"];
        NSLog(@"添加一个元素后:%@", NSCollectionToString(set));
        [set addObjectsFromArray:[NSArray
                                  arrayWithObjects:@"疯狂XML讲义",
                                  @"疯狂Ajax讲义", @"疯狂Android讲义", nil]];
        NSLog(@"使用NSArray添加3个元素后:%@", NSCollectionToString(set));
        [set removeObject:@"疯狂XML讲义"];
        NSLog(@"删除一个元素后:%@", NSCollectionToString(set));
        //再创建一个Set集合
        NSSet* set2 = [NSSet setWithObjects:@"孙悟空", @"疯狂iOS讲义", nil];
        //计算两个集合的并集,直接改变set集合的元素
        [set unionSet:set2];
        //计算两个集合的差集,直接改变set集合的元素
//        [set minusSet:set2];
        //计算两个集合的交集,直接改变set集合的元素
//        [set intersectsSet:set2];
        //用set2的集合元素替换set的集合元素,直接改变set集合的元素
//        [set setSet:set2];
        NSLog(@"%@", NSCollectionToString(set));
    }
    return 0;
}

输出结果为:
gdfg

上面最后一行输出的就是两个集合计算并集的结果。除此之外,程序中最后几行代码还可以计算两个集合的交集、差集,以及用后面集合的元素替换现有集合中的所有元素等功能。


7.6.4 NSCountedSet的功能与用法

NSCountedSet是NSMutableSet的子类,NSCountedSet为每个元素额外维护一个添加次数的状态当程序向NSCountedSet中添加一个元素时,如果NSCountedSet集合中不包含该元素,NSCountedSet真正接纳该元素,并将该元素的添加次数标注为:1;当程序向NSCountedSet中添加一个元素时,如果NSCountedSet集合中已经包含该元素,NSCountedSet不会接纳该元素,但会将该元素的添加次数加1。

当程序从NSCountedSet集合中删除元素时,NSCountedSet只是将该元素的添加次数减1,只有当该元素的添加次数变为0时,该元素才会真正从NSCountedSet集合中删除。

NSCountedSet提供返回某个元素添加次数的方法:
  • countForObject::获取指定元素的添加次数。
NSCountedSet的功能和用法:
//定义一个函数,该函数用于把NSArray或NSSet集合转换为字符串
NSString* NSCollectionToString(id collection) {
    NSMutableString* result = [NSMutableString
                               stringWithString:@"["];
    for (id object in collection) {
        [result appendString:[object description]];
        [result appendString:@","];
    }
    NSUInteger len = [result length];   //获取字符串长度
    //去掉字符串最后的一个字符
    [result deleteCharactersInRange:NSMakeRange(len - 1, 1)];
    [result appendString:@"]"];
    return result;
}

int main(int argc, char* argv[]) {
    @autoreleasepool {
        NSCountedSet* set = [NSCountedSet setWithObjects:@"疯狂iOS讲义",
                             @"疯狂Android讲义", @"疯狂Ajax讲义", nil];
        [set addObject:@"疯狂iOS讲义"];
        [set addObject:@"疯狂iOS讲义"];
        //输出集合元素
        NSLog(@"%@", NSCollectionToString(set));
        //获取指定元素的添加顺序
        NSLog(@"“疯狂iOS讲义“的添加次数为:%ld",
                [set countForObject:@"疯狂iOS讲义"]);
        //删除元素
        [set removeObject:@"疯狂iOS讲义"];
        NSLog(@"删除“疯狂iOS讲义”一次后的结果:%@",
              NSCollectionToString(set));
        NSLog(@"删除“疯狂iOS讲义”1次后的添加次数为:%ld",
              [set countForObject:@"疯狂iOS讲义"]);
        //重复删除元素
        [set removeObject:@"疯狂iOS讲义"];
        [set removeObject:@"疯狂iOS讲义"];
        NSLog(@"删除“疯狂iOS讲义”3次后的结果:%@",
              NSCollectionToString(set));
    }
    return 0;
}

输出结果为:
456

从上面程序可以看出,想要在NSCountedSet集合中真正删除一个元素,就必须让它的添加次数变为0。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值