1.2.4 字符串处理

1.2.4 字符串处理 #

字符串是编程中最常用的数据类型之一。Go 语言提供了强大的字符串处理能力,包括丰富的标准库函数和高效的字符串操作。本节将详细介绍 Go 语言中字符串的特性、操作方法和最佳实践。

字符串基础 #

1. 字符串的内部表示 #

package main

import (
    "fmt"
    "unsafe"
)

func main() {
    // 字符串的基本特性
    str := "Hello, 世界!"

    fmt.Printf("字符串: %s\n", str)
    fmt.Printf("长度(字节): %d\n", len(str))
    fmt.Printf("内存大小: %d 字节\n", unsafe.Sizeof(str))

    // 字符串是不可变的
    // str[0] = 'h'  // 编译错误:不能修改字符串

    // 字符串的内部结构(概念性展示)
    fmt.Printf("字符串地址: %p\n", &str)
    fmt.Printf("字符串数据地址: %p\n", unsafe.Pointer(&str))

    // 空字符串
    var emptyStr string
    fmt.Printf("空字符串: %q, 长度: %d\n", emptyStr, len(emptyStr))

    // 字符串字面量
    str1 := "普通字符串"
    str2 := `原始字符串
可以包含换行符
和"引号"`

    fmt.Printf("普通字符串: %s\n", str1)
    fmt.Printf("原始字符串:\n%s\n", str2)
}

2. 字符串和字节 #

package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {
    str := "Hello, 世界!"

    // 字符串转字节切片
    bytes := []byte(str)
    fmt.Printf("字节切片: %v\n", bytes)
    fmt.Printf("字节数: %d\n", len(bytes))

    // 字节切片转字符串
    newStr := string(bytes)
    fmt.Printf("转换回字符串: %s\n", newStr)

    // UTF-8 编码分析
    fmt.Printf("UTF-8 字符数: %d\n", utf8.RuneCountInString(str))

    // 逐字节遍历
    fmt.Print("逐字节: ")
    for i := 0; i < len(str); i++ {
        fmt.Printf("%02X ", str[i])
    }
    fmt.Println()

    // 逐字符遍历
    fmt.Print("逐字符: ")
    for _, r := range str {
        fmt.Printf("%c ", r)
    }
    fmt.Println()

    // 字符串索引(字节索引)
    fmt.Printf("第一个字节: %c (%d)\n", str[0], str[0])

    // 获取字符串中的 rune
    r, size := utf8.DecodeRuneInString(str[7:])  // 从"世"开始
    fmt.Printf("字符: %c, 字节大小: %d\n", r, size)
}

字符串操作 #

1. 字符串连接 #

package main

import (
    "fmt"
    "strings"
    "bytes"
)

func main() {
    // 使用 + 操作符连接
    str1 := "Hello"
    str2 := "World"
    result1 := str1 + ", " + str2 + "!"
    fmt.Printf("+ 操作符: %s\n", result1)

    // 使用 fmt.Sprintf
    result2 := fmt.Sprintf("%s, %s!", str1, str2)
    fmt.Printf("fmt.Sprintf: %s\n", result2)

    // 使用 strings.Join
    parts := []string{str1, str2}
    result3 := strings.Join(parts, ", ") + "!"
    fmt.Printf("strings.Join: %s\n", result3)

    // 使用 strings.Builder(推荐用于大量字符串连接)
    var builder strings.Builder
    builder.WriteString(str1)
    builder.WriteString(", ")
    builder.WriteString(str2)
    builder.WriteString("!")
    result4 := builder.String()
    fmt.Printf("strings.Builder: %s\n", result4)

    // 使用 bytes.Buffer
    var buffer bytes.Buffer
    buffer.WriteString(str1)
    buffer.WriteString(", ")
    buffer.WriteString(str2)
    buffer.WriteString("!")
    result5 := buffer.String()
    fmt.Printf("bytes.Buffer: %s\n", result5)

    // 性能比较示例
    fmt.Println("\n性能比较(概念性):")
    fmt.Println("+ 操作符: 简单但效率低(创建新字符串)")
    fmt.Println("strings.Builder: 高效(预分配内存)")
    fmt.Println("bytes.Buffer: 通用但稍重(支持更多操作)")
}

2. 字符串查找和替换 #

package main

import (
    "fmt"
    "strings"
)

