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
的个数。
这样子逐一遍历下去,不能在常数时间内完成也就可想而知了。
下起雨,也要勇敢前行