Swift 中 String 取下标及性能问题

取下标

String

String 用 String.Index 取下标(subscript)得到 Character,String.Index 要从 String 中获取

let greeting = "Guten Tag!"
greeting[greeting.startIndex] // Character "G"
greeting[greeting.index(before: greeting.endIndex)] // Character "!"
greeting[greeting.index(after: greeting.startIndex)] // Character "u"
let index = greeting.index(greeting.startIndex, offsetBy: 7)
greeting[index] // Character "a"

String 用 Range<String.Index> 或 ClosedRange<String.Index> (以下 Range 和 ClosedRange 统称为 Range) 取下标得到 String

let str = "abc"
str[str.startIndex..<str.index(after: str.startIndex)] // String "a"
str[str.startIndex...str.index(after: str.startIndex)] // String "ab"

Character

String 通过 characters 属性获得 String.CharacterView,表示屏幕上显示的内容。String.CharacterView 通过 String.CharacterView.Index 取下标得到 Character,String.CharacterView.Index 要从 String.CharacterView 中获取

let str = "abc"
let characters = str.characters // String.CharacterView
characters[characters.startIndex] // Character "a"

注意,String.CharacterView 不遵循 RandomAccessCollection 协议,用 String.CharacterView.Index 取下标不可以随机访问。另外,String.CharacterView.Index 与 String.Index 是相同的类型,属于 Struct。String.Index 的文档在 String 文档下

typealias Index = String.CharacterView.Index

String.CharacterView 通过 Range<String.CharacterView.Index> 得到 String.CharacterView。用 Character 和 String.CharacterView 都可以生成 String

let str = "abc"
let characters = str.characters // String.CharacterView
let characters2 = characters[characters.startIndex..<characters.index(after: characters.startIndex)] // String.CharacterView
String(characters.first!) == String(characters2) // true. characters.first! is Character

用 String.CharacterView 生成 Array<Character>,可以用 Int、Range<Int> 取下标。用 Array<Character> 也可以生成 String

let str = "abc"
let arr = Array(str.characters) // Array<Character> ["a", "b", "c"]
arr[1] // Character "b"
arr[1...2] // ArraySlice<Character> ["b", "c"]
String(arr) // String "abc"

Character 可以直接与 "a" 比较

let str = "abc"
let a = str[str.startIndex] // Character "a"
let b = str[str.index(str.startIndex, offsetBy: 1)] // Character "b"
a == "a" // true
b > "a" // true

UTF-8

String 通过 utf8 属性获得 String.UTF8View,表示 UTF-8 编码的内容。String.UTF8View 通过 String.UTF8View.Index 取下标得到 UTF8.CodeUnit,实际上是 UInt8;通过 Range<String.UTF8View.Index> 取下标得到 String.UTF8View。String.UTF8View.Index 要从 String.UTF8View 中获取。String.UTF8View 不遵循 RandomAccessCollection 协议,用 String.UTF8View.Index 取下标不可以随机访问。用 String.UTF8View 生成 Array<UInt8>,可以用 Int、Range<Int> 取下标。用 String.UTF8View 可以生成 String。用 UInt8 或 Array<UInt8> 也可以生成 String,但内容表示数字或数字数组,不是数字的 UTF-8 编码内容。

let str = "abc"
let utf8 = str.utf8 // String.UTF8View
let n = utf8[utf8.startIndex] // UInt8 97
let a = utf8[utf8.startIndex..<utf8.index(after: utf8.startIndex)] // String.UTF8View "a"
let ab = utf8[utf8.startIndex...utf8.index(after: utf8.startIndex)] // String.UTF8View "ab"
String(n) // "97", NOT "a"
String(a) // "a"
String(ab) // "ab" let arr = Array(utf8) // Array<UInt8> [97, 98, 99]
let n2 = arr[0] // UInt8 97
let arr2 = arr[0...1] // // ArraySlice<UInt8> [97, 98]

String 通过 utf8CString 属性获得 ContiguousArray<CChar>,实际上是 ContiguousArray<Int8>,表示 UTF-8 编码的内容并且末尾增加一个 0,所以长度比 utf8 属性的长度大 1。ContiguousArray<Int8> 可以用 Int、Range<Int> 取下标,分别得到 Int8 和 ArraySlice<Int8>。ContiguousArray 遵循 RandomAccessCollection 协议,用 Int 取下标可以随机访问。