func main() {
    text := "Go is a programming language. Go is simple and efficient."

    // 查找子字符串
    fmt.Printf("包含 'Go': %t\n", strings.Contains(text, "Go"))
    fmt.Printf("包含 'Java': %t\n", strings.Contains(text, "Java"))

    // 查找索引
    index := strings.Index(text, "Go")
    fmt.Printf("'Go' 第一次出现的位置: %d\n", index)

    lastIndex := strings.LastIndex(text, "Go")
    fmt.Printf("'Go' 最后一次出现的位置: %d\n", lastIndex)

    // 计数
    count := strings.Count(text, "Go")
    fmt.Printf("'Go' 出现次数: %d\n", count)

    // 前缀和后缀检查
    fmt.Printf("以 'Go' 开头: %t\n", strings.HasPrefix(text, "Go"))
    fmt.Printf("以 '.' 结尾: %t\n", strings.HasSuffix(text, "."))

    // 替换
    replaced := strings.Replace(text, "Go", "Golang", 1)  // 替换第一个
    fmt.Printf("替换第一个: %s\n", replaced)

    replacedAll := strings.ReplaceAll(text, "Go", "Golang")  // 替换所有
    fmt.Printf("替换所有: %s\n", replacedAll)

    // 使用 Replacer 进行多重替换
    replacer := strings.NewReplacer(
        "Go", "Golang",
        "simple", "easy",
        "efficient", "fast",
    )
    multiReplaced := replacer.Replace(text)
    fmt.Printf("多重替换: %s\n", multiReplaced)
}

3. 字符串分割和连接 #

package main

import (
    "fmt"
    "strings"
)

func main() {
    // 字符串分割
    text := "apple,banana,cherry,date"

    // 按分隔符分割
    fruits := strings.Split(text, ",")
    fmt.Printf("分割结果: %v\n", fruits)

    // 限制分割次数
    limitedSplit := strings.SplitN(text, ",", 3)
    fmt.Printf("限制分割: %v\n", limitedSplit)

    // 按空白字符分割
    sentence := "  Go   is    awesome  "
    words := strings.Fields(sentence)
    fmt.Printf("按空白分割: %v\n", words)

    // 自定义分割函数
    customSplit := strings.FieldsFunc(sentence, func(r rune) bool {
        return r == ' ' || r == '\t'
    })
    fmt.Printf("自定义分割: %v\n", customSplit)

    // 字符串连接
    joined := strings.Join(fruits, " | ")
    fmt.Printf("连接结果: %s\n", joined)

    // 重复字符串
    repeated := strings.Repeat("Go! ", 3)
    fmt.Printf("重复字符串: %s\n", repeated)
}

4. 字符串修剪和格式化 #

package main

import (
    "fmt"
    "strings"
    "unicode"
)

func main() {
    // 修剪空白字符
    text := "  \t  Hello, World!  \n  "
    trimmed := strings.TrimSpace(text)
    fmt.Printf("原始: %q\n", text)
    fmt.Printf("修剪空白: %q\n", trimmed)

    // 修剪指定字符
    text2 := "!!!Hello, World!!!"
    trimmedChars := strings.Trim(text2, "!")
    fmt.Printf("修剪感叹号: %q\n", trimmedChars)

    // 修剪前缀和后缀
    text3 := "prefixHello, World!suffix"
    trimmedPrefix := strings.TrimPrefix(text3, "prefix")
    trimmedSuffix := strings.TrimSuffix(trimmedPrefix, "suffix")
    fmt.Printf("修剪前后缀: %q\n", trimmedSuffix)

    // 自定义修剪函数
    text4 := "123Hello, World!456"
    trimmedFunc := strings.TrimFunc(text4, unicode.IsDigit)
    fmt.Printf("修剪数字: %q\n", trimmedFunc)

    // 大小写转换
    original := "Hello, World!"
    fmt.Printf("原始: %s\n", original)
    fmt.Printf("大写: %s\n", strings.ToUpper(original))
    fmt.Printf("小写: %s\n", strings.ToLower(original))
    fmt.Printf("标题格式: %s\n", strings.Title(original))

    // 首字母大写(Go 1.18+)
    fmt.Printf("首字母大写: %s\n", strings.ToTitle(original))
}

字符串格式化 #

1. fmt 包的格式化功能 #

package main

import (
    "fmt"
    "time"
)

func main() {
    // 基本格式化
    name := "Alice"
    age := 30
    height := 165.5

    // 字符串格式化
    formatted := fmt.Sprintf("姓名: %s, 年龄: %d, 身高: %.1f cm", name, age, height)
    fmt.Println(formatted)

    // 不同的格式化动词
    num := 42
    fmt.Printf("十进制: %d\n", num)
    fmt.Printf("二进制: %b\n", num)
    fmt.Printf("八进制: %o\n", num)
    fmt.Printf("十六进制: %x\n", num)
    fmt.Printf("十六进制(大写): %X\n", num)

    // 浮点数格式化
    pi := 3.14159265359
    fmt.Printf("默认: %f\n", pi)
    fmt.Printf("指定精度: %.2f\n", pi)
    fmt.Printf("科学计数法: %e\n", pi)
    fmt.Printf("科学计数法(大写): %E\n", pi)
    fmt.Printf("自动选择: %g\n", pi)

    // 字符串格式化
    str := "Hello"
    fmt.Printf("字符串: %s\n", str)
    fmt.Printf("带引号: %q\n", str)
    fmt.Printf("十六进制: %x\n", str)

    // 布尔值
    flag := true
    fmt.Printf("布尔值: %t\n", flag)

    // 指针
    ptr := &num
    fmt.Printf("指针: %p\n", ptr)

    // 类型信息
    fmt.Printf("类型: %T\n", num)
    fmt.Printf("值: %v\n", num)
    fmt.Printf("Go语法表示: %#v\n", num)

    // 宽度和对齐
    fmt.Printf("右对齐: '%10s'\n", "Go")
    fmt.Printf("左对齐: '%-10s'\n", "Go")
    fmt.Printf("零填充: '%010d'\n", 42)

    // 时间格式化
    now := time.Now()
    fmt.Printf("时间: %v\n", now)
    fmt.Printf("格式化时间: %s\n", now.Format("2006-01-02 15:04:05"))
}

