Swift 集合數據結構性能分析

原文鏈接:Collection Data Structures In Swift
原文日期:2015/04/21

譯者:Yake
校對:shanks
定稿:numbbbbb

假設你有一個需要處理許多數據的應用。你會把數據放在哪兒?如何高效地組織并處理數據呢?

如果你的項目只處理一個數字,可以把它存在一個變量中。如果有兩個數字你就要用兩個變量。
如果有 1000 個數字、10000 個字符串或者終極模因庫呢(能立刻找到一個完美的模因多爽啊)?在這些情況下,你將會需要一種基本的集合類數據結構。幸運的是,這篇教程會告訴你該怎么做。

下面是這篇教程的結構:

  • 復習什么是數據結構并學習大 O 符號。這是用來描述不同的數據結構性能的標準工具。
  • 下一步:衡量數組、字典和集合這些 Cocoa 開發中最基本的數據結構的性能。同時本文也會介紹一些基礎的性能測試的方法。
  • 繼續學習,你將會比較成熟的 Cocoa 數據結構與對應的純 Swift 結構的性能。
  • 最后,簡要地復習一下 Cocoa 中提供的一些相關類型。我們來看看你平時非常熟悉的數據結構有什么你不知道的東西。
你覺得這些數據結構性能如何?
你覺得這些數據結構性能如何?

開始

深入探索 iOS 中的數據結構之前,需要復習一下它們是什么,以及怎么測量它們的性能。

有許多集合類數據結構類型,每個類型都會用一個特定的方法組織并獲取數據集合。集合類型可能會使一些操作變得十分高效,例如添加新的數據、查找最小的數據以及保證不會重復添加數據。

沒有集合數據類型,你將會陷入試圖管理每個數據的困境中。集合能夠讓你:

  • 將所有的數據作為一個實體來處理
  • 給它們設置一些結構
  • 高效地插入、刪除和檢索數據

什么是大 O 標記

大 O,說的是字母 O,而不是數字 0。這個符號用來描述在某種數據結構上執行某種操作的效率。有很多種衡量效率的方法:你可以衡量這種數據結構消耗了多少內存、在最壞的情況下消耗的時間、它花費的平均時間是多少等等。

在這篇教程中,你將會統計操作的平均時長。

通常,數據量增加不會使操作變快。大多數情況下是相反的,但有時候變化很小甚至沒有差別。大 O 標記用來精確地描述這種情況。你能用一個具體的函數來表示運行時間和數據量的關系。

大 O 標記被寫作O(與 n 相關的函數),括號中的n就表示數據結構中數據的數量,而與 n 相關的函數則大約表示操作將要消耗的時間。

“大約”,確實有點諷刺,但是它有特定的含義:當n非常大時函數的漸進值。假設n是很大很大的數,當你將參數從n修改為n + 1時,考慮一些操作的性能會怎樣變化。

注意:這些都是算法復雜度分析中的一部分,如果你想深入探索的話,就多讀些計算機科學類的書籍。這樣你會掌握一些分析復雜度的數學方法、不同效率之間的微小差異、關于未知機器模型的更細化公式的假設以及許多你能想到或想不到的有趣的東西。

常見的大 O 性能測量如下(性能由高到低):

  • O(1)(常數時間):無論數據結構中有多少數據,這個函數都調用同樣次數的操作。這是最理想的性能
  • O(log n)(對數): 這個函數調用的操作次數隨著數據結構中的數據量對數級增長。這已經是很好的性能了,因為相比較數據結構中的數據的數量它的增長速度要慢得多。
  • O(n)(線性):函數調用的操作次數隨著數據結構的數據量線性增長。這是比較好的性能,但是如果數據集合較大就不合適了。
  • O(n (log n)):這個函數調用的操作次數是由數據結構中的數據量的對數乘以數據結構中的數據量。可以預見的是,這是現實中對性能的最低容忍度。較大的數據結構會執行更多的操作,對于數據量較小的數據結構來說增長是較為合理的。
  • O(n2)(平方):這個函數調用操作的次數是數據量的平方 - 這算是比較差的性能中最好的一個。哪怕你處理的數據集很小,它也會很快變慢。
  • O(2^n)(指數):函數調用的操作次數與數據量是2n次方的關系。這會導致很差的性能并很快就變得特別慢。
  • O(n!) (階乘):函數調用的操作次數與數據量之間是階乘的關系。實際上,這是性能最差的情況。例如,在一個有 100 個數據的結構中,操作次數是一個長度為158的數字。

