Swift 中 Substring 與 String

原作者 Greg Heo (@gregheo) | Twitter 课舍,原文鏈接:Swift Substrings

為文本字符串添加特性或者語法糖在各種編程語言中都很普遍敬特。就拿大家都很熟悉的 C 語言舉例,C 字符串本質(zhì)是一個字符數(shù)組(characters array)毒费,但是每次輸入字符串的時候不用輸入 ['h','e','l','l','o'] 丙唧,直接打 hello 就可以了,因為這個操作編譯器幫你做了觅玻。
更高級的語言比如 Swift 處理字符串就不僅僅是當(dāng)做字符數(shù)組了想际,String 是一個完整的類型,并且有各種特性溪厘。我們先來看一下 String 的一個特性:substring胡本。

簡單的看一下 String

首先粗略的了解一下字符串的實現(xiàn)。下面的代碼來自標(biāo)準庫中 String.swift

public struct String {
  public var _core: _StringCore
}

當(dāng)然也有一些其他初始化設(shè)置畸悬,不過在聲明里只有這一個存儲屬性侧甫!秘密一定都在 StringCore.swift 里:

public struct _StringCore {
  public var _baseAddress: UnsafeMutableRawPointer?
  var _countAndFlags: UInt
  public var _owner: AnyObject?
}

在這個類型里還有很多其他東西,不過我們還是只關(guān)注存儲屬性:

  • Base address — 一個指向內(nèi)部存儲的指針
  • Count — 字符串長度傻昙,UInt 類型闺骚,在一個 64 位的系統(tǒng)中彩扔,意味著有 62(64 - 2) 位的空間可以表示長度妆档。這是一個非常大的數(shù)字。所以字符串的長度不太可能溢出虫碉。
  • Flags — 兩個 bits 用來做標(biāo)志贾惦。第一位表示是否被 _StringBuffer 持有;第二位表示編碼格式是 ASCII 還是 UTF-16敦捧。

_StringCore 的真實情況比這里提到的要復(fù)雜的多须板,但是通過上面的內(nèi)容可以讓我們更容易理解字符串的一些信息:字符串的內(nèi)部存儲和存儲的大小(underlying storage and size)兢卵。

Substring

Swift 中要怎么創(chuàng)建一個 substring习瑰?最簡單的方式就是通過下標(biāo)從 string 取一段:

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

雖然很簡單,但是代碼看起來不太優(yōu)雅秽荤。
String 的索引不是直觀的整型甜奄,所以截取時的位置索引需要利用 startIndex 和 index(_:offsetBy:)獲取柠横。如果是從字符串開始位置截取,可以省略掉 startIndex :

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

或者用 collection 中的這個方法:

let slice = str.prefix(5)
// still "Hello"

要記住字符串也是 collection 课兄,所以你可以用集合下的方法牍氛,比如 prefix(),suffix()烟阐, dropFirst() 等搬俊。

Substring 的內(nèi)部原理

substring 一個神奇的地方是他們重用了父 string 的內(nèi)存。你可以把 substring 理解為父 string 的其中一段蜒茄。


舉個例子唉擂,如果從一個 8000 個字符的字符串中截取 100 個字符,并不需要重新初始化 100 個字符的內(nèi)存空間檀葛。
這也意味著你可能不小心就把父 string 的生命周期延長了楔敌。如果有一大段字符串,然后你只是截取了一小段驻谆,只要截取的小段字符串沒有釋放卵凑,大段的字符串也不會被釋放。
Substring 內(nèi)部到底是怎么做到的呢胜臊?

