Swift探索( 十一): String源碼分析

一:String 的內(nèi)存布局

1. String 源碼分析

Swift源碼 中找到 String.swift 文件并定位到 String 的定義。

@frozen
public struct String {
  public // @SPI(Foundation)
  var _guts: _StringGuts

  @inlinable @inline(__always)
  internal init(_ _guts: _StringGuts) {
    self._guts = _guts
    _invariantCheck()
  }

  // This is intentionally a static function and not an initializer, because
  // an initializer would conflict with the Int-parsing initializer, when used
  // as function name, e.g.
  //   [1, 2, 3].map(String.init)
  @_alwaysEmitIntoClient
  @_semantics("string.init_empty_with_capacity")
  @_semantics("inline_late")
  @inlinable
  internal static func _createEmpty(withInitialCapacity: Int) -> String {
    return String(_StringGuts(_initialCapacity: withInitialCapacity))
  }

  /// Creates an empty string.
  ///
  /// Using this initializer is equivalent to initializing a string with an
  /// empty string literal.
  ///
  ///     let empty = ""
  ///     let alsoEmpty = String()
  @inlinable @inline(__always)
  @_semantics("string.init_empty")
  public init() { self.init(_StringGuts()) }
}

通過源碼可以發(fā)現(xiàn) String 的本質(zhì)是一個結(jié)構(gòu)體,并且有一個 _StringGuts 類型的成員變量 _guts 。在初始化的時候需要傳入 _StringGuts 類型的參數(shù)。接著找到 StringGuts.swift 文件,并定位到 _StringGuts 的定義。

struct _StringGuts: @unchecked Sendable {
  @usableFromInline
  internal var _object: _StringObject

  @inlinable @inline(__always)
  internal init(_ object: _StringObject) {
    self._object = object
    _invariantCheck()
  }

  // Empty string
  @inlinable @inline(__always)
  init() {
    self.init(_StringObject(empty: ()))
  }
}

可以發(fā)現(xiàn) _StringGuts 也是一個結(jié)構(gòu)體,并且遵守了協(xié)議 Sendable , 有一個 _StringObject 類型的成員變量 _object,并且初始化的時候需要傳入 _StringObject 類型的參數(shù)。接著找到 StringObject.swift 文件,并定位到 _StringObject 的定義。

@frozen @usableFromInline
internal struct _StringObject {
  // Namespace to hold magic numbers
  @usableFromInline @frozen
  enum Nibbles {}

  // Abstract the count and performance-flags containing word
  @frozen @usableFromInline
  struct CountAndFlags {
    @usableFromInline
    var _storage: UInt64

    @inlinable @inline(__always)
    internal init(zero: ()) { self._storage = 0 }
  }

#if arch(i386) || arch(arm) || arch(arm64_32) || arch(wasm32)
  @usableFromInline @frozen
  internal enum Variant {
    case immortal(UInt)
    case native(AnyObject)
    case bridged(_CocoaString)

    @inlinable @inline(__always)
    internal static func immortal(start: UnsafePointer<UInt8>) -> Variant {
      let biased = UInt(bitPattern: start) &- _StringObject.nativeBias
      return .immortal(biased)
    }

    @inlinable @inline(__always)
    internal var isImmortal: Bool {
      if case .immortal = self { return true }
      return false
    }
  }

  @usableFromInline
  internal var _count: Int

  @usableFromInline
  internal var _variant: Variant

  @usableFromInline
  internal var _discriminator: UInt8

  @usableFromInline
  internal var _flags: UInt16

  @inlinable @inline(__always)
  init(count: Int, variant: Variant, discriminator: UInt64, flags: UInt16) {
    _internalInvariant(discriminator & 0xFF00_0000_0000_0000 == discriminator,
      "only the top byte can carry the discriminator and small count")

    self._count = count
    self._variant = variant
    self._discriminator = UInt8(truncatingIfNeeded: discriminator &>> 56)
    self._flags = flags
    self._invariantCheck()
  }

  @inlinable @inline(__always)
  init(variant: Variant, discriminator: UInt64, countAndFlags: CountAndFlags) {
    self.init(
      count: countAndFlags.count,
      variant: variant,
      discriminator: discriminator,
      flags: countAndFlags.flags)
  }

