MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Swift字符串处理艺术

2021-10-015.1k 阅读

字符串基础

在Swift中,字符串是由字符组成的有序集合,并且String类型是结构体。Swift的字符串在存储和处理上进行了优化,以适应现代编程的需求。

字符串的创建与初始化

  1. 直接创建
    let simpleString = "Hello, Swift!"
    
    这里通过双引号直接创建了一个字符串常量。
  2. 使用String构造函数
    let charArray: [Character] = ["H", "e", "l", "l", "o"]
    let fromCharArray = String(charArray)
    
    此代码通过将字符数组传递给String构造函数来创建字符串。

字符串的可变性

在Swift中,字符串可以是常量(let)或变量(var)。常量字符串一旦创建就不能修改,而变量字符串可以在其生命周期内改变。

var mutableString = "Initial value"
mutableString = "New value"

字符串的基本操作

字符串拼接

  1. 使用+运算符
    let part1 = "Hello"
    let part2 = " world"
    let combined = part1 + part2
    
    这里通过+运算符将两个字符串拼接在一起。
  2. 使用append方法
    var stringToAppend = "Start"
    stringToAppend.append(" end")
    
    append方法将新的字符串追加到现有字符串的末尾。

字符串长度

在Swift中,可以使用count属性来获取字符串的长度。

let sampleString = "Swift programming"
let length = sampleString.count

需要注意的是,Swift字符串的长度是基于字符数量计算的,而不是基于字节数。对于一些复杂的Unicode字符,一个字符可能占用多个字节。

字符串索引

Swift的字符串索引是基于字符边界的,而不是基于字节偏移。这使得在处理不同字符集时更加安全和准确。

获取字符串索引

  1. startIndexendIndex
    let text = "Swift"
    let start = text.startIndex
    let end = text.endIndex
    
    startIndex指向字符串的第一个字符的位置,endIndex指向字符串最后一个字符之后的位置。
  2. index(after:)index(before:)
    let str = "Hello"
    let indexAfterFirst = str.index(after: str.startIndex)
    let indexBeforeLast = str.index(before: str.endIndex)
    
    index(after:)获取指定索引之后的索引,index(before:)获取指定索引之前的索引。

通过索引访问字符

let lang = "Swift"
let firstChar = lang[lang.startIndex]
let secondChar = lang[lang.index(after: lang.startIndex)]

但是要注意,如果索引越界,会导致运行时错误。

字符串切片

字符串切片允许从原字符串中提取一部分子字符串。

创建字符串切片

let longString = "This is a long string"
let startIndex = longString.index(longString.startIndex, offsetBy: 5)
let endIndex = longString.index(longString.startIndex, offsetBy: 10)
let subStringSlice = longString[startIndex..<endIndex]

这里从longString中提取了从索引5到索引10(不包括10)的子字符串切片。

将字符串切片转换为字符串

let sliceToString = String(subStringSlice)

字符串切片本身不是String类型,需要通过String构造函数转换为String

字符串搜索与匹配

前缀和后缀匹配

  1. 前缀匹配
    let url = "https://example.com"
    let hasHttpPrefix = url.hasPrefix("https://")
    
    hasPrefix方法用于检查字符串是否以指定的前缀开头。
  2. 后缀匹配
    let fileName = "document.txt"
    let hasTxtSuffix = fileName.hasSuffix(".txt")
    
    hasSuffix方法用于检查字符串是否以指定的后缀结尾。

子字符串搜索

  1. range(of:)方法
    let sentence = "Swift is a powerful programming language"
    if let range = sentence.range(of: "powerful") {
        print("Substring found at range: \(range)")
    }
    
    range(of:)方法返回子字符串在原字符串中的范围,如果找不到则返回nil
  2. contains方法
    let anotherSentence = "The cat is on the mat"
    let containsCat = anotherSentence.contains("cat")
    
    contains方法简单地判断字符串是否包含指定的子字符串。

字符串替换

简单替换

