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 开发等应用奠定了基础。
练习题:
- 实现一个文本统计工具,统计字符、单词、行数等信息
- 编写一个简单的模板引擎,支持变量替换
- 创建一个文本清理工具,去除 HTML 标签和特殊字符
- 实现一个简单的搜索引擎,支持关键词高亮
- 编写一个日志解析器,从日志文件中提取有用信息