  @inlinable @inline(__always)
  internal var _countAndFlagsBits: UInt64 {
    let rawBits = UInt64(truncatingIfNeeded: _flags) &<< 48
                | UInt64(truncatingIfNeeded: _count)
    return rawBits
  }
#else

  //
  // Laid out as (_countAndFlags, _object), which allows small string contents
  // to naturally start on vector-alignment.
  //

  @usableFromInline
  internal var _countAndFlagsBits: UInt64

  @usableFromInline
  internal var _object: Builtin.BridgeObject

  @inlinable @inline(__always)
  internal init(zero: ()) {
    self._countAndFlagsBits = 0
    self._object = Builtin.valueToBridgeObject(UInt64(0)._value)
  }

#endif

  @inlinable @inline(__always)
  internal var _countAndFlags: CountAndFlags {
    _internalInvariant(!isSmall)
    return CountAndFlags(rawUnchecked: _countAndFlagsBits)
  }
}

我們可以看到 _StringObject 也是一個結(jié)構(gòu)體,并且有 4 個成員變量分別是

  • _count : Int 類型
  • _variant : variant 類型
  • _discriminator : UInt8 類型
  • _flags : UInt16類型
    _StringGuts 的定義中能夠看見空字符串創(chuàng)建時調(diào)用了 _StringObjectempty:() 函數(shù),那么找到這個函數(shù)的定義
extension _StringObject {
  @inlinable @inline(__always)
  internal init(_ small: _SmallString) {
    // Small strings are encoded as _StringObjects in reverse byte order
    // on big-endian platforms. This is to match the discriminator to the
    // spare bits (the most significant nibble) in a pointer.
    let word1 = small.rawBits.0.littleEndian
    let word2 = small.rawBits.1.littleEndian
#if arch(i386) || arch(arm) || arch(arm64_32) || arch(wasm32)
    // On 32-bit, we need to unpack the small string.
    let smallStringDiscriminatorAndCount: UInt64 = 0xFF00_0000_0000_0000

    let leadingFour = Int(truncatingIfNeeded: word1)
    let nextFour = UInt(truncatingIfNeeded: word1 &>> 32)
    let smallDiscriminatorAndCount = word2 & smallStringDiscriminatorAndCount
    let trailingTwo = UInt16(truncatingIfNeeded: word2)
    self.init(
      count: leadingFour,
      variant: .immortal(nextFour),
      discriminator: smallDiscriminatorAndCount,
      flags: trailingTwo)
#else
    // On 64-bit, we copy the raw bits (to host byte order).
    self.init(rawValue: (word1, word2))
#endif
    _internalInvariant(isSmall)
  }

  @inlinable
  internal static func getSmallCount(fromRaw x: UInt64) -> Int {
    return Int(truncatingIfNeeded: (x & 0x0F00_0000_0000_0000) &>> 56)
  }

  @inlinable @inline(__always)
  internal var smallCount: Int {
    _internalInvariant(isSmall)
    return _StringObject.getSmallCount(fromRaw: discriminatedObjectRawBits)
  }

  @inlinable
  internal static func getSmallIsASCII(fromRaw x: UInt64) -> Bool {
    return x & 0x4000_0000_0000_0000 != 0
  }
  @inlinable @inline(__always)
  internal var smallIsASCII: Bool {
    _internalInvariant(isSmall)
    return _StringObject.getSmallIsASCII(fromRaw: discriminatedObjectRawBits)
  }

  @inlinable @inline(__always)
  internal init(empty:()) {
    // Canonical empty pattern: small zero-length string
#if arch(i386) || arch(arm) || arch(arm64_32) || arch(wasm32)
    self.init(
      count: 0,
      variant: .immortal(0),
      discriminator: Nibbles.emptyString,
      flags: 0)
#else
    self._countAndFlagsBits = 0
    self._object = Builtin.valueToBridgeObject(Nibbles.emptyString._value)
#endif
    _internalInvariant(self.smallCount == 0)
    _invariantCheck()
  }
}

_StringObject 的擴展中可以找到 empty:() 函數(shù)的定義,能夠看到這里區(qū)分了架構(gòu),對于我們來說只需要看第一個分支就可以了,發(fā)現(xiàn)這里調(diào)用了一個方法 count: variant: discriminator: flags:, 找到這個方法的定義