2. 自定义格式化 #

package main

import (
    "fmt"
    "strings"
)

// 自定义类型实现 Stringer 接口
type Person struct {
    Name string
    Age  int
    City string
}

func (p Person) String() string {
    return fmt.Sprintf("%s (%d岁, 来自%s)", p.Name, p.Age, p.City)
}

// 实现 GoStringer 接口
func (p Person) GoString() string {
    return fmt.Sprintf("Person{Name: %q, Age: %d, City: %q}", p.Name, p.Age, p.City)
}

// 自定义格式化函数
func formatList(items []string, separator string) string {
    if len(items) == 0 {
        return ""
    }
    if len(items) == 1 {
        return items[0]
    }
    if len(items) == 2 {
        return items[0] + " 和 " + items[1]
    }

    return strings.Join(items[:len(items)-1], separator) + " 和 " + items[len(items)-1]
}

// 模板式格式化
func formatTemplate(template string, values map[string]interface{}) string {
    result := template
    for key, value := range values {
        placeholder := "{" + key + "}"
        result = strings.ReplaceAll(result, placeholder, fmt.Sprintf("%v", value))
    }
    return result
}

func main() {
    // 使用自定义 String 方法
    person := Person{Name: "张三", Age: 25, City: "北京"}
    fmt.Printf("默认格式: %s\n", person)
    fmt.Printf("详细格式: %v\n", person)
    fmt.Printf("Go语法格式: %#v\n", person)

    // 自定义列表格式化
    fruits := []string{"苹果", "香蕉", "橙子", "葡萄"}
    formatted := formatList(fruits, ", ")
    fmt.Printf("格式化列表: %s\n", formatted)

    // 模板式格式化
    template := "你好, {name}! 欢迎来到{city}, 今天是{date}."
    values := map[string]interface{}{
        "name": "李四",
        "city": "上海",
        "date": "2024年1月1日",
    }
    result := formatTemplate(template, values)
    fmt.Printf("模板格式化: %s\n", result)

    // 条件格式化
    score := 85
    var grade string
    switch {
    case score >= 90:
        grade = "优秀"
    case score >= 80:
        grade = "良好"
    case score >= 70:
        grade = "中等"
    case score >= 60:
        grade = "及格"
    default:
        grade = "不及格"
    }

    result2 := fmt.Sprintf("分数: %d, 等级: %s", score, grade)
    fmt.Printf("条件格式化: %s\n", result2)
}

正则表达式 #

1. 基本正则表达式操作 #

package main

import (
    "fmt"
    "regexp"
)

func main() {
    // 编译正则表达式
    pattern := `\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b`
    emailRegex, err := regexp.Compile(pattern)
    if err != nil {
        fmt.Printf("正则表达式编译错误: %v\n", err)
        return
    }

    // 测试文本
    text := "联系我们: [email protected][email protected], 无效邮箱: invalid.email"

    // 查找匹配
    if emailRegex.MatchString(text) {
        fmt.Println("文本包含邮箱地址")
    }

    // 查找所有匹配
    matches := emailRegex.FindAllString(text, -1)
    fmt.Printf("找到的邮箱: %v\n", matches)

    // 查找第一个匹配
    firstMatch := emailRegex.FindString(text)
    fmt.Printf("第一个邮箱: %s\n", firstMatch)

    // 查找匹配的位置
    indices := emailRegex.FindAllStringIndex(text, -1)
    for i, index := range indices {
        fmt.Printf("邮箱 %d 位置: [%d, %d]\n", i+1, index[0], index[1])
    }

    // 替换匹配的内容
    replaced := emailRegex.ReplaceAllString(text, "[邮箱已隐藏]")
    fmt.Printf("替换后: %s\n", replaced)

    // 使用函数替换
    replacedFunc := emailRegex.ReplaceAllStringFunc(text, func(match string) string {
        return fmt.Sprintf("[%s]", strings.ToUpper(match))
    })
    fmt.Printf("函数替换: %s\n", replacedFunc)
}

2. 高级正则表达式应用 #

package main

import (
    "fmt"
    "regexp"
    "strings"
)

// 数据验证器
type Validator struct {
    patterns map[string]*regexp.Regexp
}