let str = "abc"
let utf8 = str.utf8CString // ContiguousArray<Int8> [97, 98, 99, 0]
let a = utf8[0] // Int8 97
let ab = utf8[0...1] // ArraySlice<Int8> [97, 98]

UTF-16

String 通过 utf16 属性获得 String.UTF16View,表示 UTF-16 编码的内容。String.UTF16View 通过 String.UTF16View.Index 取下标得到 UTF16.CodeUnit,实际上是 UInt16;通过 Range<String.UTF16View.Index> 取下标得到 String.UTF16View。String.UTF16View.Index 要从 String.UTF16View 中获取。String.UTF16View 遵循 RandomAccessCollection 协议,用 String.UTF16View.Index 取下标可以随机访问。用 String.UTF16View 生成 Array<UInt16>,可以用 Int、Range<Int> 取下标。用 String.UTF16View 可以生成 String。用 UInt16 或 Array<UInt16> 也可以生成 String,但内容表示数字或数字数组,不是数字的 UTF-16 编码内容。

let str = "abc"
let utf16 = str.utf16 // String.UTF16View
let n = utf16[utf16.startIndex] // UInt16 97
let a = utf16[utf16.startIndex..<utf16.index(after: utf16.startIndex)] // String.UTF16View "a"
let ab = utf16[utf16.startIndex...utf16.index(after: utf16.startIndex)] // String.UTF16View "ab"
String(n) // "97", NOT "a"
String(a) // "a"
String(ab) // "ab" let arr = Array(utf16) // Array<UInt16> [97, 98, 99]
let n2 = arr[0] // UInt16 97
let arr2 = arr[0...1] // // ArraySlice<UInt8> [97, 98]

性能对比

对 String、String.CharacterView、Array<Character>、String.UTF8View、Array<UInt8>、ContiguousArray<Int8>、String.UTF16View、Array<UInt16> 进行判空(isEmpty)、获取长度(count)、一个位置的取下标([index])、一段距离的取下标([range])测试,统计执行时间。

定义测试类型、打印和更新时间的方法、要测试的 String

import Foundation