@inlinable @inline(__always)
  init(count: Int, variant: Variant, discriminator: UInt64, flags: UInt16) {
    _internalInvariant(discriminator & 0xFF00_0000_0000_0000 == discriminator,
      "only the top byte can carry the discriminator and small count")

    self._count = count
    self._variant = variant
    self._discriminator = UInt8(truncatingIfNeeded: discriminator &>> 56)
    self._flags = flags
    self._invariantCheck()
  }

可以看見這里就是在對 _StringObject 的成員變量進行賦值操作。那么這幾個成員變量分別代表著什么意思呢?

1.1 _count

_countInt 類型, 從字面意思其實也不難理解就是字符串大小的意思

1.2 _variant

_variantVariant 類型 找到 Variant 的定義

internal enum Variant {
    case immortal(UInt)
    case native(AnyObject)
    case bridged(_CocoaString)

可以看見 Variant 是一個枚舉類型,代表著字符串的三種情況,分別為 immortal、native 以及 bridged 。而通過剛才初始化方法的傳值,此時的 _variant 類型是 .immortal(0) 類型的,這個代表 Swift 原生的字符串類型。native 著代表著 AnyObject 。bridged 代表著 _CocoaString 也就是 NSString 。

1.3 _discriminator

_discriminatorUInt8 類型,在初始化方法中我們發(fā)現(xiàn)傳入了 Nibbles.emptyString 值,定位到 Nibbles 的定義

enum Nibbles {}

發(fā)現(xiàn)這是一個什么 case 都沒有的枚舉,但是在 StringObject.swift 文件的下面還有一些 Nibbles 的擴展

extension _StringObject.Nibbles {
  // The canonical empty string is an empty small string
  @inlinable @inline(__always)
  internal static var emptyString: UInt64 {
    return _StringObject.Nibbles.small(isASCII: true)
  }
}

extension _StringObject.Nibbles {
  // Mask for address bits, i.e. non-discriminator and non-extra high bits
  @inlinable @inline(__always)
  static internal var largeAddressMask: UInt64 { return 0x0FFF_FFFF_FFFF_FFFF }

  // Mask for address bits, i.e. non-discriminator and non-extra high bits
  @inlinable @inline(__always)
  static internal var discriminatorMask: UInt64 { return ~largeAddressMask }
}

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
  }

  // Discriminator for small strings
  @inlinable @inline(__always)
  internal static func small(withCount count: Int, isASCII: Bool) -> UInt64 {
    _internalInvariant(count <= _SmallString.capacity)
    return small(isASCII: isASCII) | UInt64(truncatingIfNeeded: count) &<< 56
  }

  // Discriminator for large, immortal, swift-native strings
  @inlinable @inline(__always)
  internal static func largeImmortal() -> UInt64 {
    return 0x8000_0000_0000_0000
  }

  // Discriminator for large, mortal (i.e. managed), swift-native strings
  @inlinable @inline(__always)
  internal static func largeMortal() -> UInt64 { return 0x0000_0000_0000_0000 }

  internal static func largeCocoa(providesFastUTF8: Bool) -> UInt64 {
    return providesFastUTF8 ? 0x4000_0000_0000_0000 : 0x5000_0000_0000_0000
  }
}

可以在第一個擴展中找到 emptyString 返回的是 Nibblessmall(isASCII:) 方法,true 代表是 ASCIIfalse 代表不是。在第三個擴展中我們看到 small(isASCII: Bool) 方法的實現(xiàn),當是 ASCII 碼的時候返回的是 0xE000_0000_0000_0000,當不是 ASCII 碼的時候返回的是 0xA000_0000_0000_0000

1.4 小字符串

對于空字符串通過上面的源碼能夠得到 _discriminator 的值應(yīng)該是 0xE000_0000_0000_0000 接下來驗證一下

空字符串.png

此時看到當前的字符串打印出來屬于 0xE000_0000_0000_0000,這個空字符串屬于 ASCII,這與源碼一致。
我們知道中文字符不是 ASCII 碼,那么將字符串賦值中文,那么這里是否會打印 0xA000_0000_0000_0000 呢?我們來試一下。

中文字符串.png

能夠發(fā)現(xiàn)這里打印的就是 0xA000_0000_0000_0000 這與我們猜想的一致。那 0xa 后面的 6 是什么意思? 前面的 0x0000bda5e5a882e6 又是什么?
這里為了方法觀察,將字符串賦值成英文字符。

