Swift 4 中的 String 與 Substring

原文Swift Substrings莺禁,作者Greg Heo

(sub)string不是字符串?是也不是

文本字符串非常常用,編程語言通常對其提供特殊特性或者語法糖。以大家最常用的C語言來說拷淘,其字符串只是字符數(shù)組。但是你不需要創(chuàng)建一個數(shù)組['h','e','l','l','o'] 指孤,只要輸入"hello"編譯器就會打點好一切启涯。

更高級的語言比如Swift中贬堵,字符串不僅僅是簡單的字符數(shù)組,而是一個完整的類型并且有著各種特性结洼。在這次的string觀察中黎做,我們關(guān)注他的一小部分行為:substring。

String松忍,簡述

首先蒸殿,我們快速遍歷一次string的實現(xiàn)。以下來自標準庫的 String.swift 文件:

public struct String {
  public var _core: _StringCore
}

里面還有很多的構(gòu)造函數(shù)鸣峭,不過整個主類型里只有一個存儲屬性宏所!好東西一定都在 StringCore.swift 里:

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

這個類型里面依然很多東西,我們還是只關(guān)注存儲屬性:

  • Base address — 指向底層存儲的原始指針叽掘。
  • Count — 字符串長度楣铁,存儲在_countAndFlags的低位 (UInt.bitWidth - 2) 玖雁。 在64位環(huán)境下更扁,這表示最大可用62位表示字符串長度,即 4 x 10^18 — 整整4 EB字節(jié)赫冬!
  • Flags — 兩個標志位:一位表示字符串底層使用Swift原生的 _StringBuffer 浓镜,或者是 NSString 形式的 Cocoa buffer;第二位表示buffer存儲的編碼是ASCII還是UTF-16劲厌。

_StringCore還有更多的復雜細節(jié)膛薛,這樣快速過一遍就能讓我們找到想要的:string有底層存儲以及大小。

Substring

Swift中如何創(chuàng)建一個substring补鼻?最簡單的辦法是通過下標對string切片:

let str = "Hello Swift!"

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

好吧哄啄,很簡單,但是代碼不太好看??风范。

string的索引不使用整數(shù)咨跌,而是靠startIndexindex(_:offsetBy:)操作。既然我們從startIndex開始硼婿,那么可以靠partial range簡化一下代碼锌半。

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

或者用collection(容器)的切片方法:

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

記住,string是容器寇漫,你可以使用所有容器常用的方法刊殉,比如 prefix()suffix()州胳,dropFirst()记焊,等等。

Substring內(nèi)部

substring的一部分魔法是重用了“parent” string的內(nèi)存栓撞。你可以認為substring由base stringrange構(gòu)成遍膜。

image

這意味著從一個8000字符的string上切一個100字符的substring不需要分配額外內(nèi)存,也不用復制100個字符。

這也意味著你可能無意延長了你的base string的生命周期捌归。如果你有一個超大字符串存了一整本小說肛响,然后從其中切片了一個單詞,那么只要substring還在惜索,這個巨大的string就會一直陰魂不散特笋。

那么到底substring內(nèi)部是如何跟蹤這些的呢?

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

內(nèi)部的 _slice屬性保存了關(guān)于base string的所有信息:

// 仍然是 Substring 的代碼
internal var _wholeString: String {
  return _slice._base
}
public var startIndex: Index { return _slice.startIndex }
public var endIndex: Index { return _slice.endIndex }

計算屬性 _wholeString(返回原來完整的字符串)巾兆,以及 startIndexendIndex (指出在string中切片的范圍)只是簡單的傳遞了內(nèi)部切片屬性的值猎物。

你也能看到切片如何用 _base來保存原始string的信息。

從Substring到String

所以你有了一堆各種各樣的的substring角塑,而你的函數(shù)需要的是string蔫磨。怎么辦?不用擔心圃伶,把substring轉(zhuǎn)換成string很簡單:

let string = String(substring)