var replaceString = "I like apples"
replaceString = replaceString.replacingOccurrences(of: "apples", with: "oranges")

replacingOccurrences(of:with:)方法将字符串中所有匹配的子字符串替换为新的字符串。

基于正则表达式的替换

Swift通过NSRegularExpression类来支持正则表达式。

import Foundation
let regexString = "a+"
let original = "aaaa bbb"
if let regex = try? NSRegularExpression(pattern: regexString) {
    let newString = regex.stringByReplacingMatches(in: original, range: NSRange(original.startIndex..., in: original), withTemplate: "X")
    print(newString)
}

这里将字符串中连续的a替换为X

字符串格式化

基本字符串插值

let number = 42
let formattedString = "The number is \(number)"

通过在字符串中使用\( ),可以将变量的值插入到字符串中。

格式化数字

  1. 使用String(format:)
    let pi = 3.14159
    let formattedPi = String(format: "%.2f", pi)
    
    这里使用%.2f格式化字符串,将pi保留两位小数。
  2. 使用NumberFormatter
    let numberToFormat = 1234567.89
    let formatter = NumberFormatter()
    formatter.numberStyle =.decimal
    if let formatted = formatter.string(from: NSNumber(value: numberToFormat)) {
        print(formatted)
    }
    
    NumberFormatter提供了更多灵活的数字格式化选项,如货币格式、百分比格式等。

字符串与其他类型转换

字符串转数字

  1. 转整数
    let intString = "42"
    if let number = Int(intString) {
        print("Converted to int: \(number)")
    }
    
    Int构造函数尝试将字符串转换为整数,如果转换失败则返回nil
  2. 转浮点数
    let doubleString = "3.14"
    if let doubleValue = Double(doubleString) {
        print("Converted to double: \(doubleValue)")
    }
    
    同样,Double构造函数用于将字符串转换为浮点数。

数字转字符串

  1. 使用String构造函数
    let num = 100
    let numToString = String(num)
    
    直接将数字传递给String构造函数可以将其转换为字符串。
  2. 使用description属性
    let decimalNumber = 123.45
    let desc = decimalNumber.description
    
    对于数字类型,description属性返回其字符串表示。

字符串本地化

在国际化应用中,字符串本地化非常重要。

创建本地化字符串文件

  1. 在Xcode项目中,创建.strings文件,例如Localizable.strings
  2. 在文件中添加键值对,如:
    "greeting" = "Hello";
    

使用本地化字符串

let key = "greeting"
let localizedString = NSLocalizedString(key, comment: "")

NSLocalizedString函数根据设备当前的语言设置,从本地化字符串文件中获取相应的字符串。

高级字符串处理 - 正则表达式深度解析

正则表达式语法基础

  1. 字符匹配
    • 普通字符:直接匹配字符本身,例如a匹配字符a
    • 元字符:具有特殊含义,如.匹配除换行符以外的任意字符。
    let pattern1 = "a.c"
    let text1 = "abc"
    if let regex1 = try? NSRegularExpression(pattern: pattern1) {
        let match1 = regex1.firstMatch(in: text1, range: NSRange(text1.startIndex..., in: text1))!= nil
        print(match1)
    }
    
  2. 字符类
    • 方括号[]定义字符类,例如[abc]匹配abc中的任意一个字符。
    • 范围表示,如[a - z]匹配任意小写字母。
    let pattern2 = "[a - z]bc"
    let text2 = "abc"
    if let regex2 = try? NSRegularExpression(pattern: pattern2) {
        let match2 = regex2.firstMatch(in: text2, range: NSRange(text2.startIndex..., in: text2))!= nil
        print(match2)
    }
    
  3. 量词
    • *:匹配前面的字符零次或多次,例如a*匹配零个或多个a
    • +:匹配前面的字符一次或多次,例如a+匹配一个或多个a
    • ?:匹配前面的字符零次或一次,例如a?匹配零个或一个a
    let pattern3 = "a*bc"
    let text3 = "bc"
    if let regex3 = try? NSRegularExpression(pattern: pattern3) {
        let match3 = regex3.firstMatch(in: text3, range: NSRange(text3.startIndex..., in: text3))!= nil
        print(match3)
    }
    
  4. 分组
    • 圆括号()用于分组,例如(ab)+匹配一个或多个ab
    • 分组可以捕获匹配的内容,以便后续引用。
    let pattern4 = "(ab)+"
    let text4 = "abab"
    if let regex4 = try? NSRegularExpression(pattern: pattern4) {
        let match4 = regex4.firstMatch(in: text4, range: NSRange(text4.startIndex..., in: text4))!= nil
        print(match4)
    }
    