下面是一個更直觀的性能圖。當集合中的數據量從1變化至25時,性能會如何變化:

圖片
圖片

你有沒有發現你幾乎看不到綠色的O(log n)線?因為在這個范圍內它幾乎與理想的O(1)重合。那太好了!另一方面,O(n!)O(2^n)標記的操作性能下降得特別快,當集合中數據超過10操作的數量就開始飆升。
是的!正如圖表中清楚展示的,你處理的數據越多,選擇正確的數據結構就越重要。

現在你已經知道怎么比較基于某種數據結構的數據操作的性能了。下面我們來復習一下 iOS 中最常用的三種數據類型以及它們在理論和實踐中是如何發揮作用的。

常見 iOS 數據結構

iOS 中三種最常用的數據結構是數組、字典和集合。首先你要考慮它們在理論上和基礎類型的區別,然后需要測試 iOS 中對應的實現類的性能。

對于這三種主要結構的類型來說,iOS 提供了多種具體的類來支持抽象的結構。除了在 Swift 和 Objective-C 中舊的 Foundation 框架中的數據結構,現在又有了新的僅支持 Swift 版本的數據結構。

Foundation 中的數據結構已經存在了很久,即使數據量很大,創建速度也很快。然而,Swift 中的數據結構查詢速度更快。如何選擇取決于你需要的操作類型。

數組

數組就是以一定順序排列的一組數據,你可以通過索引來獲取每一個數據元素,索引代表數據在排列中的位置。當你在數組變量名稱后面的括號中寫上索引時,這就叫做下標。

在 Swift 中,如果你用let將數組作為常量來定義,它們就是不可變的,如果用 var 定義為變量它們就是可變的。

作為對比,Foundation 框架中的 NSArray 默認是不可變類型,如果你想在數組創建之后添加、刪除或者修改數據,必須使用可變類 NSMuatbleArray

NSArray 是異構的,意味著它可以包含不同類型的 Cocoa 對象。 Swift 數組是同構的,意味著每一個 Swift 數組都只包含一種類型的對象。

不過,你仍然可以將一個類型定義為 AnyObject 類型,這樣 Swift 數組就可以存儲可變的 Cocoa 對象類型。

期望性能、數組使用指南

使用數組來存儲變量的一個首要原因是順序很重要。例如,你會通過名或者姓來排列聯系人、按日期寫一個計劃列表或者任何需要通過某種順序來查找和展示數據的場景。

蘋果的文檔在CFArray header一篇中闡明了三種關鍵操作的期望性能:

  • 在數組中通過一個特定的索引獲取值的時間復雜度最壞是O(log n),但通常應該是O(1)
  • 搜索一個未知索引的對象的時間復雜度最壞是O(n (log n)),但一般應該是O(n)
  • 插入或者刪除一個對象最壞的時間復雜度是O(n (log n)),但經常是O(1)

這些保證了數組基本上和你在計算機教材或者是 C 語言中學到的那樣,總是一段連續內存中的數據序列,是一種“理想型”數組。

這點對你理解文檔很有幫助。

下面這些可以幫助你更好地理解文檔中的期望性能:

  • 當你已經知道一個數據在哪兒并且需要查找時,那應該是很快的。
  • 如果你不知道某個數據在哪兒,你可能需要將數組從頭到尾遍歷一遍,查找的過程可能會比較慢。
  • 如果你知道要把某個數據添加到哪里或者從哪兒刪除,那不是很困難,雖然后來你可能需要調整數組的剩余部分,那得花費些時間。

這些期望與現實是一致的嗎?繼續往下看:

示例 App 測試結果

從這篇教程中下載示例項目并在 Xcode 中打開。里面有些方法可以創建或/和測試數組,并且展示給你執行每個任務消耗的時間。

需要注意的是:在應用中,測試配置會自動將優化選項設置成和發布配置一樣的選項。這樣當你測試 app 時,能獲得和真實環境一樣的優化選項。

你需要至少1000個數據才可以用示例 app 進行測試。編譯運行時,滑塊會被設置到1000。點擊Create Array and Test按鈕就會開始測試:

將滑塊向右拖動直到數據達到10000000,再次按下Create Array and Test按鈕,看看這個明顯增大的數組的創建時間與剛才有什么不同:

這些測試是在 Xcode6.3(Swift 1.2)、iOS 8.3 和 iPhone 6 模擬器上運行的。在數據增加了10000倍的情況下,創建數組花費的時間只增加了714倍。

O(n)函數意味著增加10000倍數據會導致時間增加10000倍,O(log n)函數意味著增加10000倍的數據會增加4倍時間。這個例子中的性能介于O(log n)O(n)之間,非常棒。

這個操作最初很費時,在這篇教程的第一版中,代碼運行在 Xcode6.0 Gold Master 版(Swift 1.0)、iOS 8 Gold Master 版和 iPhone 5 上,測試消耗了相當長的時間:

圖圖1
圖圖1
圖圖2
圖圖2

在這個例子中,數據量增加了約106.5倍,增加了約1417倍的時間。

如果要實現O(n)的性能,106.5倍的數據預期消耗時間應該是3.7秒。而以O(n log n)增長,預期消耗時間是33.5秒。以O(n2)增長,預期消耗時間395秒。

在這個例子中,Swift 中Array的創建時間大約在O(n log n)O(n2)之間。如你所見,當數據增加數十倍時時間會增加很多。

這個性能不容樂觀。不過,更新至 Swift1.2 之后,你創建多大的數組都沒有問題!

那么NSMutableArray呢?你仍然可以在 Swift 中調用Foundation中的類而不需要使用Objective-C,你會發現還有一個 Swift 類NSArrayManipulator遵守同樣的協議ArrayManipulator

正因如此,你可以輕松地把NSMutableArray替換成 Swift 中的數組。項目代碼很簡單,嘗試修改一行代碼來對比NSMuatbleArray的性能。

打開ArrayViewController.swift文件并將第 27 行由:

let arrayManipulator: ArrayManipulator = SwiftArrayManipulator()

修改為:

let arrayManipulator: ArrayManipulator = NSArrayManipulator()

再次編譯運行,點擊Create Array and Test測試創建1000個數據的NSMutableArray數組:

然后再次修改數據為10000000

創建操作原始的性能不如 Swift 1.2 中的好 - 可能是因為需要來回操作NSArray中和它們對應的 Swift 的類型對象。

不管怎樣,你創建一個數組只需要一次 - 但是需要經常執行的是其他一些操作例如查找、添加或者是移除對象。

如果繼續深入下去,比如使用 Xcode 6 中的一些性能測試方法分別調用每個方法 50 次,就會看出一些端倪:

  • 創建一個 Swift 數組和NSArray數組消耗相同的時間 - 介于O(log n)O(n)之間。如果數據量在500025000之間,Swift 比Foundation多消耗大約三倍的時間 - 但是都低于0.02秒。
  • 在 Swift 中向一個數組結構對象的開始或者是中間添加數據,相對Foundation(大約在O(1)左右)慢很多。向 Swift 數組的尾部添加數據(時間少于O(1))實際上比向NSArray的尾部添加數據(多于O(1))要快。
  • Foundation和 Swift 數組結構中移除對象操作的性能表現很相似:從起始位置、中間或者尾部移除一個對象時間大約在O(log n)O(n)之間。從數組的開頭移除數據這一操作在 Swift 中性能略差,但是時間差異是毫秒級的。
  • 通過索引查找消耗的時間在 Swift 數組以及NSArray中以相同的速率增長。當數組很大時,通過索引查找 Swift 數組消耗的時間相比NSMutableArray類型的對象快將近10倍。

注意:你可以使用iPhone 6 性能測試程序來生成每個方法運行50次的結果(環境:Xcode 6.3、Swift 1.2、iOS 8.3、iPhone 6)。你也可以嘗試運行應用中的其他測試,看看每運行10次的平均結果,以及它們在你手機上是如何運行的。

字典

字典是一種不需要特定的順序且數據都與唯一的鍵相聯系的儲值方式。你可以使用鍵來存儲或者是查詢一個值。

字典也使用下標語法。當你寫dictionary["hello"],就會得到與鍵hello對應的值。

像數組一樣,在 Swift 中你如果用let來聲明字典它就是個常量,如果用var
來聲明它就是可變的。在Foundation框架中也有對應的NSDictionaryNSMutableDictionary類型供你使用。