enum TestType {
case isEmpty
case count
case index
case range
} func printAndUpdateTime(_ date: inout Date) {
let now = Date()
print(now.timeIntervalSince(date))
date = now
} let s = "aasdfsdfsdfgfdsg vrutj7edbj7 ergcwhmkl5lknjklqawkrcqjljkljqjlqjhbrlqwfcbhafcpiluioufnlkqjvjakjn fnvjalgkhlkdkjlkasdfsdfsdfgfdsg vrutj7edbj7 ergcwhmkl5lknjklqawkrcqjliopjktyuljqjlqjhbrlqwfcbhafciluioufnlkjvjakjn fnvjalgkhlkdkjlkasdfsdfsdfgfdsg vrutj7edbj7 ergcwhmkl5lknjklqawkrcqjljkljqjlqjhbrlqwfcbhafciluioufnlkjvjakjn fnvjalgkhlkdkjlkasderwytwghfsdfsdfgfdsg vrutj7edbj7 fdgotuyoergcwhmkl5lknjklqawkyrcqjljkljqjlqjhbrlqwfcbhafci luioufnlkjvjakjn fnvjalgkhlkdkjlkasdfsdfsdfgfdsg vrutj7edbj7 ergcvcnvbwhmkl5lknjklqawkrcqjljkljqjlqjhbrlqwfcbhafciluioufnlkjvjakjn fnvjalgkhlkdkjlkasdfsdfsdfgfdsg vrutj7edbj7 ergcwhmkl5lknjklqawkrcqjljkljqjlqjhbrlqwfcbhafciluioufnlkjvjakjkn fnvjalgkhlkdkjlkasdfsdfsdfgfdsg vrutj7edbj7 ergcwhmkl5lknjklqawkrcqjljkljqjlqjhbrlqwfcbhafciluioufnlkjvjakjn fnvjalgkhlkdkjlkasdfsdfsdfgfdsg iopiouvrutj7edbj7 ergcwhmkl5lknjklqawkrcqjljkfghngdljqjlqjhbrlqwfcbhafciluioufnlkjvjakjn fnvjalgkhlkdkjlkasdfsdfsdfgfdsg vrutj7edbj7 ergcwhmbkl5lknjklqawkrcqjljkljqjlqjhbrlqwfcbhafciluioufnlkjvjakjn fnvjalgkhlkdkjlkasdfsdfsdfgfdsg vrutj7edbj7 ergcwhmkl5lknjklqawkrcqjljkljqjlqjhbrlqwfcbhafciluioufnlkjvjakjn fnvjalgkhlkdkjlkasdfsdfsdfgfdsg vrutj7edbj7 ergcwhmkl5lknjklqawkrcqjljkljqjlqjhbrlqwfcbhafciluioufnlkjvjakjn fnvjalgkhlkdkjlkasdfsdfsdfgfdsg vrutj7edbj7 ergcwhmkl5lknjklqawkrcqjljkljqjlqjhbrlqwfcbhafciluioufnlkjvjakjn fnvjalgkhlkdkjlkasdfsdfsdfgfdsg vrutj7edbj7 ergcwhmkl5lknjklqasdfsdwkrcqjljkljqjlqjhbrlqwfcbhafciluioufnlkjvjakjn fnvjalgkhlkdkjlkasdfsdfsdfgfdsg vrutj7edbj7 ergcwhmkl5lknjklqawkrcqjljkljqjlqjhbrlqwfcbhafciluioufnlkjvjakjn fnvjalgkhlkdkjlkasdfsdfsdfgfdsg vrutj7edbj7 ergcwhmkl5lknjklqawkrcqjljkljdqjlqjhbrlqwfcbhafciluioufnlkjvjakjn fnvjalgkhlkdkjlkasddfsdfsdfgfdsg vrutj7edbj7 ergcwhmkl5lknjklqawkrcqjljkljqjlqjhbrlqwfcbhafciluioufnlkjvjakjn fnvjalgkhlkdkjlkasdfsdfsdfgfdsg vrutj7edbj7 ergcwhmkl5lknjklqawkrcqjljkljqjlqjhbsdfdsrlqwfcbhafciluioufnlkjvjakjn fnvjalgkhlkdkjlkasdfsdfsdfgfdsg vrutj7edbj7 ergcwhmkl5lknjklqawkrcqjljkljqjlqjhbrlqwfcbhafciluioufnlkjvjakjn fnvjalgkhlkdkjlkasdfsdfsdfgfdsg vrutj7edbj7 ergcwhmkl5lknjklqawkrcqjljkljqjlqjhbrlqwfcbhafciluioufnlkjvjakjn fnvjalgkhlkdkjlkasdfsdfsdfgfdsg vrutj7edbj7 ergcwhmkl5lknjklqawkrcqjljkljqjlqjhbrlqwfcbhafciluioufnlkjvjakjn fnvjalgkhlkdkjlkasdfsdfsdfsadfsdgfdsg vrutj7edbj7 ergcwhmkl5lknjklqawkrcqjljkljqsdfasjlqjhbrlqwfcbhafciluioufnlkjvjakjn fnvjalgkhlkdkjlkasdfsdfsdfgfdsg vrutj7edbj7 ergcwhmkl5lknjklqawkrcqjljkljqjlqjhbrlqwfcbhafciluioufnlkjvjakjn fnvjalgkhlkdkjlkasdfsdfsdfgfdsg vrutj7edbj7 ergcwhmkl5lknjklqawkrcqjljkljqjlqjhbrlqwfcbhafciluioufnlkjvjakjn fnvjalgkhlkdkjlkasdfsdfsdafgfdsg vrutj7edbj7 ergcwhmkl5lknjklqawkrcqjljkljqjlqjhbrlqwfcbhafciluioufnlkjvjakjn fnvjalgkhlkdkjlkasdfsdfsdfgfdsg vrutj7edbj7 ergcwhmkl5lknjklqawkrcqjljkljqjlqjhbrlqwfcbhafciluioufnlkjvjakjn fnvjalgkhlkdkjlkasdfsdfsdfgfdsg vrutj7edbj7 ergcwhmkl5lknjklqawkrcqjljkljqjlqjhbrlqwfcbhafciluioufnlkjvjakjn fnvjalgkhlkdkjlkasdfsdfsdfgfdsg vrutj7edbj7 ergcwhmkl5lknjklqawkrcqjljkljqjlqjhbrlqwfcbhafciluioufnlkjvjakjn fnvjalgkhlkdkjlk"