正则表达式在Swift中的高级应用

  1. 捕获组的使用
    let datePattern = "(\\d{4})-(\\d{2})-(\\d{2})"
    let dateText = "2023 - 10 - 05"
    if let regex = try? NSRegularExpression(pattern: datePattern) {
        if let match = regex.firstMatch(in: dateText, range: NSRange(dateText.startIndex..., in: dateText)) {
            for i in 1...match.numberOfRanges {
                let range = match.range(at: i)
                let capturedString = (dateText as NSString).substring(with: range)
                print("Capture group \(i): \(capturedString)")
            }
        }
    }
    
    这里通过正则表达式捕获日期字符串中的年、月、日部分。
  2. 替换中的反向引用
    let namePattern = "(Mr|Ms|Mrs)\\. (\\w+)"
    let nameText = "Mr. John"
    let replacement = "$2, $1"
    if let regex = try? NSRegularExpression(pattern: namePattern) {
        let newText = regex.stringByReplacingMatches(in: nameText, range: NSRange(nameText.startIndex..., in: nameText), withTemplate: replacement)
        print(newText)
    }
    
    此代码将Mr. John转换为John, Mr,通过反向引用$1$2来重新排列捕获组的内容。

字符串性能优化

避免频繁拼接

在循环中频繁拼接字符串会导致性能问题,因为每次拼接都会创建一个新的字符串对象。

// 不推荐的方式
var slowString = ""
for i in 0..<1000 {
    slowString += "\(i)"
}

// 推荐的方式
var components: [String] = []
for i in 0..<1000 {
    components.append("\(i)")
}
let fastString = components.joined()

预分配空间

对于已知长度的字符串构建,可以预分配空间来提高性能。

let expectedLength = 1000
var preallocatedString = String(repeating: "", count: expectedLength)
for i in 0..<expectedLength {
    preallocatedString.append("\(i)")
}

使用NSString的性能优势

在某些情况下,NSString提供了更高效的字符串操作。例如,在处理大量文本时,NSStringrange(of:options:)方法在性能上可能优于Stringrange(of:)方法。

let largeText = "..." // 大量文本
let nsString = largeText as NSString
if let range = nsString.range(of: "substring", options:.caseInsensitive) {
    print("Range found: \(range)")
}

字符串编码与解码

常见编码格式

  1. UTF - 8
    • UTF - 8是一种变长编码,它可以表示Unicode字符集中的所有字符。在Swift中,字符串默认以UTF - 8编码存储。
    let utf8String = "Hello, 世界"
    let utf8Data = utf8String.data(using:.utf8)
    
    这里通过data(using:.utf8)方法将字符串转换为UTF - 8编码的数据。
  2. UTF - 16
    • UTF - 16也是一种常用的编码,对于基本多文种平面(BMP)内的字符,它使用16位编码,对于其他字符使用两个16位代码单元。
    let utf16Data = utf8String.data(using:.utf16)
    
  3. ASCII
    • ASCII编码只能表示128个字符,主要用于英文字符。
    let asciiString = "Hello"
    let asciiData = asciiString.data(using:.ascii)
    

编码转换

  1. 从一种编码转换为另一种编码
    let originalString = "Hello"
    if let utf8Data = originalString.data(using:.utf8),
       let newString = String(data: utf8Data, encoding:.utf16) {
        print(newString)
    }
    
    这里将UTF - 8编码的数据转换为UTF - 16编码的字符串。