與 Swift 數組相似的另外一個特征就是字典也是強類型的(即對于程序里的每一個變量,在使用前必須聲明類型,賦值的時候也必須與變量的類型相同),你必須有已知類型的鍵值對。NSDictionary類型的對象可以使用任何NSObject的子類對象作為key,可以存儲任意類型的對象為值。

當你調用某個接收或者返回字典類型的 Cocoa API 時,就會獲得。在 Swift 中,這個類型的形式是[NSObject: AnyObject]。也就是說這個類型必須以NSObject的子類作為鍵,值可以是任何兼容 Swift 的對象。

什么時候使用字典

當你要存儲的數據沒有特定順序但是在某種意義上有關聯時,字典是最好的選擇。

下面我們來學習字典以及這篇教程中其他的數據結構是如何工作的,首先通過File…\New\Playground創建一個Playground,并把它命名為DataStructures

例如,你需要存儲一個數據結構,它們包含了你所有的朋友以及它們的貓的名字,這樣就可以通過你朋友的名字查找它們的貓的名字。你再也不需要為了拉近關系去記住那些貓的名字。

首先,你需要存儲朋友和貓的字典:

let cats = [ "Ellen" : "Chaplin", "Lilia" : "George Michael", "Rose" : "Friend", "Bettina" : "Pai Mei"]

由于 Swift 有類型推斷機制,上述類型被定義為 [String: String] - 鍵值均為字符串類型的字典。

現在嘗試在里面獲取數據:

cats["Ellen"] //返回可選類型,值為Chaplin
cats["Steve"] //返回 nil

注意字典中的下標語法返回一個可選類型。如果字典中不包含某個特定鍵的值,可選類型為nil;如果包含那個鍵對應的值,你就可以將其解包來獲取值。

這樣一來,使用if let可選類型解包語法從字典中獲取值就是個很不錯的辦法:

if let ellensCat = cats["Ellen"] {
    println("Ellen's cat is named \(ellensCat).")
} else {
    println("Ellen's cat's name not found!")
}

由于確實存在‘Ellen’對應的值,你的playground中會打印‘Ellen’s cat is named Chaplin’

期望性能

蘋果再一次在 Cocoa 的CFDictionary.h頭文件中提到了字典的期望性能:

  1. 從字典中獲取值的時間復雜度最壞是O(n),但一般應該是O(1)
  2. 插入和刪除數據的時間復雜度最壞是O(n2),但由于頂層的優化通常更接近O(1)

這些沒有數組中的性能那么好。由于存儲鍵值對比有序數組更復雜,所以性能不那么好預測。

示例程序測試結果

DictionaryManipulator是跟ArrayManipulator類似的一個協議,它可以測試字典。你可以用它測試 Swift 的DictionaryNSMutableDictionary在執行相同的操作時的性能。

為了比較 Swift 和 Cocoa 字典,你可以使用與上述數組的測試程序相似的步驟。編譯運行 app,選擇底部的Dictionary標簽。

運行幾個測試程序 - 你會發現創建一個字典比創建數組消耗的時間要長得多。如果你把數據滑塊滑到10000000,可能會收到內存警告甚至內存溢出崩潰!

回到 Xcode 中,打開DictionaryViewController.swift文件并找到dictionaryManipulator屬性:

let dictionaryManipulator: DictionaryManipulator = SwiftDictionaryManipulator()

用下面的代碼替換它:

let dictionaryManipulator: DictionaryManipulator = NSDictionaryManipulator()

現在應用底層將會使用NSDictionary。再次編譯運行 app,多進行幾次測試。如果你是在 Swift 1.2 上運行,結果應該和本文一致中:

  • 在時間消耗上,創建 Swift 字典比創建NSMutableDictionaries要慢得多 - 但性能都以O(n)的速率下降。
  • 向 Swift 字典中添加數據的速度比向NSMutableDictionaries 類型的對象中添加數據大約快3倍,并且性能如蘋果的文檔所說,最好情況下能達到O(1)
  • Swift 和Foundation的字典都能以大約O(1)的速率進行查找。不像 Swift 數組,字典并不比Foundation中的字典性能要好,但是它們非常得接近。

現在,進入 iOS 中最后一個主要的數據結構:集合!