测试代码

let loopCount = 10000
let index = s.characters.count / 2
let testType: TestType = .range
print(testType)
var date = Date() forLoop: for _ in 0..<loopCount {
switch testType {
case .isEmpty:
_ = s.isEmpty
case .count:
break forLoop
case .index:
_ = s[s.index(s.startIndex, offsetBy: index)]
case .range:
let endIndex = s.index(s.startIndex, offsetBy: index)
_ = s[s.startIndex..<endIndex]
}
} if testType == .count {
date = Date()
} else {
print("String")
printAndUpdateTime(&date)
} let characters = s.characters
for _ in 0..<loopCount {
switch testType {
case .isEmpty:
_ = characters.isEmpty
case .count:
_ = characters.count
case .index:
_ = characters[characters.index(characters.startIndex, offsetBy: index)]
case .range:
let endIndex = characters.index(characters.startIndex, offsetBy: index)
_ = characters[characters.startIndex..<endIndex]
}
} print("Characters")
printAndUpdateTime(&date) let characterArr = Array(characters)
for _ in 0..<loopCount {
switch testType {
case .isEmpty:
_ = characterArr.isEmpty
case .count:
_ = characterArr.count
case .index:
_ = characterArr[index]
case .range:
_ = characterArr[0..<index]
}
} print("Characters array")
printAndUpdateTime(&date) let utf8 = s.utf8
for _ in 0..<loopCount {
switch testType {
case .isEmpty:
_ = utf8.isEmpty
case .count:
_ = utf8.count
case .index:
_ = utf8[utf8.index(utf8.startIndex, offsetBy: index)]
case .range:
let endIndex = utf8.index(utf8.startIndex, offsetBy: index)
_ = utf8[utf8.startIndex..<endIndex]
}
} print("UTF-8")
printAndUpdateTime(&date) let utf8Arr = Array(utf8)
for _ in 0..<loopCount {
switch testType {
case .isEmpty:
_ = utf8Arr.isEmpty
case .count:
_ = utf8Arr.count
case .index:
_ = utf8Arr[index]
case .range:
_ = utf8Arr[0..<index]
}
} print("UTF-8 array")
printAndUpdateTime(&date) let utf8CString = s.utf8CString
for _ in 0..<loopCount {
switch testType {
case .isEmpty:
_ = utf8CString.isEmpty
case .count:
_ = utf8CString.count
case .index:
_ = utf8CString[index]
case .range:
_ = utf8CString[0..<index]
}
} print("UTF-8 C string")
printAndUpdateTime(&date) let utf16 = s.utf16
for _ in 0..<loopCount {
switch testType {
case .isEmpty:
_ = utf16.isEmpty
case .count:
_ = utf16.count
case .index:
_ = utf16[utf16.index(utf16.startIndex, offsetBy: index)]
case .range:
let endIndex = utf16.index(utf16.startIndex, offsetBy: index)
_ = utf16[utf16.startIndex..<endIndex]
}
} print("UTF-16")
printAndUpdateTime(&date) let utf16Arr = Array(utf16)
for _ in 0..<loopCount {
switch testType {
case .isEmpty:
_ = utf16Arr.isEmpty
case .count:
_ = utf16Arr.count
case .index:
_ = utf16Arr[index]
case .range:
_ = utf16Arr[0..<index]
}
} print("UTF-16 array")
printAndUpdateTime(&date)

测试结果

判空

获取长度

一个位置的取下标

一段距离的取下标

以上比较中,判断 String 是否为空,访问 String 的 isEmpty 速度最快。对于其他操作,遵循 RandomAccessCollection 协议(ContiguousArray<Int8>、String.UTF16View 以及其他 Array)的类型效率较高。

进一步比较判空操作

let loopCount = 10000
var date = Date() for _ in 0..<loopCount {
_ = s.isEmpty
} print("isEmpty")
printAndUpdateTime(&date) for _ in 0..<loopCount {
_ = s == ""
} print("== \"\"")
printAndUpdateTime(&date)

