iOS7: 漫談基礎集合類(NSArray, NSSet, NSOrderedSet ,NSHashTable和 NSDictionary)
基礎集合類是每一個Mac/iOS應用的基本組成部分。在本文中,我們將對”老類”(NSArray, NSSet)和”新類”(NSMapTable, NSHashTable, NSPointerArray)進行一個深入的研究,探索每一個的效率細節,並討論其使用場景。
大O符號
首先,我們需要一些理論知識。效率通常用大O符號描述。它定義了一個函式的 極限特徵 ,通常被用於描繪其演算法效率。O定義了函式增長率的上限。通過檢視通常使用的O符號和所需要的運算元來檢視差異的大小。
例如,如果用演算法複雜度為O(n2)的演算法對一個有50個元素的陣列排序,需要2500步的操作。而且,還有內部的系統開銷和方法呼叫 — 所以是2500個操作的時間常量。 O(1)是理想的複雜度,代表著恆定的時間。好的演算法通常需要O(n*log n)的時間。
可變性
大多數的集合類存在兩個版本:可變和不可變(預設)。這和其他大多數的框架有非常大的不同,一開始會讓人覺得有一點奇怪。然而其他的框架現在也應用了這一特性:就在幾個月前,.
最大的好處是什麼?執行緒安全。不可變的集合完全是執行緒安全的,可以同時在多個執行緒中迭代,避免各種突變異常的風險。你的API 絕不 應該暴露可變集合。
當然從不可變到可變再變回來會有一定的代價 — 物件必須被拷貝兩次,所有集合內的物件將被retain/release。有時在內部使用一個可變的集合而在出口返回一個不可變的物件副本會更高效。
與其他框架不同,蘋果沒有提供一個執行緒安全的可變集合,NSCache是例外 — 但它真的算不上是集合類,因為它不是一個通用的容器。大多數時候,你的確想要高於集合級別的同步。想象一段程式碼,作用是檢查字典中一個key是否存在,並根據檢查結果決定設定一個新的key或者返回某些值 — 你通常需要把多個操作歸類,這時執行緒安全的可變變體並不能幫助你。
這裡有一些同步的,執行緒安全的可變集合有效的使用案例,只需要用幾行程式碼,通過子類和組合的方法建立諸如NSDictionary或NSArray。
需要注意的是,一些更新式的集合類,如NSHashTable,NSMapTable和NSPointerArray預設就是可變的,它們並沒有對應的不可變的類。它們用於類的內部使用,或者某個你想要不常見的可變類的場景。
NSArray
NSArray作為一個儲存物件的有序集合,可能是被使用最多的集合類。這也是為什麼它有自己的比原來的[NSArray arrayWithObjects:..., nil]簡短得多的快速語法糖符號@[...]。
NSArray實現了objectAtIndexedSubscript
效能特徵
關於NSArray的內容比你想象的要多的多。基於儲存物件的多少,它使用各種內部的變體。最有趣的部分是蘋果對於個別的物件訪問並不保證O(1)的訪問時間 — 正如你在CFArray.h CoreFoundation header中的關於演算法複雜度的註解中可以讀到的:
The access time for a value in the array is guaranteed to be at worst O(lg N) for any implementation, current and future, but will often be O(1) (constant time). Linear search operations similarly have a worst case complexity of O(Nlg N), though typically the bounds will be tighter, and so on. Insertion or deletion operations will typically be linear in the number of values in the array, but may be O(Nlg N) clearly in the worst case in some implementations. There are no favored positions within the array for performance; that is, it is not necessarily faster to access values with low indices, or to insert or delete values with high indices, or whatever.
在測量的時候,NSArray產生了一些有趣的額外的效能特徵。在陣列的開頭和結尾插入/刪除元素通常是一個O(1)操作,而隨機的插入/刪除通常是 O(N)的。
有用的方法
NSArray的大多數方法使用isEqual:來檢查物件間的關係(例如containsObject:)。有一個特別的方法indexOfObjectIdenticalTo
在iOS 7中,我們最終得到了與lastObject對應的公開的firstObject方法,對於空陣列,這兩個方法都會返回nil — 而常規的訪問方法會丟擲一個NSRangeException異常。
關於構造(可變)陣列有一個漂亮的細節可以節省程式碼量。如果你通過一個可能為nil的陣列建立一個可變陣列,通常會這麼寫:
-
NSMutableArray
*mutableObjects = [array mutableCopy]; -
if
(!mutableObjects) { -
mutableObjects = [NSMutableArray array]; - }
或者通過更簡潔的三元運算子:
-
NSMutableArray
*mutableObjects = [array mutableCopy] ?: [NSMutableArray array];
更好的解決方案是使用arrayWithArray:,即使原陣列為nil,該方法也會返回一個數組物件:
-
NSMutableArray
*mutableObjects = [NSMutableArray arrayWithArray:array];
這兩個操作在效率上幾乎相等。使用copy會快一點點,不過話說回來,這不太可能是你應用的瓶頸所在。提醒:不要使用[@[] mutableCopy]。經典的[NSMutableArray array]可讀性更好。
翻轉一個數組非常簡單:array.reverseObjectEnumerator.allObjects。我們使用系統提供的reverseObjectEnumerator,每一個NSEnumerator都實現了allObjects,該方法返回一個新陣列。雖然沒有原生的randomObjectEnumerator方法,你可以寫一個自定義的打亂陣列順序的列舉器或者使用一些出色的開原始碼。
陣列排序
有很多各種各樣的方法來對一個數組排序。如果陣列儲存的是字串物件,sortedArrayUsingSelector
-
NSArray
*array = @[@"John Appleseed" ,@"Tim Cook" ,@"Hair Force ,One" @"Michael Jurewitz" ]; -
NSArray
*sortedArray = [array sortedArrayUsingSelector :@selector(localizedCaseInsensitive Compare:)];
下面的程式碼對儲存數字的內容同樣很好,因為NSNumber實現了compare::
-
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的排序。
The hinted sort is most efficient when you have a large array (N entries) that you sort once and then change only slightly (P additions and deletions, where P is much smaller than N). You can reuse the work you did in the original sort by conceptually doing a merge sort between the N “old” items and the P “new” items. To obtain an appropriate hint, you use sortedArrayHint when the original array has been sorted, and keep hold of it until you need it (when you want to re-sort the array after it has been modified).
因為block的引入,也出現了一些基於block的排序方法:
-
-
(NSArray *)sortedArrayUsingComparat or:(NSComparator)cmptr; -
-
(NSArray *)sortedArrayWithOptions:(NSSortOptions)opts -
usingComparator:(NSComparator)cmptr;
效能上來說,不同的方法間並沒有太多的不同。有趣的是,基於selector的方式是最快的。你可以在GitHub上找到測試用的原始碼:
-
Sorting
1000000 elements. selector: 4947.90[ms] function: 5618.93[ms] block: 5082.98[ms].
二分查詢
NSArray從iOS 4/Snow Leopard開始內建了二分查詢
-
typedef
NS_OPTIONS(NSUInteger, NSBinarySearchingOptions ) { -
NSBinarySearchingFirstEq ual = (1UL << 8), -
NSBinarySearchingLastEqu al = (1UL << 9), -
NSBinarySearchingInserti onIndex = (1UL << 10), - };
-
-
(NSUInteger)indexOfObject:(id)obj -
inSortedRange:(NSRange)r -
options:(NSBinarySearchingOptions )opts -
usingComparator:(NSComparator)cmp;
為什麼要使用這個方法?類似containsObject:和indexOfObject:這樣的方法從0索引開始搜尋每個物件直到找到目標 — 不需要陣列被排序而且是O(n)的效率特性。換句話說,二分查詢需要陣列事先被排序,但只需要O(log n)的時間。因此,對於1,000,000的記錄,二分查詢法最多隻需要21次比較,而傳統的線性查詢則平均需要5000,000次的比較。
這是個簡單的衡量二分查詢有多快的資料:
-
Time
to search for 1000 entries within 1000000 objects. Linear: 54130.38[ms]. Binary: 7.62[ms]
作為比較,查詢NSOrderedSet中的指定索引花費0.23毫秒 — 即使跟二分查詢相比也快了30多倍。
記住排序的開銷也是昂貴的。蘋果使用複雜度為O(n*log n)的歸併排序,所以如果執行過indexOfObject:一次,就沒有必要使用二分查找了。
通過指定NSBinarySearchingInserti
列舉和高階訊息
作為參照,我們來看一個普通的使用場景。從一個數組中過濾出另一個數組。測試了多個列舉方法和API特性:
-
//
First variant, using `indexesOfObjectsWithOpti ons:passingTest:`. -
NSIndexSet
*indexes = [randomArray indexesOfObjectsWithOpti ons: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 filteredArrayUsingPredic ate:[NSPredicate predicateWithBlock:^BOOL(id obj, NSDictionary *bindings) { -
return testObj(obj); - }]];
-
//
Block-based enumeration -
NSMutableArray
*mutableArray = [NSMutableArray array]; -
[randomArray
enumerateObjectsUsingBlo ck:^(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]; -
} - }
為了更好的理解這裡的效率測量,我們首先看一下陣列是如何迭代的。
indexesOfObjectsWithOpti
最大的輸家是filteredArrayUsingPredic
為了比較的完整,我們也加入了NSEnumerator作為比較 — 雖然沒有任何理由再使用它了。然而它竟出人意料的快(比基於NSPredicate的查詢要快),它的執行時消耗無疑比快速列舉更多 — 現在它只用於向後相容。甚至沒有優化過的objectAtIndex:都要更快些。
NSFastEnumeration
在OSX 10.5和iOS的最初版本中,蘋果增加了NSFastEnumeration。在此之前,只有每次返回一個元素的NSEnumeration,每次迭代都有執行時開銷。而快速列舉,蘋果通過countByEnumeratingWithSt
應該用arrayWithCapacity:嗎?
初始化NSArray的時候,可以選擇指定陣列的預期大小。在檢測的時候,結果是在效率上沒有差別 — 測量的時間幾乎相等,且在統計不確定性的範圍內。有訊息透漏說實際上蘋果並沒有使用這個特性。然而使用arrayWithCapacity:仍然有用,在文件不清晰的程式碼中,它可以幫助理解程式碼:
-
Adding
10.000.000 elements to NSArray. no count 1067.35[ms] with count: 1083.13[ms].
子類化注意事項
很少有理由去子類化基礎集合類。大多數時候,使用CoreFoundation級別的類並且自定義回撥函式定製自定義行為是更好的解決方案。
建立一個大小寫不敏感的字典,一種方法是子類化NSDictionary並且自定義訪問方法,使其將字串始終變為小寫(或大寫),並對排序也做類似的修改。更快更好的解決方案是提供一個不同的CFDictionaryKeyCallBacks
子類作用的一個例子是有序字典的用例。.NET提供了一個SortedDictionary,Java有TreeMap,C++有std::map。雖然你 可以 使用C++的STL容器,但卻無法使它自動的retain/release,這會使使用起來笨重的多。因為NSDictionary是一個類簇,所以子類化跟人們想象的相比非常不同。這已經超過了本文的討論範疇,這裡有一個真實的有序字典的例子。
NSDictionary
一個字典儲存任意的物件鍵值對。 由於歷史原因,初始化方法使用相反的物件到值的方法,[NSDictionary dictionaryWithObjectsAnd
相關推薦
iOS7: 漫談基礎集合類(NSArray, NSSet, NSOrderedSet ,NSHashTable和 NSDictionary)
基礎集合類是每一個Mac/iOS應用的基本組成部分。在本文中,我們將對”老類”(NSArray, NSSet)和”新類”(NSMapTable, NSHashTable, NSPointerArray)進行一個深入的研究,探索每一個的效率細節,並討論其使用場景。
__block __weak漫談基礎集合類(NSArray,NSSet,NSOrderedSet,NSDictionary,NSMapTable,NSHashTable, NSPointerArra
http://www.cnblogs.com/zhaoguowen/p/4273237.html http://blog.csdn.net/leikezhu1981/article/details/45009123
Java基礎--集合類
最近在找工作,目前還沒有定下來,拿到了一個公司的offer,不過被當白菜了,正在商量薪資方面的事情。隨著百度面試的失敗,夢想再次破滅。想想這一年來的奮鬥,別是一番滋味在心頭。突然想起一句話:踏歌長行,夢想永在! 說程式設計師是幸福的,因為我們每天都會和大腦過意不
黑馬程式設計師——java基礎——集合類
------- android培訓、java培訓、期待與您交流! ---------- 面嚮物件語言對事物的體現都是以物件的形式,所以為了方便對多個物件的操作,Java就提供了集合類。 集合的繼承
java基礎集合類——ArrayList 源碼略讀
lec rst extend except ini cts 數據量 bound cif ArrayList是java的動態數組,底層是基於數組實現。 1. 成員變量 public class ArrayList<E> extends AbstractList&l
java基礎集合類——LinkedList 源碼略讀
serializa 鏈表 ngs 功能 ray 方法 == memory () 1.概覽 LinkedList是java的動態數組另一種實現方式,底層是基於雙向鏈表,而不是數組。 public class LinkedList<E> extends Ab
Java集合類中不實現Cloneable和Serializable原因
tran style 能夠 uid sta 工具 數據 類型 size Java集合類中不實現Cloneable和Serializable原因 莫名的被問這麽一個問題,也答不上,記錄記錄,為何Java集合類不實現Cloneable和Serializable接口。
Java集合類根介面:Collection 和 Map
前言 在前文中我們瞭解了幾種常見的資料結構,這些資料結構有著各自的應用場景,並且被廣泛的應用於程式語言中,其中,Java中的集合類就是基於這些資料結構為基礎。 Java的集合類是一些非常實用的工具類,主要用於儲存和裝載資料 (包括物件),因此,Java的集合類也被成為容器。在Java中,所有的集合類都位於
Java--容器/集合類(Collection)理解和使用
、陣列和集合的比較 陣列:長度固定,用來存放基本型別的資料 集合:長度不固定,用來存放物件的引用 二、集合類的基本概念 1.java.util包中提供了一些集合類,這些集合類也被稱為容器。 常用的集合有List集合、Set集合、Map集合,他們的關係繼承如下:
C# 集合類Dictionary的遍歷和修改(防止錯誤:集合已修改;可能無法執行列舉操作。)
C#中直接對集合Dictionary進行遍歷並修改其中的值,會報錯,如下程式碼就會報錯:集合已修改;可能無法執行列舉操作。程式碼如下 public void ForeachDic() { Dictionary<String, In
背水一戰 Windows 10 (50) - 控件(集合類): ItemsControl - 基礎知識, 數據綁定, ItemsPresenter, GridViewItemPresenter, ListViewItemPresenter
需要 emc rectangle ems sources mic navi schema mark 原文:背水一戰 Windows 10 (50) - 控件(集合類): ItemsControl - 基礎知識, 數據綁定, ItemsPresenter, GridViewI
背水一戰 Windows 10 (56) - 控件(集合類): ListViewBase - 基礎知識, 拖動項
tar accepted 類型 idv .get footer ati model 變化 原文:背水一戰 Windows 10 (56) - 控件(集合類): ListViewBase - 基礎知識, 拖動項[源碼下載] 背水一戰 Windows 10 (56) - 控件
java 集合類基礎問題匯總
應該 會計 queue 基本數據 actor 相同 快速查找 設置 非線程安全 1、Java集合類框架的基本接口有哪些? 參考答案 集合類接口指定了一組叫做元素的對象。集合類接口的每一種具體的實現類都可以選擇以它自己的方式對元素進行保存和排序。有的集合類允許重復的鍵
java基礎(17)、集合類(1:Collection)
實例 array 9.png str 列表 println 方式 elements .com 一. 什麽是集合類? 二. Collection接口 2.1 Collection子接口(List接口和Set接口) 2.1 Collection的常用方法 三.
【python基礎】集合類型
linux code 課程 xxd hone 插入元素 close 1.3 格式 集合類型: 作用: --> 關系運算(交集,並集,差集) --> 去重(有局限性) 定義方法:set() linuxers = {1,1.1,‘a‘,(1,2
黑馬程式設計師----Java基礎之集合類(一)
------- <a href="http://www.itheima.com" target="blank">android培訓</a>、<a href="http://www.itheima.com" target="blank">java培訓</a&g
【搞定Java基礎】之集合類面試題整理
因為集合類在Java基礎知識中是非常重要的,也是面試中最常問到的,設計的問題也比較多,因此單獨拿出來做面試題的整理,方便自己複習,也希望給看到此篇文章的你帶來一定的幫助。文章內容均來自於網路,平時看到總結不錯的題目,就收集在此。持續更新....... 先推薦幾篇不錯的文章: 1、Java集合
黑馬程式設計師—13—java基礎:有關集合類的學習筆記和心得體會
------- <a href="http://www.itheima.com" target="blank">android培訓</a>、<a href="http://www.itheima.com" target="blank">
Java基礎面試題3-說說你知道的幾個Java集合類:list、set、queue、map
關係這張圖簡單揭示了Set、List與Map之間的相對關係。 需要說明下的是,圖中的實現並不指這麼簡單的實現,這個稍後會說到。Collection介面Collection是Java中最基本的集合介面。它描述了一組有關集合操作的方法。int Size(); //集合大小 boo
Java基礎面試題3-說說你知道的幾個Java集合類:list、set、map
關係 這張圖簡單揭示了Set、List與Map之間的相對關係。 需要說明下的是,圖中的實現並不指這麼簡單的實現,這個稍後會說到。 Collection介面 Collection是Java中最基本的集合介面。它描述了一組有關集合操作的方法。 int Size(); /