Swift 中 Substrings 与 String

原做者 Greg Heo (@gregheo) | Twitter ,原文连接:Swift Substringsgit

为文本字符串添加特性或者语法糖在各类编程语言中都很广泛。就拿你们都很熟悉的 C 语言举例,C 字符串本质是一个字符数组(characters array),可是每次输入字符串的时候不用输入 ['h','e','l','l','o'] ,直接打 hello 就能够了,由于这个操做编译器帮你作了。 更高级的语言好比 Swift 处理字符串就不只仅是当作字符数组了,String 是一个完整的类型,而且有各类特性。咱们先来看一下 String 的一个特性:substrings。程序员

简单的看一下 Strings

首先粗略的了解一下字符串的实现。下面的代码来自标准库中 String.swiftgithub

public struct String {
  public var _core: _StringCore
}
复制代码

固然也有一些其余初始化设置,不过在声明里只有这一个存储属性!秘密必定都在 StringCore.swift 里:编程

public struct _StringCore {
  public var _baseAddress: UnsafeMutableRawPointer?
  var _countAndFlags: UInt
  public var _owner: AnyObject?
}
复制代码

在这个类型里还有不少其余东西,不过咱们仍是只关注存储属性:swift

  • Base address — 一个指向内部存储的指针
  • Count — 字符串长度,UInt 类型,在一个 64 位的系统中,意味着有 62(64 - 2) 位的空间能够表示长度。这是一个很是大的数字。因此字符串的长度不太可能溢出。
  • Flags — 两个 bits 用来作标志。第一位表示是否被 _StringBuffer 持有;第二位表示编码格式是 ASCII 仍是 UTF-16。 _StringCore 的真实状况比这里提到的要复杂的多,可是经过上面的内容可让咱们更容易理解字符串的一些信息:字符串有一些内部存储和存储的大小(underlying storage and size)。

Substrings

Swift 中要怎么建立一个 substring?最简单的方式就是经过下标从 string 取一段:数组

let str = "Hello Swift!"
let slice = str[str.startIndex..<str.index(str.startIndex, offsetBy: 5)]
// "Hello"
复制代码

虽然很简单,可是代码看起来不太优雅😄。 String 的索引不是直观的整型,因此截取时的位置索引须要利用 startIndex 和 index(_:offsetBy:)获取。若是是从字符串开始位置截取,能够省略掉 startIndex :app

let withPartialRange = str[..<str.index(str.startIndex, offsetBy: 5)]
// still "Hello"
复制代码

或者用 collection 中的这个方法:编程语言

let slice = str.prefix(5)
// still "Hello"
复制代码

要记住字符串也是 collection ,因此你能够用集合下的方法,好比 prefix(),suffix(), dropFirst() 等。ide

Substring 的内部原理

substring 一个神奇的地方是他们重用了父 string 的内存。你能够把 substring 理解为父 string 的其中一段。 函数

举个例子,若是从一个 8000 个字符的字符串中截取 100 个字符,并不须要从新初始化 100 个字符的内存空间。 这也意味着你可能不当心就把父 string 的生命周期延长了。若是有一大段字符串,而后你只是截取了一小段,只要截取的小段字符串没有释放,大段的字符串也不会被释放。 Substring 内部究竟是怎么作到的呢?

public struct Substring {
  internal var _slice: RangeReplaceableBidirectionalSlice<String>
复制代码

内部的 _slice 属性保存着全部关于父字符串的信息:

// Still inside Substring
internal var _wholeString: String {
  return _slice._base
}
public var startIndex: Index { return _slice.startIndex }
public var endIndex: Index { return _slice.endIndex }
复制代码

计算属性 _wholeString(返回整个父字符串),startIndex 和 endIndex 都是经过内部的 _slice 返回。 也能够看出 slice 是如何引用父字符串的。

Substring 转换为 String

最后代码里可能有不少 substring,可是函数的参数类型须要的是 string。Substring 转换到 string 的过程也很简单:

let string = String(substring)
复制代码

由于 substrings 和它的父字符串共享同一个内存空间,猜想建立一个新字符串应该会初始化一片新的存储空间。那么 string 的初始化到底过程是怎样的呢。

extension String {
  public init(_ substring: Substring) {
    // 1
    let x = substring._wholeString
    // 2
    let start = substring.startIndex
    let end = substring.endIndex
    // 3
    let u16 = x._core[start.encodedOffset..<end.encodedOffset]
    // 4A
    if start.samePosition(in: x.unicodeScalars) != nil
    && end.samePosition(in: x.unicodeScalars) != nil {
      self = String(_StringCore(u16))
    }
    // 4B
    else {
      self = String(decoding: u16, as: UTF16.self)
    }
  }
}
复制代码
  1. 建立一个对原有父字符串的引用
  2. 获取 substring 在父字符串中的开始和结束位置
  3. 获取 UTF-16 格式的 substring 内容。_core 是 _StringCore 的一个实例。
  4. 判断匹配的 unicode 编码,生成一个新的字符串实例 把 substring 转换成 string 的步骤很是简单,可是你可能要考虑是否是一须要这样作。是否是进行 substring 操做的时候都要求类型是 string?若是对 substring 的操做都须要转成 string,那么轻量级的 substring 也就失去了意义。🤔

StringProtocol

StringProtocol 上场!StringProtocol 真是面向协议编程的一个优秀表明。StringProtocol 抽象了字符串的场景功能,好比 uppercased(), lowercased(),还有 comparablecollection 等。String 和 Substring 都声明了 StringProtocol。 也就是说你能够直接使用 == 对 substring 和 string 进行判等,不须要类型转换:

let helloSwift = "Hello Swift"
let swift = helloSwift[helloSwift.index(helloSwift.startIndex, offsetBy: 6)...]

// comparing a substring to a string 😱
swift == "Swift"  // true
复制代码

也能够遍历 substring,或者从 substring 截取子字符串。 在标准库里也有一小部分函数使用 StringProtocol 类型做为参数。好比把一个字符串转换为整型就是:init(text: StringProtocol)。 虽然你可能不关心是 string 和 substring,可是使用 StringProtocol 做为参数类型,调用者就不用进行类型转换,对他们会友好不少。

总结

  • 字符串仍是那个常见的字符串。
  • Substring 是字符串的一部分,和父字符串共享同一块内存空间,而且记录了本身的开始和结束位置。
  • String 和 Substring 都声明实现了 StringProtocol。StringProtocol 包含了一个字符串的基本属性和功能。

是否是以为本身也能够自定义字符串类型,实现 StringProtocol ?

/// Do not declare new conformances to `StringProtocol`. Only the `String` and
/// `Substring` types in the standard library are valid conforming types.
public protocol StringProtocol 复制代码

可是苹果爸爸表示了拒绝。


相关文章
相关标签/搜索