集合

集合是一種存儲無序的,值唯一的數據結構。當你向一個集合中添加同樣的數據時,數據不會被添加。這里的“同樣地”即滿足方法isEqual()

Swift 在 1.2 版本中添加了對原生Set類型的支持 - 早期的 Swift 版本,你只能獲取Foundation框架下的NSSet類型。Swift 集合中的元素必須擁有相同的類型。

注意,就像數組和字典那樣,對于一個原生的 Swift 集合來說,如果你用let關鍵字進行聲明它就是常量,如果用var聲明就是可變的。在Foundation方面,也都有NSSetNSMutableSet供你使用。

什么時候使用集合

當數據的唯一性很重要而順序無所謂時可以使用集合。例如,如果你想從八個名字的數組中隨機選出四個名字并且沒有重復時,你會選擇什么呢?

Playground中輸入下面的代碼:

let names = ["John", "Paul", "George", "Ringo", "Mick", "Keith", "Charlie", "Ronnie"]
var stringSet = Set<String>() // 1
var loopsCount = 0
while stringSet.count < 4 {
    let randomNumber = arc4random_uniform(UInt32(count(names))) //2
    let randomName = names[Int(randomNumber)] //3
    println(randomName) //4
    stringSet.insert(randomName) //5
    ++loopsCount //6
}
 
//7
println("Loops: " + loopsCount.description + ", Set contents: " + stringSet.description)

在這一小段代碼中,你做了這些事:

  1. 初始化集合,這樣你就可以在里面添加數據。
  2. 0和名字的個數中間選一個隨機值。
  3. 獲取所選數字為索引對應的名字。
  4. 打印出選擇的名字。
  5. 將選擇的名字添加至可變的集合。記住,如果名字已經在集合里面了,集合就不會變化,因為集合不會存儲重復的數據。
  6. loop計數器一直在增加,這樣你就可以看到循環執行了多少次。
  7. 一旦循環結束,打印loop計數器的值和可變集合中的內容。

在這個例子中我們使用了一個隨機數字生成器,你每次都能獲取到一個不同的結果。下面的示例是寫這篇教程時的其中一次運行結果:

John
Ringo
John
Ronnie
Ronnie
George
Loops: 6, Set contents: ["Ronnie", "John", "Ringo", "George"]

在這兒,為了得到唯一的名字循環執行了6次。它分別選擇了RonnieJohn兩次,但是只在集合中添加了一次。

當你在playground中寫下這個循環時,會注意到它一遍一遍地執行,每次循環都會得到一個不同的數字。每次運行都至少有四次循環,因此集合中必須有四個數據才能跳出循環。

現在你已經看到了一個較小的集合是怎樣工作的,下面我們來看看存儲較大數據量的集合的性能。

示例應用測試結果

不像數組和字典,蘋果沒有概括集合性能的大概的預期(Swift 集合文檔中對幾個方法的性能有預期描述,但是沒有NSMutableSet對應的預期),所以在這里你只需要觀察實際情況中的性能。

Swift 1.2 版本的示例項目在SetViewController類中有NSSetManipulatorSwiftSetManipulator對象,這與ArrayDictionary的視圖控制器中的配置類似,并且也可以以同樣地方式進行替換。

在這兩種情況下,如果你追求純粹的速度,使用Set可能不能令你滿意。SetNSSet相較于ArrayNSMutableArray,你會發現集合的時間要慢得多 - 這就是檢查每一個數據在數據結構中是否唯一所付出的代價。

進一步的測試顯示由于 Swift 的集合相對于數組和字典來說,處理數據花費的時間更多,因此性能以及執行大多數操作所消耗的時間都與NSSet非常的相似:創建、移除以及查找操作對于·Foundation和 Swift 來說消耗的時間幾乎是相同的。

在 Swift 和Foundation中創建一個集合類型的對象消耗時間大約都以O(n)增長 - 這與預期相符,因為集合中的每一個數據被添加進來之前都要檢查是否與已有數據相等。

在 Swift 和Foundation中刪除和查找操作的性能大約是O(1)。這種情況是很穩定的,因為集合結構使用哈希值來檢查是否相等,而哈希值可以以某個順序進行計算和存儲。這就使得查找操作明顯比數組中的查找要快。

NSMutableSet和 Swift 原生的Set在性能上的主要差異在于添加對象的操作。