func NewValidator() *Validator {
    patterns := map[string]*regexp.Regexp{
        "email":    regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`),
        "phone":    regexp.MustCompile(`^1[3-9]\d{9}$`),
        "idcard":   regexp.MustCompile(`^\d{17}[\dXx]$`),
        "password": regexp.MustCompile(`^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d@$!%*?&]{8,}$`),
        "url":      regexp.MustCompile(`^https?://[^\s/$.?#].[^\s]*$`),
    }

    return &Validator{patterns: patterns}
}

func (v *Validator) Validate(dataType, value string) bool {
    if pattern, exists := v.patterns[dataType]; exists {
        return pattern.MatchString(value)
    }
    return false
}

// 文本处理器
type TextProcessor struct {
    htmlTagRegex    *regexp.Regexp
    whitespaceRegex *regexp.Regexp
    urlRegex        *regexp.Regexp
}

func NewTextProcessor() *TextProcessor {
    return &TextProcessor{
        htmlTagRegex:    regexp.MustCompile(`<[^>]*>`),
        whitespaceRegex: regexp.MustCompile(`\s+`),
        urlRegex:        regexp.MustCompile(`https?://[^\s]+`),
    }
}

func (tp *TextProcessor) StripHTML(text string) string {
    return tp.htmlTagRegex.ReplaceAllString(text, "")
}

func (tp *TextProcessor) NormalizeWhitespace(text string) string {
    return strings.TrimSpace(tp.whitespaceRegex.ReplaceAllString(text, " "))
}

func (tp *TextProcessor) ExtractURLs(text string) []string {
    return tp.urlRegex.FindAllString(text, -1)
}

func (tp *TextProcessor) MaskURLs(text string) string {
    return tp.urlRegex.ReplaceAllString(text, "[链接已隐藏]")
}

func main() {
    // 数据验证示例
    validator := NewValidator()

    testData := map[string][]string{
        "email": {"[email protected]", "invalid.email", "[email protected]"},
        "phone": {"13812345678", "12345678901", "1381234567"},
        "password": {"Password123", "password", "PASSWORD123", "Pass123!"},
    }

    fmt.Println("数据验证结果:")
    for dataType, values := range testData {
        fmt.Printf("\n%s 验证:\n", dataType)
        for _, value := range values {
            valid := validator.Validate(dataType, value)
            fmt.Printf("  %s: %t\n", value, valid)
        }
    }

    // 文本处理示例
    processor := NewTextProcessor()

    htmlText := `<h1>标题</h1><p>这是一个包含 <a href="https://golang.org">链接</a> 的段落。</p>
    <div>访问 https://github.com 获取更多信息。</div>`

    fmt.Printf("\n文本处理示例:\n")
    fmt.Printf("原始HTML: %s\n", htmlText)

    // 去除HTML标签
    plainText := processor.StripHTML(htmlText)
    fmt.Printf("去除HTML: %s\n", plainText)

    // 规范化空白字符
    normalized := processor.NormalizeWhitespace(plainText)
    fmt.Printf("规范化空白: %s\n", normalized)

    // 提取URL
    urls := processor.ExtractURLs(htmlText)
    fmt.Printf("提取的URL: %v\n", urls)

    // 隐藏URL
    masked := processor.MaskURLs(normalized)
    fmt.Printf("隐藏URL: %s\n", masked)

    // 复杂的正则表达式示例:解析日志
    logPattern := regexp.MustCompile(`(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) \[(\w+)\] (.+)`)
    logEntry := "2024-01-01 10:30:45 [INFO] User login successful"

    matches := logPattern.FindStringSubmatch(logEntry)
    if len(matches) > 0 {
        fmt.Printf("\n日志解析:\n")
        fmt.Printf("完整匹配: %s\n", matches[0])
        fmt.Printf("日期: %s\n", matches[1])
        fmt.Printf("时间: %s\n", matches[2])
        fmt.Printf("级别: %s\n", matches[3])
        fmt.Printf("消息: %s\n", matches[4])
    }
}

字符串性能优化 #

1. 字符串构建性能比较 #

package main

import (
    "fmt"
    "strings"
    "bytes"
    "time"
)

// 性能测试函数
func benchmarkStringConcat(n int) {
    // 方法1: 使用 + 操作符
    start := time.Now()
    result1 := ""
    for i := 0; i < n; i++ {
        result1 += "a"
    }
    duration1 := time.Since(start)

    // 方法2: 使用 strings.Builder
    start = time.Now()
    var builder strings.Builder
    builder.Grow(n) // 预分配容量
    for i := 0; i < n; i++ {
        builder.WriteString("a")
    }
    result2 := builder.String()
    duration2 := time.Since(start)

    // 方法3: 使用 bytes.Buffer
    start = time.Now()
    var buffer bytes.Buffer
    buffer.Grow(n) // 预分配容量
    for i := 0; i < n; i++ {
        buffer.WriteString("a")
    }
    result3 := buffer.String()
    duration3 := time.Since(start)

    // 方法4: 使用 strings.Join
    start = time.Now()
    parts := make([]string, n)
    for i := 0; i < n; i++ {
        parts[i] = "a"
    }
    result4 := strings.Join(parts, "")
    duration4 := time.Since(start)

    fmt.Printf("字符串长度: %d\n", n)
    fmt.Printf("+ 操作符: %v (长度: %d)\n", duration1, len(result1))
    fmt.Printf("strings.Builder: %v (长度: %d)\n", duration2, len(result2))
    fmt.Printf("bytes.Buffer: %v (长度: %d)\n", duration3, len(result3))
    fmt.Printf("strings.Join: %v (长度: %d)\n", duration4, len(result4))
    fmt.Println()
}