aa字符串.png

將字符串賦值成 aa 后,能夠發(fā)現(xiàn)第一個 8 字節(jié)存儲的是 0x0000000000006161,而我們知道 aASCII 的值是 97,而 9716 進制就是 61 ,而 0xe 后面的值是 2,是不是就代表著 _count ?接著試一試 abc

abc.png

字符串是 abc 的時候,第一個 8 字節(jié)存儲的正是 abc 所對應(yīng)的 ASCII 碼的 16 進制。并且 0xe 后面這時已經(jīng)變成了 3 。我們知道字符串的大小是 16 字節(jié),那么對于小字符串 (長度小于 16 ) 它的值是否就直接存儲在這 16 字節(jié)中?

abcdefghijklmno.png

我們可以看到字符串的長度為 15 時 剛好占滿這 16 字節(jié)。

1.5 大字符串

那對于長度超過 15 的大字符串,這又是怎么存儲的呢?

abcdefghijklmnopq.png

我們可以看到當字符串的長度大于 15 的時候這里的存儲內(nèi)容就發(fā)生了變化,第二個 8 字節(jié)變成了 0x8 開頭,這是找到源碼中的 0x8000000000000000

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
  }

  // Discriminator for small strings
  @inlinable @inline(__always)
  internal static func small(withCount count: Int, isASCII: Bool) -> UInt64 {
    _internalInvariant(count <= _SmallString.capacity)
    return small(isASCII: isASCII) | UInt64(truncatingIfNeeded: count) &<< 56
  }

  // Discriminator for large, immortal, swift-native strings
  @inlinable @inline(__always)
  internal static func largeImmortal() -> UInt64 {
    return 0x8000_0000_0000_0000
  }

  // Discriminator for large, mortal (i.e. managed), swift-native strings
  @inlinable @inline(__always)
  internal static func largeMortal() -> UInt64 { return 0x0000_0000_0000_0000 }

  internal static func largeCocoa(providesFastUTF8: Bool) -> UInt64 {
    return providesFastUTF8 ? 0x4000_0000_0000_0000 : 0x5000_0000_0000_0000
  }
}

可以看到這里的 largeImmortal() 方法返回的是 0x8000_0000_0000_0000 代表著原生字符串的大字符串

extension _StringObject {
  @inlinable @inline(__always)
  internal init(immortal bufPtr: UnsafeBufferPointer<UInt8>, isASCII: Bool) {
    let countAndFlags = CountAndFlags(
      immortalCount: bufPtr.count, isASCII: isASCII)
#if arch(i386) || arch(arm) || arch(arm64_32) || arch(wasm32)
    self.init(
      variant: .immortal(start: bufPtr.baseAddress._unsafelyUnwrappedUnchecked),
      discriminator: Nibbles.largeImmortal(),
      countAndFlags: countAndFlags)
#else
    // We bias to align code paths for mortal and immortal strings
    let biasedAddress = UInt(
      bitPattern: bufPtr.baseAddress._unsafelyUnwrappedUnchecked
    ) &- _StringObject.nativeBias

    self.init(
      pointerBits: UInt64(truncatingIfNeeded: biasedAddress),
      discriminator: Nibbles.largeImmortal(),
      countAndFlags: countAndFlags)
#endif
  }
...
}

largeImmortal() 方法在初始化的時候調(diào)用,并且將 largeImmortal() 的返回值賦值給了_discriminator , 這與我們之前研究小字符串時的邏輯是一致的。
那么0xd0000000000000110x0000000100003f60 這兩個地址又是什么呢?

1.5.1 前 8 字節(jié)

0xd000000000000011 這個存儲的到底是什么呢?
在上面的初始化方法中我們看到調(diào)用的是 self.init(variant: discriminator: countAndFlags:) 這個初始化方法,這個傳了一個參數(shù)叫做 countAndFlags 并且 countAndFlags 等于 CountAndFlags( immortalCount: bufPtr.count, isASCII: isASCII) 定位到 CountAndFlags 的初始化方法 immortalCount: isASCII:

extension _StringObject.CountAndFlags {
  ...

