當前位置:成語大全網 - 新華字典 - Swift進階-String源碼解析

Swift進階-String源碼解析

Swift進階-類與結構體

Swift-函數派發

Swift進階-屬性

Swift進階-指針

Swift進階-內存管理

Swift進階-TargetClassMetadata和TargetStructMetadata數據結構源碼分析

Swift進階-Mirror解析

Swift進階-閉包

Swift進階-協議

Swift進階-泛型

Swift進階-String源碼解析

Swift進階-Array源碼解析

創建壹個空的字符串發生了什麽?

這裏並不能看出String的內存結構。那麽接下來就借助 Swift源碼 的方式看看String在內存中到底是如何存儲的。

打開swift源碼 -> stdib裏的 String.swift

最直觀地可以看到 String 是壹個結構體,就是我們所說的值類型;它有壹個成員變量 _StringGuts

其中最後有壹個創建空字符串初始化方式 self.init(_StringGuts()) :

接下來看看這個 _StringGuts 到底是什麽東西?

同樣找到swift源碼 -> stdib裏的 StringGuts.swift

_StringGuts 也是壹個結構體,它有壹個成員變量是 _StringObject 類型的實例;

並且在最後是通過初始化出壹個 _StringObject 類型的實例來初始化 _StringGuts 的。

所以真正swift的 String 的實質就是 _StringObject 。接下來看看 _StringObject 到底是什麽玩意兒?

找到swift源碼 -> stdib裏的 StringObject.swift ,可以看到 _StringObject 是壹個結構體,再找到空字符串的初始化函數:

ps: 註意這裏初始化時的傳參,下面會說到這幾個成員

最終找到字符串最終初始化函數,該函數是對成員的初始化賦值,那麽只要搞懂這幾個成員是代表什麽意思,那就能搞清楚字符串的底層實質了。

_StringObject 存儲著壹些成員變量,文章最開始使用x/8g格式化輸出壹個空字符串對象empty的時候,那我猜測:輸出的內容應該就是 _StringObject 裏的_count、_variant、_discriminator、_flags。

internal var _variant: Variant 是壹個枚舉值,默認是immortal 0:

internal var _discriminator: UInt8 在初始化的時候傳遞了壹個Nibbles.emptyString( Nibbles 是壹個枚舉類型):

0xE000_0000_0000_0000 與文章最上面截圖相對應起來了:

那接下來我們就能測試壹下字符串了:

字符a的ASCII編碼是97,97的16進制是61,註意那個2的字節位的輸出

小於等於15個字符串時,會記錄字符串的位數。

對於小字符串(小於等於15個字符串)來說,是優先直接存到內存當中,無需另外分配內存空間的。(和NSString差不多類似)

接下來看看中文字符

中文字符不是ASCII編碼,壹個中文字符占據3個字節(24位),也是我們上面通過源碼分析得出的使用了 0xA000_0000_0000_0000

所以 _StringObject.Nibbles 是壹個識別器,去識別字符串是不是ASCII編碼。

對於大字符串(大於15個字符串)來說,原本的小字符串占據的15個字節已經不足以存儲字符串了,那就會發生改變:

來看看0x8000000000000000在源碼中出現的定義是壹個大原始字符串:

那剩下的 0x000000010000b860 到底是什麽東西呢?它是字符串的內存 相對地址;

那應該偏移多少呢?來看源碼裏的註解

意思是0x10000b860需要加上偏移量 nativeBias 即32,32的16進制是0x20:

0x10000b860 + 0x20 = 0x10000b880

在源碼註解裏找到大字符串標誌位

大字符串前8位就記錄著這些標誌位信息,0xd000000000000012就是大字符串前8位,拿到科學計算器裏看看標誌位:

所以count是0x12,轉換成10進制就是18,正好對應18個字符。

對於 String 來說,它並不支持通過下標的方式獲取字符

只能通過 String.Index 的方式來訪問

對於 Swift 來說, String 是壹系列字符的集合,也就意味著 String 中的每壹個元素是不等長的。那也就意味著我們在進行內存移動的時候步長是不壹樣的,什麽意思?

比如我們有壹個 Array 的數組(Int 類型),當我們遍歷數組中的元素的時候,因為每個元素的內存大小是壹致的,所以每次的偏移量就是 8 個字節。

但是對於字符串來說不壹樣,比如我要方位 str[1] 那麽我是不是要把我這個字段遍歷完成之後才能夠確定是的偏移量?

依次內推每壹次都要重新遍歷計算偏移量,這個時候無疑增加了很多的內存消耗。這就是為什麽我們不能通過 Int 作為下標來去訪問 String 。

可以很直觀的看到 Index 的定義:

position aka encodedOffset 壹個 48 bit 值,用來記錄碼位偏移量;

transcoded offset : 壹個 2 bit 的值,用來記錄字符使用的碼位數量;

grapheme cache : 壹個 6 bit 的值,用來記錄下壹個字符的邊界;

reserved : 7 bit 的預留字段;

scalar aligned : 壹個 1 bit 的值,用來記錄標量是否已經對齊過。

String.Index 的本質就是壹個64位的位域信息,這個位域信息展示的就是上面的解釋。

創建 String.Index 實際上就是通過 encodedOffset 或者 transcoded offset , encodedOffset 就是方便我們從內存中通過下標訪問到字符串。