public struct Substring {
  internal var _slice: RangeReplaceableBidirectionalSlice<String>

內(nèi)部的 _slice 屬性保存著所有關(guān)于父字符串的信息:

// 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 都是通過內(nèi)部的 _slice 返回。
也可以看出 slice 是如何引用父字符串的象对。

Substring 轉(zhuǎn)換為 String

最后代碼里可能有很多 substring黑忱,但是函數(shù)的參數(shù)類型需要的是 string。Substring 轉(zhuǎn)換到 string 的過程也很簡單:

let string = String(substring)

因為 substrings 和它的父字符串共享同一個內(nèi)存空間勒魔,猜測創(chuàng)建一個新字符串應(yīng)該會初始化一片新的存儲空間甫煞。那么 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. 創(chuàng)建一個對原有父字符串的引用
  2. 獲取 substring 在父字符串中的開始和結(jié)束位置
  3. 獲取 UTF-16 格式的 substring 內(nèi)容冠绢。_core 是 _StringCore 的一個實例抚吠。
  4. 判斷匹配的 unicode 編碼,生成一個新的字符串實例

把 substring 轉(zhuǎn)換成 string 的步驟非常簡單弟胀,但是你可能要考慮是不是一需要這樣做楷力。是不是進行 substring 操作的時候都要求類型是 string?如果對 substring 的操作都需要轉(zhuǎn)成 string孵户,那么輕量級的 substring 也就失去了意義萧朝。??

StringProtocol

StringProtocol 上場!StringProtocol 真是面向協(xié)議編程的一個優(yōu)秀代表夏哭。StringProtocol 抽象了字符串的常見功能检柬,比如 uppercased(), lowercased(),還有 comparable竖配、collection 等何址。String 和 Substring 都聲明了 StringProtocol酱固。
也就是說你可以直接使用 == 對 substring 和 string 進行判等,不需要類型轉(zhuǎn)換:

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

// comparing a substring to a string 
swift == "Swift"  // true

也可以遍歷 substring头朱,或者從 substring 截取子字符串运悲。
在標(biāo)準庫里也有一小部分函數(shù)使用 StringProtocol 類型作為參數(shù)。比如把一個字符串轉(zhuǎn)換為整型就是:init(text: StringProtocol)项钮。
雖然你可能不關(guān)心是 string 和 substring班眯,但是使用 StringProtocol 作為參數(shù)類型,調(diào)用者就不用進行類型轉(zhuǎn)換烁巫,對他們會友好很多署隘。

總結(jié)

  • 字符串還是那個常見的字符串。
  • Substring 是字符串的一部分亚隙,和父字符串共享同一塊內(nèi)存空間磁餐,并且記錄了自己的開始和結(jié)束位置。
  • String 和 Substring 都聲明實現(xiàn)了 StringProtocol阿弃。StringProtocol 包含了一個字符串的基本屬性和功能诊霹。


是不是覺得自己也可以自定義字符串類型,實現(xiàn) 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

但是蘋果爸爸表示了拒絕脾还。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市入愧,隨后出現(xiàn)的幾起案子鄙漏,更是在濱河造成了極大的恐慌,老刑警劉巖棺蛛,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件怔蚌,死亡現(xiàn)場離奇詭異,居然都是意外死亡旁赊,警方通過查閱死者的電腦和手機桦踊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來彤恶,“玉大人钞钙,你說我怎么就攤上這事∩耄” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵瘫怜,是天一觀的道長术徊。 經(jīng)常有香客問我,道長鲸湃,這世上最難降的妖魔是什么赠涮? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任子寓,我火速辦了婚禮,結(jié)果婚禮上笋除,老公的妹妹穿的比我還像新娘斜友。我一直安慰自己,他們只是感情好垃它,可當(dāng)我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布鲜屏。 她就那樣靜靜地躺著,像睡著了一般国拇。 火紅的嫁衣襯著肌膚如雪洛史。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天酱吝,我揣著相機與錄音也殖,去河邊找鬼。 笑死务热,一個胖子當(dāng)著我的面吹牛忆嗜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播崎岂,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼霎褐,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了该镣?” 一聲冷哼從身側(cè)響起冻璃,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎损合,沒想到半個月后省艳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡嫁审,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年跋炕,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片律适。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡辐烂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出捂贿,到底是詐尸還是另有隱情纠修,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布厂僧,位于F島的核電站扣草,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜辰妙,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一鹰祸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧密浑,春花似錦蛙婴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至呆瞻,卻和暖如春台夺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背痴脾。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工颤介, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人赞赖。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓滚朵,卻偏偏與公主長得像,于是被迫代替她去往敵國和親前域。 傳聞我的和親對象是個殘疾皇子辕近,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內(nèi)容