Swift String 在内存中是如何存储的
1.空字符串
var emty = ""
print("end")
lldb调试
0xe代表什么
探究源码
@inlinable @inline(__always)
@_semantics("string.init_empty")
public init() { self.init(_StringGuts()) }
当前的 init 方法 -> 内部的 init 方法 -> _StringGuts 对象
StringGuts.swift文件中
// Empty string
@inlinable @inline(__always)
init() {
self.init(_StringObject(empty: ()))
}
StringGuts -> StringObject
在StringObject.Swift 文件
@inlinable @inline(__always)
internal init(empty:()) {
// Canonical empty pattern: small zero-length string
#if arch(i386) || arch(arm)
self.init(
count: 0,
variant: .immortal(0),
discriminator: Nibbles.emptyString,
flags: 0)
#else
...
调用了 init(count: variant: discirminator: flags:) 这个方法
推断String 的基本数据结构
@usableFromInline
internal var _count: Int
@usableFromInline
internal var _variant: Variant
@usableFromInline
internal var _discriminator: UInt8
@usableFromInline
internal var _flags: UInt16
其中discriminator: Nibbles.emptyString
Nibbles是个枚举
// Namespace to hold magic numbers
@usableFromInline @frozen
enum Nibbles {}
extension _StringObject.Nibbles {
// The canonical empty sting is an empty small string
@inlinable @inline(__always)
internal static var emptyString: UInt64 {
return _StringObject.Nibbles.small(isASCII: true)
}
}
extension _StringObject.Nibbles {
// Discriminator for small strings
@inlinable @inline(__always)
internal static func small(isASCII: Bool) -> UInt64 {
return isASCII ? 0xE000_0000_0000_0000 : 0xA000_0000_0000_0000
}
Nibbles.small 小字符串
0xE000_0000_0000_0000 是ASCII 字符
0xA000_0000_0000_0000 不是ASCII 字符
2.15个字节以内的小字符串
var emty = "abcdefghigkabcd"
print("end")
lldb调试
第一个红框代表 abcd对应的ASCII 字符
第二个红框 e 代表ASCII 字符 ,f代表内存所需要的字节数 15
3. 大字符串
var emty = "abcdefghigkabcdf"
print("end")
lldb调试
第二个红框
0x8标识的是大字符串
Nibbles 的布局结构如下
结合 nibbles 在内存当中的布局,知道其中 b60:b0 是存储字符串的地址,当然这个 地址要加上偏移量,这个偏移量是 32
0x10f3e88a0 + 32 = 0x10F3E88C0
原生的Swift大字符串来说,采取的是 tail-allocated 存储,也就是在当前实例分配有超出其最后存储属性的额外空间,额外的空间可用于直接在实例中存储任意数据,无需额外的堆分配
第一个红框
网上查询得知,表示countAndFlags
countAndFlags布局
0xd000000000000010转化为二进制
63位 是ASCII 字符
60位是tail-allocated 存储
47-0代表count, 0x10 , 16个字节储存
Swift Index
字符串是UTF-8 编码,最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符 号,根据不同的符号而变化字节长度
字符串使用下标访问,比如我要方位 str[1] 那么我是不是要把 我 这个字段遍历完成之后才能够确定 是 的偏移量?依次内推每一次都要重新遍历计算偏移量,这个时候无疑增加了很多的内存消耗。这就是为什么我们不能通过 Int 作为下标来去访问 String
Index 布局
position 一个48 bit 值,用来记录码位偏移量
transcoded offset: 一个 2 bit 的值,用来记录字符使用的码位数量
grapheme cache: 一个 6 bit 的值,用来记录下一个字符的边界
reserved : 7 bit 的预留字段
scalar aligned: 一个1 bit 的值,用来记录标量是否已经对齐过
看出 String.Index 关键是encodedOffset 和 offset