关闭

oc集合类库

4493人阅读 评论(0) 收藏 举报
分类:

译自:http://www.objc.io/issue-7/collections.html

(注:我不是全文翻译,只译重要部分)

NSArray

NSArray是最常使用的有序集合,还有自己的语法糖@[...]取代[NSArray arrayWithObjects:..., nil]。

NSArray实现了objectAtIndexedSubscript,从而我们可以使用类C语法array[0]取代[array objectAtIndex:0]。

Useful Methods
大多数NSArray函数使用isEqual来判断数组元素相等(例如containsObject)。但有个函数indexOfObjectIdenticalTo是用来做指针比较的,若某个对象是该集合的元素,调用这个函数可以加快检索。

ios7新引入了firstObject(其实ios4起就有了,只是ios7公开化了,ios4以上都可以调用),空数组调用会返回nil,而array[0]则会抛出越界异常。

创建可变数组的简化方式细节:如果用一个可能为nil的源数组来创建可变数组,通常代码是这样:
NSMutableArray *mutableObjects = [array mutableCopy];
if (!mutableObjects) {
    mutableObjects = [NSMutableArray array];
}
或者三目运算符:
NSMutableArray *mutableObjects = [array mutableCopy] ?: [NSMutableArray array];

更好的方式是使用arrayWithArray,即使源数组为nil(那mutableObjects也为nil)
NSMutableArray *mutableObjects = [NSMutableArray arrayWithArray:array];

两者性能差不多,copy稍微快一点,使用arrayWithArray也不会造成性能瓶颈。题外话:不要使用[@[] mutableCopy],[NSMutableArray array]易读些(原文是这么写的,但易读这个理由较牵强,我反而觉得用mutableCopy更简便些

反转数组相当简单:array.reverseObjectEnumerator.allObjects,但并没有randomObjectEnumerator这样的函数,可以自定义enumerator或者使用开源类(我以前做牌类游戏,洗牌算法有用到java的Collections.shuffle函数,还专门去看了下函数源代码如何实现,挺简单的)

Sorting Arrays(排序)

排序有若干个函数。
若元素是字符串,首选sortedArrayUsingSelector:
NSArray *array = @[@"John Appleseed", @"Tim Cook", @"Hair Force One", @"Michael Jurewitz"];
NSArray *sortedArray = [array sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];

对于NSNumber同样适用:
NSArray *numbers = @[@9, @5, @11, @3, @1];
NSArray *sortedNumbers = [numbers sortedArrayUsingSelector:@selector(compare:)];

也可以使用基于函数指针的函数:
- (NSData *)sortedArrayHint;
- (NSArray *)sortedArrayUsingFunction:(NSInteger (*)(id, id, void *))comparator 
                              context:(void *)context;
- (NSArray *)sortedArrayUsingFunction:(NSInteger (*)(id, id, void *))comparator 
                              context:(void *)context hint:(NSData *)hint;

这里要重点说下sortedArrayHint,其可以加快排序:
若对一个元素较多的数组(n个元素)排序一次后,之后较少的改动(p次增删操作,p << n);这种情况下,第一次调用排序函数后,可调用sortedArrayHint保存其返回的NSData,之后增删操作后再次调用排序函数可以传入这个data,排序会快些。

另外还有块语法的排序函数:
- (NSArray *)sortedArrayUsingComparator:(NSComparator)cmptr;
- (NSArray *)sortedArrayWithOptions:(NSSortOptions)opts 
                    usingComparator:(NSComparator)cmptr;

几种类型的排序函数没有明显的性能差异,非得要找个最快的那就是sortedArrayUsingSelector了,github有个benchmark示例
Sorting 1000000 elements. selector: 4947.90[ms] function: 5618.93[ms] block: 5082.98[ms].
从ios4/mac雪豹系统起NSArray集成了二分查找。
typedef NS_OPTIONS(NSUInteger, NSBinarySearchingOptions) {
        NSBinarySearchingFirstEqual     = (1UL << 8),
        NSBinarySearchingLastEqual      = (1UL << 9),
        NSBinarySearchingInsertionIndex = (1UL << 10),
};

- (NSUInteger)indexOfObject:(id)obj 
              inSortedRange:(NSRange)r 
                    options:(NSBinarySearchingOptions)opts 
            usingComparator:(NSComparator)cmp;

我专门去运行使用了下这个函数,代码如下:
NSArray *bsArr = @[@(0), @(2), @(3), @(3), @(4), @(5), @(6)];
    NSUInteger index = [bsArr indexOfObject:@(3) inSortedRange:NSMakeRange(0, bsArr.count)
                                    options:NSBinarySearchingInsertionIndex | NSBinarySearchingLastEqual
                            usingComparator:^NSComparisonResult(id obj1, id obj2)
    {
        NSInteger value1 = [obj1 integerValue];
        NSInteger value2 = [obj2 integerValue];
        
        return value1 > value2 ? NSOrderedDescending :
              (value1 == value2 ? NSOrderedSame : NSOrderedAscending);
    }];
    NSLog(@"binary search index=%d",index);

几种options传参情况的输出如下:
NSBinarySearchingInsertionIndex | NSBinarySearchingLastEqual
输出:binary search index=4

NSBinarySearchingInsertionIndex | NSBinarySearchingFirstEqual 或者NSBinarySearchingInsertionIndex
输出:binary search index=2

NSBinarySearchingFirstEqual或0
输出:binary search index=2

NSBinarySearchingLastEqual
输出:binary search index=3

若第一个参数obj传入的对象是@7,
options为0或NSBinarySearchingFirstEqual或NSBinarySearchingLastEqual
输出binary search index=2147483647(NSNotFound)

options为NSBinarySearchingInsertionIndex或外加FirstEqual或外加LastEqual
输出:binary search index=7


为何使用该函数?诸如containsObject和indexOfObject的函数都是从0开始顺序遍历每个元素直到匹配为止。这类函数不需要事先排序好的数组,但时间复杂度为O(n);另一方面,二分查找需要数组先排序,时间复杂度仅为O(log n)。若有一百万个元素的数组,二分查找至多需要21次比较,而线性查找平均需要500000次比较。

二分查找benchmark:
Time to search for 1000 entries within 1000000 objects. Linear: 54130.38[ms]. Binary: 7.62[ms]

作个比较,用NSOrderedSet查找某个索引只需要0.23毫秒,比二分查找还快30多倍。

排序是费时的,Apple使用归并排序,时间复杂度为O(n*log n),如果仅需调用一次indexOfObject,那没必要使用二分查找。

指定参数NSBinarySearchingInsertionIndex,向已排序数组插入新元素时可以得到插入索引(上面例子已经说明)

Enumeration and Higher-Order Messaging

让我们看个常例,从数组筛选出元素到新数组。这个测试使用了若干种遍历方法,以及专门的一个过滤函数:
// First variant, using `indexesOfObjectsWithOptions:passingTest:`.
NSIndexSet *indexes = [randomArray indexesOfObjectsWithOptions:NSEnumerationConcurrent 
                                                   passingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
    return testObj(obj);
}];
NSArray *filteredArray = [randomArray objectsAtIndexes:indexes];