  //
  // Specialized initializers
  //
  @inlinable @inline(__always)
  internal init(immortalCount: Int, isASCII: Bool) {
    self.init(
      count: immortalCount,
      isASCII: isASCII,
      isNFC: isASCII,
      isNativelyStored: false,
      isTailAllocated: true)
  }
  ...

在這里找到了這個方法的定義。我們注意到這個 _StringObject.CountAndFlags 擴展的上方有著蘋果官方留下的注釋

CountAndFlags.png

通過閱讀這個注釋,我們了解到

  • isASCII :用來判斷當前字符串是否是 ASCII,在高 63 位。
  • isNFC :這個默認為 1,在高 62 位。
  • isNativelyStored :是否是原生存儲,在 高 61 位。
  • isTailAllocated :是否是尾部分配的,在 高 60 位。
  • TBD :留作將來使用,在高 59 位到高 48 位。
  • count :當前字符串的大小,在高 47 位到低 0 位。
    利用蘋果電腦的計算器(程序員型) 來看一下 0xd000000000000011
    0xd000000000000011.png

    41101 與代碼和注釋一致,那么 16 進制的 0x1110 進制 就是 17 而我們的字符串是 abcdefghijklmnopq 正好 17 個 所以大字符串的前 8 位地址存儲的是 countAndFlags
1.5.2 后 8 字節(jié)

0x8000000100003f60 對與后 8 字節(jié)之前已經(jīng)知道了 0x8000_0000_0000_0000 代表著大原生字符串。那么后面的 0x100003f60 代表著什么呢?通過源碼找到了蘋果留下的另一段注釋

image.png

通過官方的注釋得知大字符串可以是原生的、共享的或者是外來的。我們這里主要探究原生的字符串。根據(jù)官方的注釋,這個原生的字符串具有尾部分配 ( tail-allocated ) 的存儲空間,它從存儲對象地址的 nativeBias 偏移量開始。這個偏移量是 32。通過前 8 字節(jié)的分析我們得到 isTailAllocated1 ,這里也就和前面我們分析的相呼應(yīng)。
接下來看一下 discriminator(鑒別器) 和 objectAddr 的地址分配方式,根據(jù)官方給的注釋,這個 discriminator 在 后 8 字節(jié)中,占據(jù)的位置是高 63 位到高 60 位。高 60 位到低 0 位存儲的就是這個額外的存儲空間的內(nèi)存地址。
這個 objectAddr 存儲的是這個額外的存儲空間的內(nèi)存地址,但是它是一個相對地址,因為它需要加上 nativeBias,得到的才是這個額外的存儲空間的地址值。
那也就是意味著當字符串是大字符串的時候,會分配額外的存儲空間,用這個額外的存儲空間存儲字符串的值。
那么對于 0x100003f60 這個地址來說就是 objectAddr , 這個地址再偏移 32 位得到的地址就是存儲字符串的值的地址。
0x100003f60 + 32 = 0x100003f60 + 0x20 = 0x100003f80

0x100003f80.png

1.6 String的內(nèi)存結(jié)構(gòu)總結(jié)
  • 一個 String 變量/常量的大小為 16 個字節(jié)。
  • 當字符串的大小小于等于 15 的時候為小字符串,當字符串的大小大于 15 的時候為大字符串。
  • 小字符串時,前 15 個字節(jié)用來存儲字符串的值,最后一個字節(jié)記錄當前字符串是否是 ASCII 和字符串的大小 count。
  • 大字符串時,前 8 個字節(jié)用來記錄字符串的大小和其它的一些信息 countAndFlags,比如是否是 ASCII。后 8 個字節(jié)中,高 63 位到高 60 位存儲的是鑒別器( discriminator )的值,剩余的用來存儲相對偏移地址( objectAddr ),這個地址需要再偏移 32 位才是存儲字符串的值的地址。

二. String.index

Swift 中對于 String 我們想要訪問到某一個字符可以通過 Index 去獲取

let string = "大家好"
// 從開始位置向后偏移1,返回結(jié)果是String.Index類型
let index = string.index(string.startIndex, offsetBy: 1);
print(string[index]);

這里能夠通過 string[index] 這樣的方式去獲取一個字符,那么為什么不能像數(shù)組那樣直接傳入一個數(shù)字而且必須要傳入一個 Swift.Index 類型呢?

string[1].png

2.1 Swift 中 String 的本質(zhì)

Swift 中的 String 代表的是一系列的 characters(字符),字符的表示方式有很多種,比如我們最熟悉的 ASCII 碼,ASCII 碼一共規(guī)定了 128 個字符的編碼,對于英文字符來說 128 個字符已經(jīng)夠用了,但是相對于其他語言來說,這是遠遠不夠用的,比如中國漢字。這也就意味著不同國家不同語言都需要有自己的編碼格式,這個時候同一個二進制文件就有可能被翻譯成不同的字符。

有一種編碼能夠把所有的符號都納入其中的方式,就是我們熟悉的 Unicode。但是 Unicode 只是規(guī)定了符號對應(yīng)的二進制代碼,并沒有詳細明確這個二進制代碼應(yīng)該如何存儲。

比如 "大家好hello" 對應(yīng)的 Unicode 編碼以及轉(zhuǎn)成二進制的結(jié)果如下

大  5927  0101 1001 0010 0111
家  5bb6  0101 1011 1011 0110
好  597d  0101 1001 0111 1101
h  0068  0000 0000 0110 1000
e  0065  0000 0000 0110 0101
l  006c  0000 0000 0110 1100
l  006c  0000 0000 0110 1100
o  006f  0000 0000 0110 1111

對于英文字符如果統(tǒng)一采用中文字符這種方式去存儲,也就是用和中文字符一樣的步長去存儲英文字符,必然會有很大的浪費(前 8 位必為 0 )。

為了解決這個問題,就可以用 UTF-8,UTF-8 最大的一個特點,就是它是一種變長的編碼方式。它可以使用 1~4 個字節(jié)表示一個符號,根據(jù)不同的符號而變化字節(jié)長度。這里簡單說一下 UTF-8 的規(guī)則:

    1. 單字節(jié)的字符,字節(jié)的第一位設(shè)為 0,對于英語文本,UTF-8 碼只占用一個字節(jié),和 ASCII 碼完全相同;
    1. n 個字節(jié)的字符( n>1 ),第一個字節(jié)的前 n 位設(shè)為 1 ,第 n+1 位設(shè)為 0,后面字節(jié)的前兩位都設(shè)為 10 ,這 n 個字節(jié)的其余空位填充該字符 Unicode 碼,高位用 0 補足。
大  11100101 10100100 10100111
家  11100101 10101110 10110110
好  11100101 10100101 10111101
h  0110 1000
e  0110 0101
l  0110 1100
l  0110 1100
o  0110 1111

對于 Swift 來說, String 是一系列字符的集合,也就意味著 String 中的每一個元素是不等?的。就是說在進行內(nèi)存移動的時候步?是不一樣的。這里和 Array 數(shù)組不一樣,當我們遍歷數(shù)組中的元素的時候,因為每個元素的內(nèi)存大小是一致的,所以每次的偏移量就是數(shù)組元素的內(nèi)存大小( Int 類型就偏移 8 字節(jié))。
但是對于 String 來說如果我要訪問 string[1] 那么是不是要把 "大" 這個字段遍歷完成之后才能夠確定 "家" 的偏移量? 依次內(nèi)推每一次都要重新遍歷計算偏移量,這個時候無疑增加了很多的內(nèi)存消耗。這就是為什么不能通過 Int 作為下標來去訪問 String。

2.2 Swift.Index 的本質(zhì)

來到源碼中關(guān)于 StringIndex 布局的描述

Index的描述.png

從注釋中我們大致明白了上述表示的意思:

  • position aka encodedOffset :一個 48 bit 值,用來記錄碼位偏移量。
  • transcoded offset : 一個 2 bit 的值,用來記錄字符使用的碼位數(shù)量。
  • grapheme cache : 一個 6 bit 的值,用來記錄下一個字符的邊界。
  • reserved : 7 bit 的預(yù)留字段
  • scalar aligned : 一個 1 bit 的值,用來記錄標量是否已經(jīng)對齊過。

所以對于 StringIndex 的本質(zhì)是存儲了 encodedOffsettranscoded offset。當我們構(gòu)建 StringIndex 的時候,其實是把 encodedOffsettranscoded offset 計算出來存放到 Index 的內(nèi)存信息里面。而這個 Index 本身就是一個 64 位的位域信息。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容