说说Swift中的String

420 阅读4分钟

概述

Swift中的 String是一个 Collection,可以拥有和 Array 等 Collection 一样的操作方式,比如:

let text = "Hello world"

for char in text {
        print(char)
}

text.prefix(5) // "Hello"
text.suffix(5) // "world"

text.dropFirst() // "H"
text.dropLast() // "d"

上面所有针对 text 的方法都是在 Array(或其他 Collection)上的有。但是这里有一点要注册,这些方法返回的,可不是 String,而是 Substring。

Substring 是什么?

String 和 Substring 都遵守了 StringProtocol 这个东西,很多方法也都是一样的,那么为何要有这个设计呢?主要一个原因是:性能。

看了这张图,你就知道什么是 Substring 了:

image.png 当我们用一些 Collection 的方式得到 String 的 Slice(一部分)的时候,这时候创建的都是 Substring,Substring 与 String 共享一个 Storage,这意味我们在操作 String 的一部分的时候,是不需要频繁的去创建内存的,这使得 Swift 的 String 相关的操作可以获取比较高的性能。

只有当你显式地将 Substring 转成 String 的时候,才会 Copy 一份 String 到新的内存空间来,这时新的 String 和之前的 String 就没有关系了。

那么作为开发者,我们是不是就可以高枕无忧,只需要用,不需要操心什么是 Substring 什么是 String 呢?当然不是。

使用 Substring 的注意点

由于 Substring 是共享 String 的 Storage 的,这意味着,假如在你从一个非常大的 String 里得到一个 Substring,即使在之后你只用 Substring 了,但内存依然是占用着整个 String 的大小。直到 Substring 被释放以后,整个 String 才会被释放。

理解了这个原理后,我们就可以在具体的场景去优化性能了。只要记住两个原则:1、创建 Substring 的性能快,因为共享 String 的 Storage;2、如果有 Substring 存在,则 String 的 Storage 不会释放。

可以说,Swift的这个设计,还是提供了不少灵活性的。

常规的使用场景

可能大家看到这,会觉得 Swift 引入了复杂度,用 String 就用 String 好了嘛,非要搞一个 Substring 出来。但事实上,在大多数项目里,我们不会遇到要用 String 还是用 Substring 这个「选择」问题。

因为 Swift 是强类型的语言,这意味着如果你的函数参数写着「name: String」,这是不接受 Substring 的,你一定要显式的转成 String,而转换成 String 的过程就意味着进行 Copy,也就规避了共享 Storage 的问题,不会存在内存占用的问题。

只有当你特别需要去优化时,才需要好好去设计和思索一下要用 Substring 或是 String 来做操作。

一个实用的 Extension

把 Substring 显式地转成 String。

extension String {
    public func substring(from index: Int) -> String {
        if self.characters.count > index {
            let startIndex = self.index(self.startIndex, offsetBy: index)
            let subString = self[startIndex..<self.endIndex]

            return String(subString)
        } else {
            return self
        }
    }
}

比如我是这样用的:

let text = "@图拉鼎" 
let name = text.substring(from: 1)
print(name) // "图拉鼎"

简单地举这个例子,就是想说平常使用中,也可以完全无视 Substring 这个东西,只需要把常用的操作写成 extension 即可。

String的增、删、替换

String的Index

swift中的字符串不能直接通过下标[]整数的形式访问

var line = "I want magic"
line[0]// 编译报错

indices 属性会创建一个包含全部索引的数组

for i in line.indices{
    print(line[i])// OK的
}

除此之外,index(_:offsetBy:)获取指定位置的索引

var version = "10.11.12"
//对于一个确定的字符串,能直接获取到它的startIndex,endIndex
//endIndex 是最后一个字符之后的索引
var prefixIndex = version.startIndex
var suffixIndex = version.endIndex

//获取 第二个索引
var secondIndex = version.index(prefixIndex, offsetBy: 2)
//获取倒数第二个索引
var reversalSecondIndex = version.index(suffixIndex, offsetBy: -2)
var index = line.firstIndex(of: "0") ?? line.endIndex

有了String.Index字符串截取,通过下标[]运算访问

//substring(to:)已弃用
//从首位到索引位置的字符串
print(version.substring(to: secondIndex))//10
print(version.substring(to: reversalSecondIndex))//10.11.

//倒数到索引位置的字符串
//substring(from:)已弃用
print(version.substring(from: secondIndex))//.11.12
print(version.substring(from: reversalSecondIndex))// 12

// range 注意区间右边越界 suffixIndex 是最后一个字符之后的索引
print(version[secondIndex]) // .
print(version[prefixIndex..<suffixIndex]) //"10.11.12"
print(version[prefixIndex...suffixIndex]) //越界 异常
print(version[secondIndex...reversalSecondIndex])//.11.1
print(version[index]) // 0
print(version[...index])//10
print(version[index...])//0.11.12

替换

var time = "2021:1:1"
var newTime = time.replacingOccurrences(of: ":", with: "-")
print(newTime)//2021-1-1

//替换一段字符
var strat = time.index(time.startIndex, offsetBy: 4)
var end = time.index(time.startIndex, offsetBy: 5)
var newTime2 = time.replacingCharacters(in: strat..<end, with: "AAAA")
print(newTime2)//2021AAAA1:1

删除

/ 删除 单个字符
time.remove(at: time.index(time.startIndex, offsetBy: 4))
print(time)//20211:1
//删除一段字符
time.removeSubrange(time.range(of: "2021")!) //:1:1
time.removeSubrange(time.startIndex..<time.endIndex)// 空字符串
//等效写法
time.removeAll()

插入

//在指定索引 前面 插入字符
time.insert("A", at: strat)//A2021:1:1

//在指定索引 前面 插入多个字符
time.insert(contentsOf: "BBBB", at: strat)//BBBB2021:1:1

过滤trimmingCharacters(in:)会对字符串的开头和结尾过滤处理

var time = "  2021:1:1     "
//过滤前后空格
var nowhitespaces = time.trimmingCharacters(in: .whitespaces)
// 20BBBB21:1:1

//filter
time.filter { $0.isNumber }
// 202111

由于字符串遵守Sequence协议,所以Sequence系列方法也都适用

参考

1,你需要了解的 Swift 4 新东西之 Substring
2,Swift 字符串String的常见用法