深入理解Swift中的Substring和String
前言
為文本字符串添加特性或者語(yǔ)法糖在各種編程語(yǔ)言中都很普遍。就拿大家都很熟悉的 C 語(yǔ)言舉例,C 字符串本質(zhì)是一個(gè)字符數(shù)組(characters array),但是每次輸入字符串的時(shí)候不用輸入 ['h','e','l','l','o'] ,直接打 hello 就可以了,因?yàn)檫@個(gè)操作編譯器幫你做了。
更高級(jí)的語(yǔ)言比如 Swift 處理字符串就不僅僅是當(dāng)做字符數(shù)組了,String 是一個(gè)完整的類(lèi)型,并且有各種特性。我們先來(lái)看一下 String 的一個(gè)特性:substring。
簡(jiǎn)單的看一下 String
首先粗略的了解一下字符串的實(shí)現(xiàn)。下面的代碼來(lái)自標(biāo)準(zhǔn)庫(kù)中 String.swift :
public struct String { public var _core: _StringCore }
當(dāng)然也有一些其他初始化設(shè)置,不過(guò)在聲明里只有這一個(gè)存儲(chǔ)屬性!秘密一定都在 StringCore.swift 里:
public struct _StringCore { public var _baseAddress: UnsafeMutableRawPointer? var _countAndFlags: UInt public var _owner: AnyObject? }
在這個(gè)類(lèi)型里還有很多其他東西,不過(guò)我們還是只關(guān)注存儲(chǔ)屬性:
- Base address — 一個(gè)指向內(nèi)部存儲(chǔ)的指針
- Count — 字符串長(zhǎng)度,UInt 類(lèi)型,在一個(gè) 64 位的系統(tǒng)中,意味著有 62(64 - 2) 位的空間可以表示長(zhǎng)度。這是一個(gè)非常大的數(shù)字。所以字符串的長(zhǎng)度不太可能溢出。
- Flags — 兩個(gè) bits 用來(lái)做標(biāo)志。第一位表示是否被 _StringBuffer 持有;第二位表示編碼格式是 ASCII 還是 UTF-16。
_StringCore 的真實(shí)情況比這里提到的要復(fù)雜的多,但是通過(guò)上面的內(nèi)容可以讓我們更容易理解字符串的一些信息:字符串的內(nèi)部存儲(chǔ)和存儲(chǔ)的大小(underlying storage and size)。
Substring
Swift 中要怎么創(chuàng)建一個(gè) substring?最簡(jiǎn)單的方式就是通過(guò)下標(biāo)從 string 取一段:
let str = "Hello Swift!" let slice = str[str.startIndex.. <str index="" str="" startindex="" nbsp="" offsetby:="" 5="" hello=""></str>
雖然很簡(jiǎn)單,但是代碼看起來(lái)不太優(yōu)雅。
String 的索引不是直觀的整型,所以截取時(shí)的位置索引需要利用 startIndex 和 index(_:offsetBy:)獲取。如果是從字符串開(kāi)始位置截取,可以省略掉 startIndex :
let withPartialRange = str[.. <str index="" str="" startindex="" nbsp="" offsetby:="" 5="" still="" hello=""></str>
或者用 collection 中的這個(gè)方法:
let slice = str.prefix(5) // still "Hello"
要記住字符串也是 collection ,所以你可以用集合下的方法,比如 prefix(),suffix(), dropFirst() 等。
Substring 的內(nèi)部原理
substring 一個(gè)神奇的地方是他們重用了父 string 的內(nèi)存。你可以把 substring 理解為父 string 的其中一段。
舉個(gè)例子,如果從一個(gè) 8000 個(gè)字符的字符串中截取 100 個(gè)字符,并不需要重新初始化 100 個(gè)字符的內(nèi)存空間。
這也意味著你可能不小心就把父 string 的生命周期延長(zhǎng)了。如果有一大段字符串,然后你只是截取了一小段,只要截取的小段字符串沒(méi)有釋放,大段的字符串也不會(huì)被釋放。
Substring 內(nèi)部到底是怎么做到的呢?
public struct Substring { internal var _slice: RangeReplaceableBidirectionalSlice <string></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 }
計(jì)算屬性 _wholeString(返回整個(gè)父字符串),startIndex 和 endIndex 都是通過(guò)內(nèi)部的 _slice 返回。
也可以看出 slice 是如何引用父字符串的。
Substring 轉(zhuǎn)換為 String
最后代碼里可能有很多 substring,但是函數(shù)的參數(shù)類(lèi)型需要的是 string。Substring 轉(zhuǎn)換到 string 的過(guò)程也很簡(jiǎn)單:
let string = String(substring)
因?yàn)?substrings 和它的父字符串共享同一個(gè)內(nèi)存空間,猜測(cè)創(chuàng)建一個(gè)新字符串應(yīng)該會(huì)初始化一片新的存儲(chǔ)空間。那么 string 的初始化到底過(guò)程是怎樣的呢。
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="" nbsp="" 4a="" if="" start="" sameposition="" in:="" x="" unicodescalars="" end="" self="" 4b="" else=""></end>
- 創(chuàng)建一個(gè)對(duì)原有父字符串的引用
- 獲取 substring 在父字符串中的開(kāi)始和結(jié)束位置
- 獲取 UTF-16 格式的 substring 內(nèi)容。_core 是 _StringCore 的一個(gè)實(shí)例。
- 判斷匹配的 unicode 編碼,生成一個(gè)新的字符串實(shí)例
把 substring 轉(zhuǎn)換成 string 的步驟非常簡(jiǎn)單,但是你可能要考慮是不是一需要這樣做。是不是進(jìn)行 substring 操作的時(shí)候都要求類(lèi)型是 string?如果對(duì) substring 的操作都需要轉(zhuǎn)成 string,那么輕量級(jí)的 substring 也就失去了意義。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
從零開(kāi)始講解Java微信公眾號(hào)消息推送實(shí)現(xiàn)
微信公眾號(hào)分為訂閱號(hào)和服務(wù)號(hào),無(wú)論有沒(méi)有認(rèn)證,訂閱號(hào)每天都能推送一條消息,也就是每天只能推送一次消息給粉絲,這篇文章主要給大家介紹了關(guān)于Java微信公眾號(hào)消息推送實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下2022-09-09當(dāng)Transactional遇上synchronized的解決方法分享
前些時(shí)間剛好刷到了有關(guān)于“# 【事務(wù)與鎖】當(dāng)Transactional遇上synchronized”這一類(lèi)的文章,感覺(jué)這也是工作中經(jīng)常會(huì)遇到的一類(lèi)問(wèn)題了。所以就針對(duì)這個(gè)話題進(jìn)行了分析并整理了常用的解決方法,希望對(duì)大家有所幫助2023-05-05spring @Scheduled注解的使用誤區(qū)及解決
這篇文章主要介紹了spring @Scheduled注解的使用誤區(qū)及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11SpringBoot2整合Redis實(shí)現(xiàn)讀寫(xiě)操作
Redis,對(duì)于大家來(lái)說(shuō)應(yīng)該不陌生,是經(jīng)常使用的開(kāi)發(fā)技術(shù)之一。本文將結(jié)合實(shí)例代碼,介紹SpringBoot2整合Redis實(shí)現(xiàn)讀寫(xiě)操作,感興趣的小伙伴們可以參考一下2021-07-07idea創(chuàng)建SpringBoot項(xiàng)目及注解配置相關(guān)應(yīng)用小結(jié)
Spring Boot是Spring社區(qū)發(fā)布的一個(gè)開(kāi)源項(xiàng)目,旨在幫助開(kāi)發(fā)者快速并且更簡(jiǎn)單的構(gòu)建項(xiàng)目,Spring Boot框架,其功能非常簡(jiǎn)單,便是幫助我們實(shí)現(xiàn)自動(dòng)配置,本文給大家介紹idea創(chuàng)建SpringBoot項(xiàng)目及注解配置相關(guān)應(yīng)用,感興趣的朋友跟隨小編一起看看吧2023-11-11解決Lombok使用@Builder無(wú)法build父類(lèi)屬性的問(wèn)題
這篇文章主要介紹了解決Lombok使用@Builder無(wú)法build父類(lèi)屬性的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09Java中重寫(xiě)和重載的區(qū)別及說(shuō)明
Java語(yǔ)言中的重載和重寫(xiě)是實(shí)現(xiàn)多態(tài)的兩種方式,但他們的實(shí)現(xiàn)方式和規(guī)則有所不同,重載發(fā)生在一個(gè)類(lèi)中,同名的方法如果有不同的參數(shù)列表,則視為重載,重寫(xiě)則發(fā)生在子類(lèi)和父類(lèi)之間,要求子類(lèi)重寫(xiě)方法和父類(lèi)被重寫(xiě)方法有相同的返回類(lèi)型2024-10-10