Swift 中 Substrings 與 String

原做者 Greg Heo (@gregheo) | Twitter ,原文連接:Swift Substringsgit

爲文本字符串添加特性或者語法糖在各類編程語言中都很廣泛。就拿你們都很熟悉的 C 語言舉例,C 字符串本質是一個字符數組(characters array),可是每次輸入字符串的時候不用輸入 ['h','e','l','l','o'] ,直接打 hello 就能夠了,由於這個操做編譯器幫你作了。 更高級的語言好比 Swift 處理字符串就不只僅是當作字符數組了,String 是一個完整的類型,而且有各類特性。咱們先來看一下 String 的一個特性:substrings。程序員

簡單的看一下 Strings

首先粗略的瞭解一下字符串的實現。下面的代碼來自標準庫中 String.swiftgithub

public struct String {
  public var _core: _StringCore
}
複製代碼

固然也有一些其餘初始化設置,不過在聲明裏只有這一個存儲屬性!祕密必定都在 StringCore.swift 裏:編程

public struct _StringCore {
  public var _baseAddress: UnsafeMutableRawPointer?
  var _countAndFlags: UInt
  public var _owner: AnyObject?
}
複製代碼

在這個類型裏還有不少其餘東西,不過咱們仍是隻關注存儲屬性:swift

  • Base address — 一個指向內部存儲的指針
  • Count — 字符串長度,UInt 類型,在一個 64 位的系統中,意味着有 62(64 - 2) 位的空間能夠表示長度。這是一個很是大的數字。因此字符串的長度不太可能溢出。
  • Flags — 兩個 bits 用來作標誌。第一位表示是否被 _StringBuffer 持有;第二位表示編碼格式是 ASCII 仍是 UTF-16。 _StringCore 的真實狀況比這裏提到的要複雜的多,可是經過上面的內容可讓咱們更容易理解字符串的一些信息:字符串有一些內部存儲和存儲的大小(underlying storage and size)。

Substrings

Swift 中要怎麼建立一個 substring?最簡單的方式就是經過下標從 string 取一段:數組

let str = "Hello Swift!"
let slice = str[str.startIndex..<str.index(str.startIndex, offsetBy: 5)]
// "Hello"
複製代碼

雖然很簡單,可是代碼看起來不太優雅😄。 String 的索引不是直觀的整型,因此截取時的位置索引須要利用 startIndex 和 index(_:offsetBy:)獲取。若是是從字符串開始位置截取,能夠省略掉 startIndex :app

let withPartialRange = str[..<str.index(str.startIndex, offsetBy: 5)]
// still "Hello"
複製代碼

或者用 collection 中的這個方法:編程語言

let slice = str.prefix(5)
// still "Hello"
複製代碼

要記住字符串也是 collection ,因此你能夠用集合下的方法,好比 prefix(),suffix(), dropFirst() 等。ide

Substring 的內部原理

substring 一個神奇的地方是他們重用了父 string 的內存。你能夠把 substring 理解爲父 string 的其中一段。 函數

舉個例子,若是從一個 8000 個字符的字符串中截取 100 個字符,並不須要從新初始化 100 個字符的內存空間。 這也意味着你可能不當心就把父 string 的生命週期延長了。若是有一大段字符串,而後你只是截取了一小段,只要截取的小段字符串沒有釋放,大段的字符串也不會被釋放。 Substring 內部究竟是怎麼作到的呢?

public struct Substring {
  internal var _slice: RangeReplaceableBidirectionalSlice<String>
複製代碼

內部的 _slice 屬性保存着全部關於父字符串的信息:

// Still inside Substring
internal var _wholeString: String {
  return _slice._base
}
public var startIndex: Index { return _slice.startIndex }
public var endIndex: Index { return _slice.endIndex }
複製代碼

計算屬性 _wholeString(返回整個父字符串),startIndex 和 endIndex 都是經過內部的 _slice 返回。 也能夠看出 slice 是如何引用父字符串的。

Substring 轉換爲 String

最後代碼裏可能有不少 substring,可是函數的參數類型須要的是 string。Substring 轉換到 string 的過程也很簡單:

let string = String(substring)
複製代碼

由於 substrings 和它的父字符串共享同一個內存空間,猜想建立一個新字符串應該會初始化一片新的存儲空間。那麼 string 的初始化到底過程是怎樣的呢。

extension String {
  public init(_ substring: Substring) {
    // 1
    let x = substring._wholeString
    // 2
    let start = substring.startIndex
    let end = substring.endIndex
    // 3
    let u16 = x._core[start.encodedOffset..<end.encodedOffset]
    // 4A
    if start.samePosition(in: x.unicodeScalars) != nil
    && end.samePosition(in: x.unicodeScalars) != nil {
      self = String(_StringCore(u16))
    }
    // 4B
    else {
      self = String(decoding: u16, as: UTF16.self)
    }
  }
}
複製代碼
  1. 建立一個對原有父字符串的引用
  2. 獲取 substring 在父字符串中的開始和結束位置
  3. 獲取 UTF-16 格式的 substring 內容。_core 是 _StringCore 的一個實例。
  4. 判斷匹配的 unicode 編碼,生成一個新的字符串實例 把 substring 轉換成 string 的步驟很是簡單,可是你可能要考慮是否是一須要這樣作。是否是進行 substring 操做的時候都要求類型是 string?若是對 substring 的操做都須要轉成 string,那麼輕量級的 substring 也就失去了意義。🤔

StringProtocol

StringProtocol 上場!StringProtocol 真是面向協議編程的一個優秀表明。StringProtocol 抽象了字符串的場景功能,好比 uppercased(), lowercased(),還有 comparablecollection 等。String 和 Substring 都聲明瞭 StringProtocol。 也就是說你能夠直接使用 == 對 substring 和 string 進行判等,不須要類型轉換:

let helloSwift = "Hello Swift"
let swift = helloSwift[helloSwift.index(helloSwift.startIndex, offsetBy: 6)...]

// comparing a substring to a string 😱
swift == "Swift"  // true
複製代碼

也能夠遍歷 substring,或者從 substring 截取子字符串。 在標準庫裏也有一小部分函數使用 StringProtocol 類型做爲參數。好比把一個字符串轉換爲整型就是:init(text: StringProtocol)。 雖然你可能不關心是 string 和 substring,可是使用 StringProtocol 做爲參數類型,調用者就不用進行類型轉換,對他們會友好不少。

總結

  • 字符串仍是那個常見的字符串。
  • Substring 是字符串的一部分,和父字符串共享同一塊內存空間,而且記錄了本身的開始和結束位置。
  • String 和 Substring 都聲明實現了 StringProtocol。StringProtocol 包含了一個字符串的基本屬性和功能。

是否是以爲本身也能夠自定義字符串類型,實現 StringProtocol ?

/// Do not declare new conformances to `StringProtocol`. Only the `String` and
/// `Substring` types in the standard library are valid conforming types.
public protocol StringProtocol 複製代碼

可是蘋果爸爸表示了拒絕。


相關文章
相關標籤/搜索