總之,向一個NSSet中添加對象時間復雜度約為O(1),而在 Swift 的Set結構下可能高于O(n)了。

Swift 才出生不久,已經在集合類數據結構的性能方面有了很大的提升,相信之后會繼續提高。

鮮為人知的Foundation數據結構

數組、字典和集合肩負著數據處理的重任。然而,Cocoa 提供了許多了解的人很少甚至不被欣賞的集合類型。如果字典、數組、集合都不能勝任某個功能,那么你可以試試這些是否管用。

NSCache

使用NSCache與使用NSMutableDictionary類似 - 通過鍵來添加和獲取值。區別在于NSCache是用來暫時存儲一些你總是可以重新計算和生成的東西。如果可用內存降低,NSCache可能會移除一些對象。它們是線程安全的,但是蘋果的文檔提示:

如果緩存被要求釋放內存,它就會自動執行異步的更新操作。

從根本上說,NSCacheNSMutableDictionary很像,除了NSCache可以在你的代碼沒有做任何事情的時候從另一個線程中移除一些對象。這對于管理緩存所使用的內存來說是很好的,但是如果你試圖使用一個突然消失的對象,那么就會出問題了。

NSCache對鍵采取弱引用而不是強引用。

NSCountedSet

NSCountedSet可以檢測到你向某個集合中添加某個對象操作執行的次數。它繼承自NSMutableSet,所以如果你再次向集合中添加同樣的對象,集合中也只會有一個那樣的對象。

不管怎樣,在一個NSCountedSet類型的對象中,集合記錄下對象被添加了多少次。你可以通過countForObject()查看某個對象被添加的次數。

注意當你調用NSMutableSetcount屬性時,它只返回唯一對象的數量,而不是所有被添加到集合中的元素的數量。

例如,在你的playground中,使用之前NSMutableSet測試中創建的名字數組,將每個名字都向NSCountedSet對象中添加兩次。

let countedMutable = NSCountedSet()
for name in names {
    countedMutable.addObject(name)
    countedMutable.addObject(name)
}

然后,打印集合,看看“Ringo”被添加了多少次:

let ringos = countedMutable.countForObject("Ringo")
println("Counted Mutable set: \(countedMutable)) with count for Ringo: \(ringos)")

你的打印結果應該是:

Counted Mutable set: {(
    George,
    John,
    Ronnie,
    Mick,
    Keith,
    Charlie,
    Paul,
    Ringo
)}) with count for Ringo: 2

注意集合中元素的順序可能不同,但是你應該看到“Ringo”在名字列表中出現了一次,即使它被添加了兩次。

NSOrderedSet

NSOrderedSet以及它對應的可變類型NSMutableOrderedSet,是一種允許你以特定順序存儲一組不重復的對象的數據結構。
"特定順序" - 天哪,這聽起來很像數組對不對?蘋果簡單總結了你為什么要使用NSOrderedSet而不是數組:

當元素順序以及測試某個元素是否存在于集合中的性能很重要時,你可以使用有序的集合作為數組的備選項。

因此,當你需要存儲一組有序又不能重復的數據時使用NSOrderedSet是最好的。

注意:由于NSCountedSet繼承自NSMutableSet,因此NSOrderedSet也繼承自NSObject。這個例子很好地表明了蘋果命名一個類是基于它們的功能,而不一定是基于它們在底層是如何工作的。

NSHashTable 和 NSMapTable

NSHashTable是另外一種與集合類似的數據結構,但是與NSMutableSet有幾處關鍵的不同之處。

你可以使用任意指針類型而不僅僅是對象來搭建一個NSHashTable類型對象,所以可以向NSHashTable中添加結構體和非對象類型的數據。你也可以使用NSHashTableOptions枚舉值來設置內存管理和相等的條件等。

NSMapTable是一種類字典的數據結構,但是在內存管理方面與NSHashTable有著相似的行為。

NSCache那樣,NSMapTable對鍵是弱引用。然而,不管什么時候鍵被釋放它都能夠自動刪除與那個鍵對應的數據。這些選項都可以在NSMapTableOptions枚舉值中進行設置。

NSIndexSet

NSIndexSet是一個不可變集合,用來存儲唯一的無符號整數,來表示數組的索引值。