处理编码错误

在编码和解码过程中,可能会出现错误。例如,当尝试将无效的字节数据解码为字符串时。

let invalidData: Data = // 无效的字节数据
if let wrongString = String(data: invalidData, encoding:.utf8) {
    print(wrongString)
} else {
    print("Decoding failed")
}

通过这种方式可以检测并处理编码和解码过程中的错误。

字符串与集合的交互

字符串与数组

  1. 字符串拆分为字符数组
    let charArrayFromString: [Character] = Array("Swift")
    
    这里通过Array构造函数将字符串转换为字符数组。
  2. 字符串拆分为子字符串数组
    let sentence = "Swift is a programming language"
    let words = sentence.components(separatedBy: " ")
    
    components(separatedBy:)方法根据指定的分隔符将字符串拆分为子字符串数组。

字符串与集合类型转换

  1. 将字符串转换为集合
    let uniqueChars: Set<Character> = Set("Hello")
    
    这里将字符串转换为字符集合,集合会自动去除重复的字符。
  2. 从集合创建字符串
    let charSet: Set<Character> = ["a", "b", "c"]
    let newStringFromSet = String(charSet.sorted())
    
    先对集合进行排序,然后通过String构造函数将字符集合转换为字符串。

字符串处理中的错误处理

解析错误

在将字符串解析为其他类型(如数字)时,可能会出现解析错误。

let badNumberString = "abc"
if let number = Int(badNumberString) {
    print("Parsed number: \(number)")
} else {
    print("Failed to parse number")
}

编码和解码错误

如前面提到的,在字符串编码和解码过程中也可能出现错误。在处理文件读取或网络传输时,这种错误更为常见。

let fileURL = URL(fileURLWithPath: "text.txt")
if let data = try? Data(contentsOf: fileURL),
   let string = String(data: data, encoding:.utf8) {
    print(string)
} else {
    print("Failed to read or decode file")
}

通过这种方式可以在字符串处理过程中有效地处理各种可能出现的错误,确保程序的健壮性。

字符串处理的实际应用场景

表单验证

在用户输入表单数据时,经常需要对输入的字符串进行验证。例如,验证电子邮件地址格式。

let emailPattern = "[A - Za - z0 - 9._%+-]+@[A - Za - z0 - 9.-]+\\.[A - Za - z]{2,}"
let testEmail = "user@example.com"
if let regex = try? NSRegularExpression(pattern: emailPattern) {
    let isValid = regex.firstMatch(in: testEmail, range: NSRange(testEmail.startIndex..., in: testEmail))!= nil
    print(isValid)
}

数据提取

从文本数据中提取特定信息是字符串处理的常见应用。例如,从HTML文档中提取链接。

let html = "<a href=\"https://example.com\">Link</a>"
let linkPattern = "<a href=\"([^\"]+)\">"
if let regex = try? NSRegularExpression(pattern: linkPattern) {
    if let match = regex.firstMatch(in: html, range: NSRange(html.startIndex..., in: html)) {
        let linkRange = match.range(at: 1)
        let link = (html as NSString).substring(with: linkRange)
        print(link)
    }
}

文本格式化与排版

在文本处理应用中,需要对文本进行格式化和排版。例如,将文本按照一定的宽度进行换行。

let longText = "This is a very long text that needs to be formatted."
let width = 20
var formattedText = ""
var index = longText.startIndex
while index < longText.endIndex {
    let endIndex = longText.index(index, offsetBy: min(width, longText.distance(from: index, to: longText.endIndex)), limitedBy: longText.endIndex)?? longText.endIndex
    let line = longText[index..<endIndex]
    formattedText += "\(line)\n"
    index = endIndex
}
print(formattedText)

通过这些实际应用场景,可以看到字符串处理在Swift编程中无处不在,掌握好字符串处理技巧对于开发高质量的应用程序至关重要。