Go 语言字符串
字符串是 Go 语言中最常用的数据类型之一。Go 的字符串是 UTF-8 编码的不可变字节序列,原生支持 Unicode。理解字符串的本质和操作方式,是高效处理文本数据的基础。
字符串的本质
Go 中字符串的本质是不可变的字节序列。一旦创建,字符串的内容不能被修改。
package main
import "fmt"
func main() {
s := "Hello, Go"
// s[0] = 'h' // 编译错误!字符串是不可变的
fmt.Println(s)
fmt.Printf("类型: %T\n", s) // string
}字符串的底层结构包含两个字段:
// runtime 中的简化定义
type StringHeader struct {
Data uintptr // 指向字节数组的指针
Len int // 字节长度
}字符串创建
双引号字面量
双引号包裹的字符串支持转义字符,是最常用的创建方式。
package main
import "fmt"
func main() {
s1 := "Hello, World"
s2 := "Go\n语言" // \n 会被解释为换行符
s3 := "中文\u0041" // \u0041 是 Unicode 转义,表示 'A'
fmt.Println(s1) // Hello, World
fmt.Println(s2) // Go
// 语言
fmt.Println(s3) // 中文A
}反引号原始字符串
反引号(`)包裹的字符串是原始字符串,不支持转义,可以跨行,常用于多行文本和正则表达式。
package main
import "fmt"
func main() {
// 原始字符串,\n 不会被转义
s1 := `Hello\nWorld`
fmt.Println(s1) // Hello\nWorld
// 多行字符串
sql := `SELECT id, name, email
FROM users
WHERE status = 'active'
ORDER BY name`
fmt.Println(sql)
// HTML 模板
html := `<div class="container">
<h1>标题</h1>
<p>这是一段 HTML 代码</p>
</div>`
fmt.Println(html)
}字符串与字节(byte / []byte)
byte 是 uint8 的别名,表示一个字节。字符串可以转换为字节切片,反之亦然。
package main
import "fmt"
func main() {
s := "Hello"
// 字符串转字节切片
b := []byte(s)
fmt.Println(b) // [72 101 108 108 111]
// 字节切片转字符串
s2 := string(b)
fmt.Println(s2) // Hello
// 通过字节切片修改字符串内容
b[0] = 'h'
s3 := string(b)
fmt.Println(s3) // hello
}注意:对于包含中文的字符串,按字节操作可能会截断字符。
package main
import "fmt"
func main() {
s := "你好世界"
b := []byte(s)
fmt.Printf("字节长度: %d\n", len(b)) // 12(每个中文字符 3 字节)
fmt.Printf("字节切片: %v\n", b)
// 按字节截取可能产生乱码
fmt.Println(string(b[:3])) // 你(恰好是完整字符)
fmt.Println(string(b[:2])) // 乱码
}字符串与 Unicode(rune)
rune 是 int32 的别名,表示一个 Unicode 码点。Go 使用 UTF-8 编码,一个中文字符占 3 个字节,但只有 1 个 rune。
package main
import "fmt"
func main() {
s := "Hello世界"
// 转换为 rune 切片
runes := []rune(s)
fmt.Println(runes) // [72 101 108 108 111 19990 30028]
fmt.Println(string(runes[5])) // 世
fmt.Printf("rune 数量: %d\n", len(runes)) // 7
// 单个字符用 rune 表示
ch := '中' // 单引号表示 rune
fmt.Printf("rune: %c, Unicode: %U\n", ch, ch)
}字符串遍历
按字节遍历
package main
import "fmt"
func main() {
s := "Hello"
for i := 0; i < len(s); i++ {
fmt.Printf("字节 %d: %c (%d)\n", i, s[i], s[i])
}
}按字节遍历时,s[i] 返回的是 byte 类型。对于包含多字节字符(如中文)的字符串,这种方式会逐个字节处理,无法正确识别字符边界。
按 rune 遍历(for-range)
package main
import "fmt"
func main() {
s := "Hello世界"
// for-range 自动按 UTF-8 解码,返回 rune
for i, ch := range s {
fmt.Printf("索引 %d: %c (U+%04X)\n", i, ch, ch)
}
}for-range 遍历字符串时,索引 i 是字节偏移量,ch 是 rune 类型。这种方式能正确处理 Unicode 字符。
常用 strings 包函数
Go 的 strings 包提供了丰富的字符串操作函数。
package main
import (
"fmt"
"strings"
)
func main() {
s := "Hello, World! Go is awesome."
// 判断是否包含子串
fmt.Println(strings.Contains(s, "Go")) // true
fmt.Println(strings.Contains(s, "Java")) // false
// 判断是否包含任意字符
fmt.Println(strings.ContainsAny(s, "abc")) // true(包含 'a')
// 分割字符串
parts := strings.Split(s, " ")
fmt.Println(parts) // [Hello, World! Go is awesome.]
// 连接字符串
joined := strings.Join(parts, "-")
fmt.Println(joined) // Hello,-World!-Go-is-awesome.
// 替换
replaced := strings.Replace(s, "Go", "Golang", 1)
fmt.Println(replaced) // Hello, World! Golang is awesome.
// 替换所有
replacedAll := strings.ReplaceAll("aaa", "a", "b")
fmt.Println(replacedAll) // bbb
// 去除首尾空白
trimmed := strings.TrimSpace(" hello ")
fmt.Printf("[%s]\n", trimmed) // [hello]
// 去除首尾指定字符
trimmed2 := strings.Trim("##hello##", "#")
fmt.Println(trimmed2) // hello
// 判断前缀/后缀
fmt.Println(strings.HasPrefix(s, "Hello")) // true
fmt.Println(strings.HasSuffix(s, "awesome.")) // true
// 转大小写
fmt.Println(strings.ToUpper("hello")) // HELLO
fmt.Println(strings.ToLower("HELLO")) // hello
// 统计子串出现次数
fmt.Println(strings.Count("banana", "an")) // 2
// 查找子串首次出现的位置
fmt.Println(strings.Index(s, "World")) // 7
fmt.Println(strings.Index(s, "Java")) // -1
}字符串格式化
fmt.Sprintf 用于格式化字符串,类似 C 语言的 sprintf。
package main
import "fmt"
func main() {
name := "Golang"
version := 1.21
year := 2023
// 常用格式化动词
fmt.Println(fmt.Sprintf("名称: %s", name)) // 名称: Golang
fmt.Println(fmt.Sprintf("版本: %.2f", version)) // 版本: 1.21
fmt.Println(fmt.Sprintf("年份: %d", year)) // 年份: 2023
fmt.Println(fmt.Sprintf("十六进制: %x", 255)) // 十六进制: ff
fmt.Println(fmt.Sprintf("布尔值: %t", true)) // 布尔值: true
fmt.Println(fmt.Sprintf("宽度对齐: [%10s]", name)) // 宽度对齐: [ Golang]
fmt.Println(fmt.Sprintf("左对齐: [%-10s]", name)) // 左对齐: [Golang ]
// 组合使用
result := fmt.Sprintf("[%s] 版本 %.1f 发布于 %d 年", name, version, year)
fmt.Println(result) // [Golang] 版本 1.2 发布于 2023 年
}字符串拼接方式对比
Go 中字符串是不可变的,每次拼接都会创建新字符串。不同拼接方式的性能差异很大。
使用 + 运算符
package main
import (
"fmt"
"time"
)
func main() {
start := time.Now()
s := ""
for i := 0; i < 10000; i++ {
s += "a"
}
fmt.Printf("+ 拼接耗时: %v\n", time.Since(start))
}+ 拼接在循环中性能最差,每次都会分配新内存。
使用 strings.Builder
package main
import (
"fmt"
"strings"
"time"
)
func main() {
start := time.Now()
var builder strings.Builder
// 可选:预分配容量
builder.Grow(10000)
for i := 0; i < 10000; i++ {
builder.WriteString("a")
}
result := builder.String()
fmt.Printf("Builder 拼接耗时: %v, 长度: %d\n", time.Since(start), len(result))
}strings.Builder 是推荐的拼接方式,内部使用可增长的字节缓冲区,性能远优于 +。
bytes.Buffer 与 strings.Builder 性能接近,适用于需要同时处理字节和字符串的场景。当已有字符串切片时,strings.Join 是最简洁高效的拼接方式。
strconv 包常用操作
strconv 包用于字符串与其他基本类型之间的转换。
package main
import (
"fmt"
"strconv"
)
func main() {
// 字符串转整数
n, err := strconv.Atoi("42")
if err != nil {
fmt.Println("转换错误:", err)
}
fmt.Println(n) // 42
// 整数转字符串
s := strconv.Itoa(42)
fmt.Println(s) // "42"
// 字符串转浮点数
f, _ := strconv.ParseFloat("3.14", 64)
fmt.Println(f) // 3.14
// 浮点数转字符串
s3 := strconv.FormatFloat(3.14159, 'f', 2, 64)
fmt.Println(s3) // "3.14"
// 字符串转布尔
b, _ := strconv.ParseBool("true")
fmt.Println(b) // true
// 整数转指定进制字符串
s2 := strconv.FormatInt(255, 16)
fmt.Println(s2) // "ff"
}总结
本篇全面介绍了 Go 语言字符串的核心知识:字符串是不可变的 UTF-8 字节序列,支持双引号字面量和反引号原始字符串两种创建方式。byte 和 rune 分别用于按字节和按 Unicode 字符处理字符串。strings 包提供了丰富的字符串操作函数,fmt.Sprintf 用于格式化,strconv 包处理字符串与基本类型的转换。在拼接大量字符串时,推荐使用 strings.Builder 以获得最佳性能。