如果你有一個包含十個數據的數組,而你只需要使用其中某幾個固定位置的數據,就可以將索引存儲在NSIndexSet對象中,并使用NSArrayobjectsAtIndexes來直接獲取對象:

let items : NSArray = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"]
 
let indexSet = NSMutableIndexSet()
indexSet.addIndex(3)
indexSet.addIndex(8)
indexSet.addIndex(9)
 
items.objectsAtIndexes(indexSet) // returns ["four", "nine", "ten"]

你所指定的某個“數據”現在是一個數組了,Swift 數組沒有對應的方法可以通過NSIndexSet或者別的什么類來獲取多個數據。

NSIndexSet保留了NSSet中只允許某個值出現一次的特性。因此,你不能用它來存儲任意一組數據,除非這組數據沒有重復值。

有了NSIndexSet,你就可以將索引按一定的順序存儲,這比僅僅存儲一個整數數組效率更高。

小測試

已經學到了這里,下面這個小測試是關于你想用哪種數據結構來存儲某種類型的數據的,你可以通過小測試來鞏固記憶。

為了做下面的測試,假設你有一個可以在圖書館展示數據的應用程序。

Q:你會用什么來創建圖書館中所有作者的列表?

答案:集合!它會自動移除重復的數據,也就是說你可以任意輸入作者的名字,多少次都可以,但是你只會有一次錄入 - 除非你輸錯了作者的名字!

Q:你怎么按字母排序的方式存儲一個多產作者的所有作品的標題?

答案:用數組!這種情況下你有許多相似的數據對象(所有的標題都是字符串類型的),并且它們有順序(標題必須按照字母順序排列),這絕對是使用數組的最佳場景。

Q:你怎樣存儲每個作者最受歡迎的作品?

答案:字典!如果你使用作者的名字作為鍵,使用作者最受歡迎的作品為值,你可以以下面的方式獲取這個作者最受歡迎的作品:

mostPopularBooks["Gillian Flynn"] //Returns "Gone Girl"

擴展閱讀

我想特別感謝我的一個同事Chris Wagner,在我們接觸到 Swift 之前他寫了這篇教程的 OC 版本,為了可以使用他的筆記和示例項目,把那篇教程也放在這里。

我也要感謝蘋果公司的 Swift 團隊 - 盡管在原生的數據結構方面還有很大的提升空間,我已經很享受用 Swift 編碼和測試了。一段時間內我可能還會使用Foundation下的數據結構,但也可能開發一些 Swift 小插件。

不管怎樣,如果你想學習更多 iOS 的數據結構,這兒有一些很棒的資源:

  • NSHipster是一個很棒的資源,可以讓你去探索Cocoa的包括數據結構在內的一些鮮為人知的API。
  • PSPDFKit公司的Peter Steinberger的最著名的關于Foundation 數據結構的excellent article in ObjC.io issue 7
  • 前任UIKit工程師Andy Matuschak的一篇關于 Swift 中基于結構的數據結構的文章article in ObjC.io issue 16
  • AirspeedVelocity,一個研究Swift本質特征的博客 - 由于在標題中引用了Monty Python的臺詞而獲得了加分(Monty Python 1975年有一個電影 叫 Monty Python and the Holy Grail,里面有一個角色叫bridgekeeper ,他有一句臺詞:What... is the air-speed velocity of an unladen swallow? )。
  • 一篇很棒的文章deep dive into the internals of NSMutableArray深入研究了NSMutableArray,以及修改NSMutableArray中的數據對內存的影響。
  • 一篇關于NSArray and CFArray performance changes with extremely large data sets很棒的研究。這篇文章進一步證明了蘋果對類的命名并不是基于類在后臺的行為,而是它們于開發者的行為。
  • 如果你想學習更多的算法復雜度分析Introduction to Algorithms會教你比你在實際應用中可能了解到的還要多的內容,但能使你順利通過工作面試。

如果你想仔細分析呈現在這篇文章中的數據,你可以自己下載the numbers spreadsheet used to track all the testing runs with Swift 1.2分析數據。

更多問題或者想進一步分析數據的話,在下面盡情地留言吧!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,117評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,860評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,128評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,291評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,025評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,421評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,477評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,642評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,177評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,970評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,157評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,717評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,410評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,821評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,053評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,896評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,157評論 2 375

推薦閱讀更多精彩內容