// Filtering using predicates (block-based or text)    
NSArray *filteredArray2 = [randomArray filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id obj, NSDictionary *bindings) {
    return testObj(obj);
}]];

// Block-based enumeration 
NSMutableArray *mutableArray = [NSMutableArray array];
[randomArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    if (testObj(obj)) {
        [mutableArray addObject:obj];
    }
}];

// Classic enumeration
NSMutableArray *mutableArray = [NSMutableArray array];
for (id obj in randomArray) {
    if (testObj(obj)) {
        [mutableArray addObject:obj];
    }
}

// Using NSEnumerator, old school.
NSMutableArray *mutableArray = [NSMutableArray array];
NSEnumerator *enumerator = [randomArray objectEnumerator];
id obj = nil;
while ((obj = [enumerator nextObject]) != nil) {
    if (testObj(obj)) {
        [mutableArray addObject:obj];
    }
}

// Using objectAtIndex: (via subscripting)
NSMutableArray *mutableArray = [NSMutableArray array];
for (NSUInteger idx = 0; idx < randomArray.count; idx++) {
    id obj = randomArray[idx];
    if (testObj(obj)) {
        [mutableArray addObject:obj];
    }
}

运行消耗时间:
Enumeration Method / Time [ms] 10.000.000 elements 10.000 elements
indexesOfObjects:, concurrent 1844.73 2.25
NSFastEnumeration (for in) 3223.45 3.21
indexesOfObjects: 4221.23 3.36
enumerateObjectsUsingBlock: 5459.43 5.43
objectAtIndex: 5282.67 5.53
NSEnumerator 5566.92 5.75
filteredArrayUsingPredicate: 6466.95 6.31
为了更深入理解性能差异,让我们先来看看数组是如何遍历的。