既然substring和他們的base string共享內(nèi)存堤如,創(chuàng)建一個新的string理當分配新的內(nèi)存。那么String中這個接受substring的構(gòu)造函數(shù)中有發(fā)生了什么窒朋?

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. 獲取完整base string的引用搀罢。

  2. 獲取開始和結(jié)束范圍的索引。

  3. 獲取切片的UTF-16表現(xiàn)形式侥猩。_core是一個_StringCore實例榔至,而 encodedOffset屬性是string的UTF-16版本的索引。

  4. 檢查索引是否與unicode標量匹配欺劳,分支4A表示表示你沒有不成對的代理碼 (Unicode is hard)唧取,因而可以使用基于 _StringCore 的UTF-16 buffer切片直接創(chuàng)建新的字符串。

    否則划提,走分支4B重新以UTF-16編碼buffer枫弟,使用init(decoding:as:)初始化string。無論如何腔剂,最后都得到一個新的String實例媒区。

把substring轉(zhuǎn)換為string非常簡單,不過真的是必須嘛掸犬?每次使用的時候都要把subtstring用String()包起來袜漩?那樣的話我們使用輕量化的substring所得到的效率不就白費了?

StringProtocol

來看看 StringProtocol湾碎!作為面向協(xié)議編程的絕佳例子宙攻, StringProtocol抽象了字符串的函數(shù)功能,比如uppercased()介褥,lowercased()座掘, comparable递惋,hashable,collection溢陪,等等萍虽。 StringSubstring都遵循StringProtocol

這意味著你可以用 ==比較string和substring形真,而不用先轉(zhuǎn)換substring:

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

// 直接比較 substring 和 string ??
swift == "Swift"  // true 

你也可以遍歷substring杉编,以及從substring再取一個substring。

你會發(fā)現(xiàn)標準庫里有一些函數(shù)接受StringProtocol而非String咆霜,比如把string轉(zhuǎn)換成整數(shù)和浮點使用的構(gòu)造器init(text: StringProtocol)邓馒。

也許你自己的代碼里,你不在乎處理的是string還是substring蛾坯?那樣的話光酣,考慮接受 StringProtocol參數(shù),那樣調(diào)用者就不用在傳參的時候再手工轉(zhuǎn)換一次了脉课。

結(jié)語

小結(jié)一下:

  • String 還是字符串救军,始終如一。
  • Substring 是string的切片下翎。它們與base string共享內(nèi)存buffer缤言,并擁有一對范圍索引宝当。
  • StringProtocol 抽取出字符串的特征以及如何訪問其功能视事,放進一個協(xié)議中。StringSubstring都遵循StringProtocol庆揩。
image

所以有誰準備好寫一個自己的字符串類型了俐东?搞一個你自己StringProtocol實現(xiàn)來加入string聚會???

/// 不要定義新的 `StringProtocol`實現(xiàn)订晌。 標準庫中只有 `String` 和
/// `Substring` 是有效的實現(xiàn)虏辫。
public protocol StringProtocol

那好吧,我們還是來搞自己的 boolean類型好了吧锈拨???

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末砌庄,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子奕枢,更是在濱河造成了極大的恐慌娄昆,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缝彬,死亡現(xiàn)場離奇詭異萌焰,居然都是意外死亡,警方通過查閱死者的電腦和手機谷浅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門扒俯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奶卓,“玉大人,你說我怎么就攤上這事撼玄《峁茫” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵掌猛,是天一觀的道長瑟幕。 經(jīng)常有香客問我,道長留潦,這世上最難降的妖魔是什么只盹? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮兔院,結(jié)果婚禮上殖卑,老公的妹妹穿的比我還像新娘。我一直安慰自己坊萝,他們只是感情好孵稽,可當我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著十偶,像睡著了一般菩鲜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上惦积,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天接校,我揣著相機與錄音,去河邊找鬼狮崩。 笑死蛛勉,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的睦柴。 我是一名探鬼主播诽凌,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼坦敌!你這毒婦竟也來了侣诵?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤狱窘,失蹤者是張志新(化名)和其女友劉穎杜顺,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體训柴,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡哑舒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了幻馁。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片洗鸵。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡越锈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出膘滨,到底是詐尸還是另有隱情甘凭,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布火邓,位于F島的核電站丹弱,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏铲咨。R本人自食惡果不足惜躲胳,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望纤勒。 院中可真熱鬧坯苹,春花似錦、人聲如沸摇天。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽泉坐。三九已至为鳄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間腕让,已是汗流浹背孤钦。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留记某,地道東北人司训。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像液南,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子勾徽,可洞房花燭夜當晚...
    茶點故事閱讀 44,629評論 2 354

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