与访问 String 的 isEmpty 相比,判断 String 是否等于空 String 速度更快!

注意到文档中,对 String.UTF8View 和 String.UTF16View 的 Range 取下标方法的说明

subscript(bounds: Range<String.UTF8View.Index>) -> String.UTF8View { get }
subscript(bounds: Range<String.UTF16View.Index>) -> String.UTF16View { get }
Complexity: O(n) if the underlying string is bridged from Objective-C, where n is the length of the string; otherwise, O(1).

如果 String 是从 Objective-C 的 NSString 桥接来的,时间复杂度为 O(n),否则为 O(1)。这句话怎么理解呢?前面说了,String.UTF8View 不遵循 RandomAccessCollection 协议,而 String.UTF16View 遵循 RandomAccessCollection 协议,两者的时间复杂度应该不同。这里怎么说时间复杂度与 String 是否桥接自 NSString 有关?以下进一步探究。

let s2 = NSString(string: s) as String

let loopCount = 10000
let index = s.characters.count / 2
let index2 = s.characters.count - 1 func test(_ s: String) {
var date = Date() let utf8 = s.utf8
for _ in 0..<loopCount {
_ = utf8[utf8.startIndex..<utf8.index(utf8.startIndex, offsetBy: index)]
} print("UTF-8 index")
printAndUpdateTime(&date) for _ in 0..<loopCount {
_ = utf8[utf8.startIndex..<utf8.index(utf8.startIndex, offsetBy: index2)]
} print("UTF-8 index2")
printAndUpdateTime(&date) let utf16 = s.utf16
for _ in 0..<loopCount {
_ = utf16[utf16.startIndex..<utf16.index(utf16.startIndex, offsetBy: index)]
} print("UTF-16 index")
printAndUpdateTime(&date) for _ in 0..<loopCount {
_ = utf16[utf16.startIndex..<utf16.index(utf16.startIndex, offsetBy: index2)]
} print("UTF-16 index2")
printAndUpdateTime(&date)
} print("String")
test(s)
print("\nString bridged from NSString")
test(s2)

测试结果

对比 index 与 index2 的差异。测试参数 index2 约为 index 的 2 倍。UTF-8 index2 的耗时也约为 index 的 2 倍。UTF-16 的 index 和 index2 耗时相近。这与是否遵循 RandomAccessCollection 协议一致。

对比 String 与 NSString 的差异。桥接自 NSString 的 String 耗时比 String 要长,UTF-8 尤其明显。这应该就是文档说明的情况。用 Range 取下标,桥接自 NSString 的 String,比 String 多一些操作,多出 O(n) 级别的时间,而不是取下标的时间复杂度是 O(n)。

应用

具体应用时,选取哪种编码方式、取下标方式?首先,编码方式要看具体应用场景。编码方法不同,字符串的长度可能不同。如果字符串只含英文,比较好办。如果字符串含有中文或 Emoji,选择编码方式就要慎重。注意,NSString 的 length 属性获得的长度对应 UTF-16 编码。

let str = "abc"
str.characters.count // 3
str.unicodeScalars.count // 3
str.utf16.count // 3
(str as NSString).length // 3
str.utf8.count // 3
str.utf8CString.count - 1 // 3
strlen(str) // 3 let emojiStr = "

