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.CharacterViewcharacters[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.CharacterViewlet characters2 = characters[characters.startIndex..<characters.index(after: characters.startIndex)] // String.CharacterViewString(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" // trueb > "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.UTF8Viewlet n = utf8[utf8.startIndex] // UInt8 97let 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 97let 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 97let 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.UTF16Viewlet n = utf16[utf16.startIndex] // UInt16 97let 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 97let 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 Foundationenum 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 = 10000let index = s.characters.count / 2let testType: TestType = .rangeprint(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.charactersfor _ 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.utf8for _ 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.utf8CStringfor _ 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.utf16for _ 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)

测试结果

判空

1089786-20170519112256557-1487081520.png

获取长度

1089786-20170519112311432-1018793472.png

一个位置的取下标

1089786-20170519112328213-1921912.png

一段距离的取下标

1089786-20170519112346682-746173450.png

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

进一步比较判空操作

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

1089786-20170519112408353-2037886189.png

与访问 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 Stringlet loopCount = 10000let index = s.characters.count / 2let index2 = s.characters.count - 1func 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)

测试结果

1089786-20170519112431057-1897148719.png

对比 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 // 3str.unicodeScalars.count // 3str.utf16.count // 3(str as NSString).length // 3str.utf8.count // 3str.utf8CString.count - 1 // 3strlen(str) // 3let emojiStr = ""emojiStr.characters.count // 1emojiStr.unicodeScalars.count // 2emojiStr.utf16.count // 4(emojiStr as NSString).length // 4emojiStr.utf8.count // 8emojiStr.utf8CString.count - 1 // 8strlen(emojiStr) // 8let ChineseStr = "中文"ChineseStr.characters.count // 2ChineseStr.unicodeScalars.count // 2ChineseStr.utf16.count // 2(ChineseStr as NSString).length // 2ChineseStr.utf8.count // 6ChineseStr.utf8CString.count - 1 // 6strlen(ChineseStr) // 6













本文转自xmgdc51CTO博客,原文链接:http://blog.51cto.com/12953214/1940545 ,如需转载请自行联系原作者



相关文章
|
6月前
|
存储 缓存 安全
String 既然能这样性能调优,我直呼内行(文末送书)
String 既然能这样性能调优,我直呼内行(文末送书)
69 1
|
10天前
|
存储 Swift iOS开发
Swift 下标脚本
10月更文挑战第30天
18 3
|
6月前
|
安全 Swift 开发者
【Swift开发专栏】Swift中的异步编程与性能提升
【4月更文挑战第30天】本文探讨了Swift中的异步编程,旨在提升应用性能。首先,介绍了异步编程的基本概念,强调其在处理并发任务和提高响应性中的重要性。接着,阐述了Swift中的异步模型,包括回调函数、Promises以及新引入的`async/await`和`Task`结构体。最后,讨论了通过避免主线程阻塞、合理使用线程和并发优化等策略来提升性能。Swift的异步模型为开发者提供了更简洁、安全的异步编程方式,有助于构建高效应用程序。
82 1
|
6月前
|
缓存 算法 Swift
【Swift 开发专栏】Swift 应用的性能优化技巧
【4月更文挑战第30天】本文探讨了Swift应用性能优化,强调理解性能瓶颈、针对性优化和平衡性能与代码质量的重要性。提出优化技巧,包括选择合适数据结构、避免不必要的对象创建、使用缓存、优化算法、减少计算、管理内存、利用多核处理、优化网络请求和界面渲染。通过实际案例分析证明了这些方法能有效提升应用性能和用户体验。开发者应持续关注新技术和方法,以适应不断提升的性能要求。
90 1
|
存储 Java 索引
[oeasy]python0071_字符串类型_str_string_下标运算符_中括号
[oeasy]python0071_字符串类型_str_string_下标运算符_中括号
84 0
|
Swift
Swift - 用装有控制器name的数组for循环批量创建控制器(string转UIViewController)
Swift - 用装有控制器name的数组for循环批量创建控制器(string转UIViewController)
99 0
|
存储 Swift iOS开发
Swift实用小册11: Subscript下标语法的使用
Swift实用小册11: Subscript下标语法的使用
247 0
Swift实用小册11: Subscript下标语法的使用
JAVA String.format的使用以及StringBuilder和String ‘+’的性能对比
JAVA String.format的使用以及StringBuilder和String ‘+’的性能对比
616 0
JAVA String.format的使用以及StringBuilder和String ‘+’的性能对比
|
C# C++ 索引
30天C#基础巩固------this,base,string中的方法,StringBuilder性能
30天C#基础巩固------this,base,string中的方法,StringBuilder性能
160 0
30天C#基础巩固------this,base,string中的方法,StringBuilder性能