// 字符串池示例
type StringPool struct {
    pool map[string]string
}

func NewStringPool() *StringPool {
    return &StringPool{
        pool: make(map[string]string),
    }
}

func (sp *StringPool) Intern(s string) string {
    if interned, exists := sp.pool[s]; exists {
        return interned
    }
    sp.pool[s] = s
    return s
}

func (sp *StringPool) Size() int {
    return len(sp.pool)
}

func main() {
    // 性能比较
    fmt.Println("字符串构建性能比较:")
    benchmarkStringConcat(1000)
    benchmarkStringConcat(10000)

    // 字符串池示例
    fmt.Println("字符串池示例:")
    pool := NewStringPool()

    // 模拟重复字符串
    strings1 := []string{"hello", "world", "hello", "go", "world", "hello"}

    fmt.Printf("原始字符串数量: %d\n", len(strings1))

    var internedStrings []string
    for _, s := range strings1 {
        internedStrings = append(internedStrings, pool.Intern(s))
    }

    fmt.Printf("池中唯一字符串数量: %d\n", pool.Size())

    // 验证字符串是否是同一个实例
    s1 := pool.Intern("test")
    s2 := pool.Intern("test")
    fmt.Printf("字符串实例相同: %t\n", &s1 == &s2)

    // 内存使用优化示例
    fmt.Println("\n内存使用优化:")

    // 避免不必要的字符串复制
    largeString := strings.Repeat("abcdefghijklmnopqrstuvwxyz", 1000)

    // 错误的做法:会复制整个字符串
    // substring := largeString[0:10]

    // 正确的做法:只复制需要的部分
    substring := string([]byte(largeString[0:10]))

    fmt.Printf("大字符串长度: %d\n", len(largeString))
    fmt.Printf("子字符串长度: %d\n", len(substring))

    // 字符串比较优化
    str1 := "这是一个很长的字符串用于测试比较性能"
    str2 := "这是一个很长的字符串用于测试比较性能"
    str3 := "这是另一个字符串"

    // 长度检查优化
    if len(str1) != len(str3) {
        fmt.Println("长度不同,无需进一步比较")
    } else if str1 == str3 {
        fmt.Println("字符串相同")
    } else {
        fmt.Println("字符串不同")
    }

    fmt.Printf("str1 == str2: %t\n", str1 == str2)
}

2. 字符串处理最佳实践 #

package main

import (
    "fmt"
    "strings"
    "unicode"
    "unicode/utf8"
)

// 高效的字符串处理工具
type StringUtils struct{}

// 安全的字符串截取(考虑UTF-8)
func (su StringUtils) SafeSubstring(s string, start, length int) string {
    if start < 0 || length <= 0 {
        return ""
    }

    runes := []rune(s)
    if start >= len(runes) {
        return ""
    }

    end := start + length
    if end > len(runes) {
        end = len(runes)
    }

    return string(runes[start:end])
}

// 高效的字符串反转(考虑UTF-8)
func (su StringUtils) Reverse(s string) string {
    runes := []rune(s)
    for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
        runes[i], runes[j] = runes[j], runes[i]
    }
    return string(runes)
}

// 检查字符串是否为回文
func (su StringUtils) IsPalindrome(s string) bool {
    // 转换为小写并移除非字母数字字符
    var builder strings.Builder
    for _, r := range s {
        if unicode.IsLetter(r) || unicode.IsDigit(r) {
            builder.WriteRune(unicode.ToLower(r))
        }
    }

    cleaned := builder.String()
    return cleaned == su.Reverse(cleaned)
}

// 计算字符串的显示宽度(考虑中文字符)
func (su StringUtils) DisplayWidth(s string) int {
    width := 0
    for _, r := range s {
        if unicode.Is(unicode.Han, r) {
            width += 2  // 中文字符占2个显示位置
        } else {
            width += 1  // 其他字符占1个显示位置
        }
    }
    return width
}

// 按显示宽度截取字符串
func (su StringUtils) TruncateByWidth(s string, maxWidth int) string {
    if maxWidth <= 0 {
        return ""
    }

    var result strings.Builder
    currentWidth := 0

    for _, r := range s {
        charWidth := 1
        if unicode.Is(unicode.Han, r) {
            charWidth = 2
        }

        if currentWidth+charWidth > maxWidth {
            break
        }

        result.WriteRune(r)
        currentWidth += charWidth
    }

    return result.String()
}