indexesOfObjectsWithOptions:passingTest:由于每次遍历都要调用block,因此相对于传统的NSFastEnumeration遍历方式效率要低些。但是若第一个参数传入NSEnumerationConcurrent,则其速度会快近两倍。如果集合元素较少,使用哪种方式都无所谓,但对于NSEnumerationConcurrent额外的线程管理反而会偏慢。

最差劲的当属filteredArrayUsingPredicate。但NSPredicate还是要提一下,其可以实现较复杂的过滤表达式,用过Core Data的对此应该较为熟悉。

另外NSEnumerator这个开发中避免使用,尽管它要比filteredArrayUsingPredicate还快些,其只是为了向后兼容而存在。

Should I Use arrayWithCapacity:?

使用array构造函数即可,指定capacity并没有多少性能变化。

NSDictionary

// Using keysOfEntriesWithOptions:passingTest:,optionally concurrent
NSSet *matchingKeys = [randomDict keysOfEntriesWithOptions:NSEnumerationConcurrent 
                                               passingTest:^BOOL(id key, id obj, BOOL *stop) 
{
    return testObj(obj);
}];
NSArray *keys = matchingKeys.allObjects;
NSArray *values = [randomDict objectsForKeys:keys notFoundMarker:NSNull.null];
__unused NSDictionary *filteredDictionary = [NSDictionary dictionaryWithObjects:values 
                                                                        forKeys:keys];    
    
// Block-based enumeration.
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionary];
[randomDict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
    if (testObj(obj)) {
        mutableDictionary[key] = obj;
    }
}];

// NSFastEnumeration
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionary];
for (id key in randomDict) {
    id obj = randomDict[key];
    if (testObj(obj)) {
        mutableDictionary[key] = obj;
    }
}

 // NSEnumeration
 NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionary];
 NSEnumerator *enumerator = [randomDict keyEnumerator];
 id key = nil;
 while ((key = [enumerator nextObject]) != nil) {
       id obj = randomDict[key];
       if (testObj(obj)) {
           mutableDictionary[key] = obj;
       }
 }

// C-based array enumeration via getObjects:andKeys:
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionary];
id __unsafe_unretained objects[numberOfEntries];
id __unsafe_unretained keys[numberOfEntries];
[randomDict getObjects:objects andKeys:keys];
for (int i = 0; i < numberOfEntries; i++) {
    id obj = objects[i];
    id key = keys[i];
    if (testObj(obj)) {
       mutableDictionary[key] = obj;
    }
 }

Filtering/Enumeration Method Time [ms], 50.000 elements 1.000.000 elements
keysOfEntriesWithOptions:, concurrent 16.65 425.24
getObjects:andKeys: 30.33 798.49*
keysOfEntriesWithOptions: 30.59 856.93
enumerateKeysAndObjectsUsingBlock: 36.33 882.93
NSFastEnumeration 41.20 1043.42
NSEnumeration 42.21 1113.08

Shared Keys

sharedKeySetForKeys用来创建预定义好的key集合,返回的值作为NSMutableDictionary的dictionaryWithSharedKeySet参数传入,可以重用key,从而节约copy操作,节省内存。

NSOrderedSet

排序的set集合,但添加元素操作较费时

NSHashTable

可以包含弱引用对象的set集合,通常使用weakObjectsHashTable构造函数

NSMapTable

对应NSDictionary的弱引用版本,添加元素和存取操作都比NSMutableDictionary慢点

NSPointerArray

NSArray的弱引用版本,也可以存储非oc对象,添加元素操作很慢

NSIndexSet

专门用来存储正整数,且数值在集合中唯一

1
0

猜你在找
【直播】机器学习&深度学习系统实战(唐宇迪)
【直播】Kaggle 神器:XGBoost 从基础到实战(冒教授)
【直播回放】深度学习基础与TensorFlow实践(王琛)
【直播】计算机视觉原理及实战(屈教授)
【直播】机器学习之凸优化(马博士)
【直播】机器学习之矩阵(黄博士)
【直播】机器学习之概率与统计推断(冒教授)
【直播】机器学习之数学基础
【直播】TensorFlow实战进阶(智亮)
【直播】深度学习30天系统实训(唐宇迪)
查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:101729次
    • 积分:1056
    • 等级:
    • 排名:千里之外
    • 原创:5篇
    • 转载:13篇
    • 译文:2篇
    • 评论:45条
    文章分类
    最新评论