Swift 中 String 取下标及性能问题的更多相关文章

  1. Swift中String和NSString的一个不同之处

    我们知道在Swift中String和NSString是可以互相转换使用的-额-应该是在绝大数情况下可以互相转换使用.在某些情况下可能还有一丝丝略微的差别:比如在涉及到处理字符串中字符索引的时候. 我们 ...

  2. Swift 中 String 与 CChar 数组的转换

    在现阶段Swift的编码中,我们还是有很多场景需要调用一些C函数.在Swift与C的混编中,经常遇到的一个问题就是需要在两者中互相转换字符串.在C语言中,字符串通常是用一个char数组来表示,在Swi ...

  3. swift 中String,Int 等类型使用注意,整理中

    swfit中的String和Int是 struct定义的,不同于NSString和NSNumber, 如果想在一个数组中同时包含String和Int,那么这个数组要声明为[Any] 而不是 [AnyO ...

  4. swift 中String常用操作

    1.  字符串定义 var s = "aaaaaa" // 两个字符串均为空并等价. var emptyString = ""   var anotherEmp ...

  5. Swift中String与NSDate的互相转换

    其实每种编程语言,我都觉得String和日期对象的相互转换是一种十分麻烦的事情,Swift也不例外.这篇博客记录了我学到的String与NSDate的互相转换方法,供大家参考. 从String转为NS ...

  6. Swift Tips - 在 Swift 中自定义下标访问

    Untitled Document.md input[type="date"].form-control,.input-group-sm>input[type="d ...

  7. swift中Any,AnyObject,AnyClass的区别

    这几个概念让人很迷惑,看了很多帖子,终于搞明白了,简单总结: Any 和 AnyObject 是 Swift 中两个妥协的产物.什么意思呢,oc中有个id关键字,表示任何对象,oc和swift混编的时 ...

  8. Swift中实现ruby中字符串乘法倍增的功能

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 我们知道在ruby中对于字符串类型我们可以用乘法生成一个指定数 ...

  9. 30天C#基础巩固------this,base,string中的方法,StringBuilder性能

    这里主要是记录下自己学习笔记,希望有个地方在以后可以看到自己走过的路. 关于之前多态的知识有一个口诀,很好理解里面的override和new,virtual关键字. "new则隐藏,over ...

随机推荐

  1. 苹果App删除 AppStore应用删除指南

    最近做IOS应用上传时,想改一下SUK或者Bundle ID ,发现改不了,只能删除. 但是删除却没有找到删除按钮,百度半天不给力,不过后来多亏一位群里的兄弟给找个链接,终于解决 今天就给他记录下来, ...

  2. [SinGuLaRiTy] 米勒罗宾素数判定法

    [SinGuLaRiTy-1003] Copyright (c) SinGuLaRiTy 2017. All Rights Reserved. 背景 数论学家利用费马小定理研究出了多种素数测试办法,M ...

  3. C#研究OpenXML之路(2-DocumentFormat.OpenXml命名空间)

    一.OpenXML对象结构预览 昨天感受了一下OpenXML的编程,今天开始准备一头扎进OpenXML了.在了解一门新的知识前,首先最重要的是理清逻辑结构,否则学习起来会感觉摸不着北. 1.首先打开V ...

  4. 深入浅出分析MySQL索引设计背后的数据结构

    在我们公司的DB规范中,明确规定: 1.建表语句必须明确指定主键 2.无特殊情况,主键必须单调递增 对于这项规定,很多研发小伙伴不理解.本文就来深入简出地分析MySQL索引设计背后的数据结构和算法,从 ...

  5. z-index用法总结

    一.定义: z-index 只适用于元素有定位的情况,表示层级 数值越大 层级越高 展示的位置越靠前. 二.用法: 1.同级关系: z-index值较大的元素将叠加在z-index值较小的元素之上 ( ...

  6. 使用点击二分图计算query-document的相关性

    之前的博客中已经介绍了Ranking Relevance的一些基本情况(Click Behavior,和Text Match):http://www.cnblogs.com/bentuwuying/p ...

  7. Select()和SelectMany()的区别

    Select与SelectMany的区别 Select() 和 SelectMany() 的工作都是依据源值生成一个或多个结果值. Select() 为每个源值生成一个结果值.因此,总体结果是一个与源 ...

  8. [Linux] PHP程序员玩转Linux系列-升级PHP到PHP7

    1.PHP程序员玩转Linux系列-怎么安装使用CentOS 2.PHP程序员玩转Linux系列-lnmp环境的搭建 3.PHP程序员玩转Linux系列-搭建FTP代码开发环境 4.PHP程序员玩转L ...

  9. HTTP长连接、短连接使用及测试

    概念 HTTP短连接(非持久连接)是指,客户端和服务端进行一次HTTP请求/响应之后,就关闭连接.所以,下一次的HTTP请求/响应操作就需要重新建立连接. HTTP长连接(持久连接)是指,客户端和服务 ...

  10. 新型钓鱼手段预警:你看到的 аррӏе.com 真是苹果官网?

    研究人员发现一种"几乎无法检测"的新型钓鱼攻击,就连最细心的网民也难以辨别.黑客可通过利用已知漏洞在 Chrome.Firefox 与 Opera 浏览器中伪造显示合法网站域名(例 ...