// 智能的单词换行
func (su StringUtils) WordWrap(text string, lineWidth int) []string {
    words := strings.Fields(text)
    if len(words) == 0 {
        return []string{}
    }

    var lines []string
    var currentLine strings.Builder

    for _, word := range words {
        // 检查添加这个单词是否会超出行宽
        testLine := currentLine.String()
        if testLine != "" {
            testLine += " "
        }
        testLine += word

        if su.DisplayWidth(testLine) <= lineWidth {
            if currentLine.Len() > 0 {
                currentLine.WriteString(" ")
            }
            currentLine.WriteString(word)
        } else {
            // 当前行已满,开始新行
            if currentLine.Len() > 0 {
                lines = append(lines, currentLine.String())
                currentLine.Reset()
            }
            currentLine.WriteString(word)
        }
    }

    if currentLine.Len() > 0 {
        lines = append(lines, currentLine.String())
    }

    return lines
}

// 字符串相似度计算(简单版本)
func (su StringUtils) LevenshteinDistance(s1, s2 string) int {
    r1, r2 := []rune(s1), []rune(s2)
    len1, len2 := len(r1), len(r2)

    // 创建距离矩阵
    matrix := make([][]int, len1+1)
    for i := range matrix {
        matrix[i] = make([]int, len2+1)
    }

    // 初始化第一行和第一列
    for i := 0; i <= len1; i++ {
        matrix[i][0] = i
    }
    for j := 0; j <= len2; j++ {
        matrix[0][j] = j
    }

    // 填充矩阵
    for i := 1; i <= len1; i++ {
        for j := 1; j <= len2; j++ {
            cost := 0
            if r1[i-1] != r2[j-1] {
                cost = 1
            }

            matrix[i][j] = min(
                matrix[i-1][j]+1,      // 删除
                matrix[i][j-1]+1,      // 插入
                matrix[i-1][j-1]+cost, // 替换
            )
        }
    }

    return matrix[len1][len2]
}

func min(a, b, c int) int {
    if a < b && a < c {
        return a
    }
    if b < c {
        return b
    }
    return c
}

func main() {
    utils := StringUtils{}

    // 安全的字符串截取
    text := "Hello, 世界! 这是一个测试字符串。"
    fmt.Printf("原始字符串: %s\n", text)
    fmt.Printf("安全截取(2,5): %s\n", utils.SafeSubstring(text, 2, 5))

    // 字符串反转
    fmt.Printf("反转字符串: %s\n", utils.Reverse(text))

    // 回文检查
    palindromes := []string{"A man a plan a canal Panama", "race a car", "hello"}
    for _, s := range palindromes {
        fmt.Printf("'%s' 是回文: %t\n", s, utils.IsPalindrome(s))
    }

    // 显示宽度计算
    mixedText := "Hello世界123"
    fmt.Printf("'%s' 显示宽度: %d\n", mixedText, utils.DisplayWidth(mixedText))

    // 按宽度截取
    truncated := utils.TruncateByWidth(mixedText, 8)
    fmt.Printf("按宽度截取(8): %s\n", truncated)

    // 单词换行
    longText := "这是一个很长的文本,需要进行换行处理,以便在指定的宽度内显示。"
    lines := utils.WordWrap(longText, 20)
    fmt.Println("单词换行结果:")
    for i, line := range lines {
        fmt.Printf("第%d行: %s (宽度: %d)\n", i+1, line, utils.DisplayWidth(line))
    }

    // 字符串相似度
    str1 := "kitten"
    str2 := "sitting"
    distance := utils.LevenshteinDistance(str1, str2)
    fmt.Printf("'%s' 和 '%s' 的编辑距离: %d\n", str1, str2, distance)

    // UTF-8 处理示例
    fmt.Println("\nUTF-8 处理示例:")
    utf8Text := "Go语言🚀"
    fmt.Printf("字符串: %s\n", utf8Text)
    fmt.Printf("字节长度: %d\n", len(utf8Text))
    fmt.Printf("字符数量: %d\n", utf8.RuneCountInString(utf8Text))
    fmt.Printf("是否有效UTF-8: %t\n", utf8.ValidString(utf8Text))

    // 逐字符处理
    fmt.Print("逐字符: ")
    for i, r := range utf8Text {
        fmt.Printf("[%d:%c] ", i, r)
    }
    fmt.Println()
}

实际应用示例 #

文本处理系统 #

package main

import (
    "fmt"
    "regexp"
    "strings"
    "unicode"
)

// 文本分析器
type TextAnalyzer struct {
    wordRegex     *regexp.Regexp
    sentenceRegex *regexp.Regexp
    emailRegex    *regexp.Regexp
    urlRegex      *regexp.Regexp
}

func NewTextAnalyzer() *TextAnalyzer {
    return &TextAnalyzer{
        wordRegex:     regexp.MustCompile(`\b\w+\b`),
        sentenceRegex: regexp.MustCompile(`[.!?]+`),
        emailRegex:    regexp.MustCompile(`\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b`),
        urlRegex:      regexp.MustCompile(`https?://[^\s]+`),
    }
}

