Swift 里字符串(七)stringIndex

String 里,用来索引 Character 的,不是整数,而是StringIndex

内部结构

extension String {
  /// A position of a character or code unit in a string.
  @_fixed_layout
  public struct Index {
    @usableFromInline
    internal var _rawBits: UInt64

    @inlinable @inline(__always)
    init(_ raw: UInt64) {
      self._rawBits = raw
      self._invariantCheck()
    }
  }
}

其实就是一个UInt64的值,不过不同的bit有不同的含义。

String's Index has the following layout:

 ┌──────────┬───────────────────┬────────────────┬──────────┐
 │ b63:b16  │      b15:b14      │     b13:b8     │ b7:b0    │
 ├──────────┼───────────────────┼────────────────┼──────────┤
 │ position │ transcoded offset │ grapheme cache │ reserved │
 └──────────┴───────────────────┴────────────────┴──────────┘

- grapheme cache: A 6-bit value remembering the distance to the next grapheme
boundary
- position aka `encodedOffset`: An offset into the string's code units
- transcoded offset: a sub-scalar offset, derived from transcoding

  • position,即当前字符的相对于起始位置的偏移,以 code unit 计算。在String里,默认是utf8编码,所以指代距离起始位置的字节数。
  • transcoded offset 和编码相关
  • grapheme cache 标记当前字符距离下一个字符的距离。

起始和结束位置的定义

// Index
extension _StringGuts {
  @usableFromInline
  internal typealias Index = String.Index

  @inlinable
  internal var startIndex: String.Index {
    @inline(__always) get { return Index(encodedOffset: 0) }
  }
  @inlinable
  internal var endIndex: String.Index {
    @inline(__always) get { return Index(encodedOffset: self.count) } //count 不是String 的count
  }
}

即指向了内存地址的起始和结束。

计算字符串的长度

String 的长度,根据文档说,不可以在O(1)时间内获得,因为要遍历整个字符串。string遵守BidirectionalCollection,而不是RandomAccessCollection

  /// The number of characters in a string.
  public var count: Int {
    @inline(__always) get {
      return distance(from: startIndex, to: endIndex)
    }
  }

从定义中可以看到,为了计算长度,需要计算两个Index 之间的距离。
最终是需要从startIndex 遍历到 endIndex的。

  @inlinable // protocol-only
  internal func _distance(from start: Index, to end: Index) -> Int {
    var start = start
    var count = 0

    if start < end {
      while start != end {
        count += 1
        formIndex(after: &start)
      }
    }
    else if start > end {
      while start != end {
        count -= 1
        formIndex(before: &start)
      }
    }

    return count
  }

每一次formIndex都会调用到下面的代码:

  public func index(after i: Index) -> Index {
    _precondition(i < endIndex, "String index is out of bounds")

    // TODO: known-ASCII fast path, single-scalar-grapheme fast path, etc.
    let stride = _characterStride(startingAt: i)
    let nextOffset = i.encodedOffset &+ stride
    let nextStride = _characterStride(
      startingAt: Index(encodedOffset: nextOffset))

    return Index(
      encodedOffset: nextOffset, characterStride: nextStride)
  }

可以看到,需要确定当前字符占用code unit 的个数,以及下一个字符占用code unit的个数。
这样子逐一遍历下去,不能在常数时间内完成也就可想而知了。

posted on 2019-03-20 08:02  花老🐯  阅读(657)  评论(0编辑  收藏  举报

导航