Swift 中的 String 和 Substring 如何工作
| 作者:Suragch
以下所有示例都基於下面這行程式碼:
var str = "Hello, playground" 複製程式碼
字串與子字串
字串在不同的 Swift 版本中變化比較大。在 Swift 4 中,當需要從一個字串中獲取子串時,我們獲取到的是Substring
型別而不是一個String
型別的值。為什麼會這樣?在 Swift 中,字串是值型別。這意味著如果想從一個字串中生成一個新的字串,則需要做拷貝處理。這對於穩定性是有益的,但效率卻不高。
從另一方面講,一個Substring
則是對其原始String
的一個引用。它不需要做拷貝操作,所以更加高效。但是有另一個問題,假設我們要從一個很長的字串中獲取一個長度為 10 的 Substring,由於這個 Substring 會引用 String,那麼只要 Substring 一直存在,String 就必須一直存在。所以,任何時候當處理完 Substring 後,需要將其轉換為 String。
let myString = String(mySubstring) 複製程式碼
這樣只會拷貝子串,而原來的字串可以被正確地回收。Substring 作為一種型別,本身即表示臨時存在的。
在 Swift 4 另一個大的改進是字串又成為集合了。這意味著在集合上的所有操作,也可以應用在字串中(如下標、迭代字元、過濾等)。
String.Index
在我們更多地瞭解子字串之前,瞭解 String 索引如何作用於字串中的字元會有很大的幫助。
startIndex 和 endIndex
startIndex endIndex
示例
var str = "Hello, playground" // character str[str.startIndex] // H str[str.endIndex]// error: after last character // range let range = str.startIndex..<str.endIndex str[range]// "Hello, playground" 複製程式碼
基於 Swift 4 的單側範圍(one-side ranges
)功能,可將範圍簡化為以下形式之一。
let range = str.startIndex... let range = ..<str.endIndex 複製程式碼
為了清晰起見,在下面的示例中,我將使用完整形式,不過為了更可讀,你可能在你的程式碼中傾向於使用單側範圍。
after
如在index(after: String.Index)
-
after
指的是給定索引後面的字元索引。
示例:
// character let index = str.index(after: str.startIndex) str[index]// "e" // range let range = str.index(after: str.startIndex)..<str.endIndex str[range]// "ello, playground" 複製程式碼
before
如在index(before: String.Index)
-
before
指的是給定索引前面的字元索引。
示例
// character let index = str.index(before: str.endIndex) str[index]// d // range let range = str.startIndex..<str.index(before: str.endIndex) str[range]// Hello, playgroun 複製程式碼
offsetBy
如在index(String.Index, offsetBy: String.IndexDistance)
-
offsetBy
的值可為正也可為負,以指定索引為起始位置。雖然它的型別是 String.IndexDistance,但可以給它一個 Int 值
示例
// character let index = str.index(str.startIndex, offsetBy: 7) str[index]// p // range let start = str.index(str.startIndex, offsetBy: 7) let end = str.index(str.endIndex, offsetBy: -6) let range = start..<end str[range]// play 複製程式碼
limitedBy
如在index(String.Index, offsetBy: String.IndexDistance, limitedBy: String.Index)
-
limitedBy
可用於確保偏移量不會導致索引超出範圍。這是一個有界的索引。由於偏移量可能超出限制,因此此方法返回Optional
。如果索引超出範圍,則返回 nil。
示例:
// character if let index = str.index(str.startIndex, offsetBy: 7, limitedBy: str.endIndex) { str[index]// p } 複製程式碼
如果偏移量為77
而不是7
,那麼將跳過if
語句。
獲取子字串
我們可以使用下標或許多其他方法(例如,字首,字尾,拆分)從字串中獲取子字串。但是,我們仍然需要使用String.Index
而不是Int
值來指定索引範圍。
字串的起始值
我們可以使用下標(注意 Swift 4 的單側範圍):
let index = str.index(str.startIndex, offsetBy: 5) let mySubstring = str[..<index] // Hello 複製程式碼
或者prefix
:
let index = str.index(str.startIndex, offsetBy: 5) let mySubstring = str.prefix(upTo: index) // Hello 複製程式碼
或者更簡單的方式:
let mySubstring = str.prefix(5) // Hello 複製程式碼
字串的結束值
使用下標:
let index = str.index(str.endIndex, offsetBy: -10) let mySubstring = str[index...] // playground 複製程式碼
或者suffix
:
let index = str.index(str.endIndex, offsetBy: -10) let mySubstring = str.suffix(from: index) // playground 複製程式碼
或者更簡單的方式:
let mySubstring = str.suffix(10) // playground 複製程式碼
請注意,當使用suffix(from: index)
時,我必須使用 -10 從最後算起。 當只使用suffix(x)
時,則不需要,字尾只佔用字串的最後 x 個字元。
字串的範圍
我們再次使用下標:
let start = str.index(str.startIndex, offsetBy: 7) let end = str.index(str.endIndex, offsetBy: -6) let range = start..<end let mySubstring = str[range]// play 複製程式碼
將 Substring 轉換為 String
不要忘記,當您準備儲存子字串時,應將其轉換為String
,以便清除舊字串的記憶體。
let myString = String(mySubstring) 複製程式碼
使用 Int 索引擴充套件
對字串使用Int
索引要容易得多。實際上,通過使用基於 Int 的擴充套件,可以隱藏 String 索引的複雜性。然而,在讀完 Airspeed Velocity 和 Ole Begemann 的文章Strings in Swift 3
後,我猶豫不決。此外,Swift 團隊故意沒有使用Int
索引,而仍然使用String.Index
。原因是 Swift 中的 Character 在底層的長度並不完全相同。單個 Swift 字元可能由一個、兩個甚至更多Unicode
組成。 因此,每個唯一的 String 必須計算其 Character 的索引。
不得不說,我希望 Swift 團隊找到一種方法來在將來抽象出String.Index
。但在此之前,我選擇使用他們的 API。它幫助我記住 String 操作不僅僅是簡單的 Int 索引查詢。