// 文本统计信息
type TextStats struct {
    CharCount     int
    WordCount     int
    SentenceCount int
    ParagraphCount int
    EmailCount    int
    URLCount      int
    AvgWordsPerSentence float64
    ReadingTime   int // 分钟
}

func (ta *TextAnalyzer) Analyze(text string) TextStats {
    stats := TextStats{}

    // 字符统计
    stats.CharCount = len([]rune(text))

    // 单词统计
    words := ta.wordRegex.FindAllString(text, -1)
    stats.WordCount = len(words)

    // 句子统计
    sentences := ta.sentenceRegex.FindAllString(text, -1)
    stats.SentenceCount = len(sentences)
    if stats.SentenceCount == 0 {
        stats.SentenceCount = 1 // 至少有一个句子
    }

    // 段落统计
    paragraphs := strings.Split(strings.TrimSpace(text), "\n\n")
    stats.ParagraphCount = len(paragraphs)

    // 邮箱统计
    emails := ta.emailRegex.FindAllString(text, -1)
    stats.EmailCount = len(emails)

    // URL统计
    urls := ta.urlRegex.FindAllString(text, -1)
    stats.URLCount = len(urls)

    // 平均每句话的单词数
    stats.AvgWordsPerSentence = float64(stats.WordCount) / float64(stats.SentenceCount)

    // 阅读时间估算(假设每分钟200个单词)
    stats.ReadingTime = (stats.WordCount + 199) / 200
    if stats.ReadingTime == 0 {
        stats.ReadingTime = 1
    }

    return stats
}

// 文本清理器
type TextCleaner struct {
    htmlRegex       *regexp.Regexp
    whitespaceRegex *regexp.Regexp
    punctRegex      *regexp.Regexp
}

func NewTextCleaner() *TextCleaner {
    return &TextCleaner{
        htmlRegex:       regexp.MustCompile(`<[^>]*>`),
        whitespaceRegex: regexp.MustCompile(`\s+`),
        punctRegex:      regexp.MustCompile(`[^\w\s\u4e00-\u9fff]`),
    }
}

func (tc *TextCleaner) RemoveHTML(text string) string {
    return tc.htmlRegex.ReplaceAllString(text, "")
}

func (tc *TextCleaner) NormalizeWhitespace(text string) string {
    return strings.TrimSpace(tc.whitespaceRegex.ReplaceAllString(text, " "))
}

func (tc *TextCleaner) RemovePunctuation(text string) string {
    return tc.punctRegex.ReplaceAllString(text, "")
}

func (tc *TextCleaner) ToLowerCase(text string) string {
    return strings.ToLower(text)
}

func (tc *TextCleaner) CleanText(text string, options map[string]bool) string {
    result := text

    if options["removeHTML"] {
        result = tc.RemoveHTML(result)
    }

    if options["normalizeWhitespace"] {
        result = tc.NormalizeWhitespace(result)
    }

    if options["removePunctuation"] {
        result = tc.RemovePunctuation(result)
    }

    if options["toLowerCase"] {
        result = tc.ToLowerCase(result)
    }

    return result
}

// 关键词提取器(简单版本)
type KeywordExtractor struct {
    stopWords map[string]bool
}

func NewKeywordExtractor() *KeywordExtractor {
    stopWords := map[string]bool{
        "的": true, "了": true, "在": true, "是": true, "我": true,
        "有": true, "和": true, "就": true, "不": true, "人": true,
        "都": true, "一": true, "一个": true, "上": true, "也": true,
        "很": true, "到": true, "说": true, "要": true, "去": true,
        "你": true, "会": true, "着": true, "没有": true, "看": true,
        "好": true, "自己": true, "这": true, "那": true, "里": true,
        "就是": true, "还": true, "把": true, "比": true, "从": true,
    }

    return &KeywordExtractor{stopWords: stopWords}
}

func (ke *KeywordExtractor) ExtractKeywords(text string, topN int) []string {
    // 简单的词频统计
    words := strings.Fields(strings.ToLower(text))
    wordCount := make(map[string]int)

    for _, word := range words {
        // 过滤停用词和短词
        if len([]rune(word)) < 2 || ke.stopWords[word] {
            continue
        }

        // 移除标点符号
        cleanWord := strings.Trim(word, ".,!?;:\"'()[]{},。!?;:""''()【】")
        if cleanWord != "" {
            wordCount[cleanWord]++
        }
    }

    // 按频率排序
    type wordFreq struct {
        word  string
        count int
    }

    var frequencies []wordFreq
    for word, count := range wordCount {
        frequencies = append(frequencies, wordFreq{word, count})
    }

    // 简单排序(冒泡排序)
    for i := 0; i < len(frequencies)-1; i++ {
        for j := 0; j < len(frequencies)-i-1; j++ {
            if frequencies[j].count < frequencies[j+1].count {
                frequencies[j], frequencies[j+1] = frequencies[j+1], frequencies[j]
            }
        }
    }

    // 返回前N个关键词
    var keywords []string
    limit := topN
    if limit > len(frequencies) {
        limit = len(frequencies)
    }

    for i := 0; i < limit; i++ {
        keywords = append(keywords, frequencies[i].word)
    }

    return keywords
}

func main() {
    // 示例文本
    sampleText := `
    <h1>Go语言简介</h1>
    <p>Go语言是Google开发的一种静态强类型、编译型语言。Go语言语法与C相近,但功能上有:内存安全,GC(垃圾回收),结构形态及CSP-style并发计算。</p>

    <p>Go的语法接近C语言,但对于变量的声明有所不同。Go支持垃圾回收功能。Go的并行模型是以东尼·霍尔的通信顺序进程(CSP)为基础,采取类似模型的其他语言包括Occam和Limbo。</p>

    <p>联系我们:[email protected] 或访问 https://golang.org 了解更多信息。</p>
    `

    // 创建分析器和清理器
    analyzer := NewTextAnalyzer()
    cleaner := NewTextCleaner()
    extractor := NewKeywordExtractor()

    fmt.Println("=== 原始文本 ===")
    fmt.Println(sampleText)

    // 文本分析
    fmt.Println("\n=== 文本统计 ===")
    stats := analyzer.Analyze(sampleText)
    fmt.Printf("字符数: %d\n", stats.CharCount)
    fmt.Printf("单词数: %d\n", stats.WordCount)
    fmt.Printf("句子数: %d\n", stats.SentenceCount)
    fmt.Printf("段落数: %d\n", stats.ParagraphCount)
    fmt.Printf("邮箱数: %d\n", stats.EmailCount)
    fmt.Printf("URL数: %d\n", stats.URLCount)
    fmt.Printf("平均每句单词数: %.1f\n", stats.AvgWordsPerSentence)
    fmt.Printf("预估阅读时间: %d分钟\n", stats.ReadingTime)

    // 文本清理
    fmt.Println("\n=== 文本清理 ===")
    cleanOptions := map[string]bool{
        "removeHTML":          true,
        "normalizeWhitespace": true,
        "removePunctuation":   false,
        "toLowerCase":         false,
    }

    cleanedText := cleaner.CleanText(sampleText, cleanOptions)
    fmt.Printf("清理后文本:\n%s\n", cleanedText)

    // 关键词提取
    fmt.Println("\n=== 关键词提取 ===")
    keywords := extractor.ExtractKeywords(cleanedText, 10)
    fmt.Printf("前10个关键词: %v\n", keywords)

    // 文本处理管道示例
    fmt.Println("\n=== 文本处理管道 ===")
    pipeline := func(text string) string {
        // 步骤1: 移除HTML
        step1 := cleaner.RemoveHTML(text)
        fmt.Printf("步骤1 - 移除HTML: 完成\n")

        // 步骤2: 规范化空白
        step2 := cleaner.NormalizeWhitespace(step1)
        fmt.Printf("步骤2 - 规范化空白: 完成\n")

        // 步骤3: 提取关键信息
        emails := analyzer.emailRegex.FindAllString(step2, -1)
        urls := analyzer.urlRegex.FindAllString(step2, -1)
        fmt.Printf("步骤3 - 提取邮箱: %v\n", emails)
        fmt.Printf("步骤3 - 提取URL: %v\n", urls)

        return step2
    }

    processedText := pipeline(sampleText)
    fmt.Printf("最终处理结果:\n%s\n", processedText)
}

小结 #

本节详细介绍了 Go 语言的字符串处理,主要内容包括:

字符串基础 #

  • 内部表示:UTF-8 编码,不可变性
  • 字符串和字节:[]byte 转换,Unicode 处理
  • 字符类型:byte 和 rune 的区别

字符串操作 #

  • 连接方法:+, fmt.Sprintf, strings.Join, strings.Builder
  • 查找替换:Contains, Index, Replace, ReplaceAll
  • 分割连接:Split, Join, Fields
  • 修剪格式化:Trim, ToUpper, ToLower

高级特性 #

  • 正则表达式:模式匹配,文本处理
  • 格式化:fmt 包,自定义格式化
  • 性能优化:strings.Builder, 字符串池

最佳实践 #

  • 使用 strings.Builder 进行大量字符串连接
  • 注意 UTF-8 字符处理
  • 合理使用正则表达式
  • 考虑字符串的显示宽度

字符串处理是 Go 语言编程的重要技能,掌握这些知识为后续的文本处理、Web 开发等应用奠定了基础。


练习题:

  1. 实现一个文本统计工具,统计字符、单词、行数等信息
  2. 编写一个简单的模板引擎,支持变量替换
  3. 创建一个文本清理工具,去除 HTML 标签和特殊字符
  4. 实现一个简单的搜索引擎,支持关键词高亮
  5. 编写一个日志解析器,从日志文件中提取有用信息