Go 字符串

Go 中字符串是不可变的 UTF-8 编码字节序列,理解底层字节与 Unicode 码点的关系是正确处理文本的关键。

#type / concept #status / growing #tech / dev #resource / go

[!info] related notes

Go 字符串

一句话定义

Go 的 string 是一个不可变的 UTF-8 编码字节序列,底层结构为一个指向字节数组的指针和一个长度字段。

核心机制 / 工作原理

  1. 底层结构string 本质是一个 reflect.StringHeader,包含 Data(指向底层字节数组)和 Len(字节长度)。赋值和传参仅拷贝这个 16 字节的 header,不拷贝底层数据。

  2. 不可变性string 的底层字节数组一旦创建就不能修改。s[0] = 'x' 会编译报错。任何对字符串的”修改”都会产生新的字符串。

  3. UTF-8 编码:Go 源码和字符串默认使用 UTF-8。ASCII 字符占 1 字节,中文等多字节字符占 3-4 字节。

  4. rune 类型runeint32 的别名,表示一个 Unicode 码点。遍历字符串时需要用 range 才能正确按 rune 迭代。

  5. len() vs utf8.RuneCountInString()len(s) 返回字节数,utf8.RuneCountInString(s) 返回 rune(字符)数。对含中文的字符串两者不同。

  6. string[]byte 转换[]byte(s)string(b) 都会复制底层数据(除非编译器优化)。高频转换应尽量避免,可用 strings.Builderunsafe 零拷贝。

最小例子 / 最小场景

package main

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

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

	// len 返回字节数, RuneCountInString 返回字符数
	fmt.Println(len(s))                        // 13 (7 ASCII + 两个中文各 3 字节)
	fmt.Println(utf8.RuneCountInString(s))     // 9

	// 按字节索引 — 拿到的是 byte
	fmt.Printf("%x\n", s[7:10]) // e4b896 ('世' 的 UTF-8 编码)

	// range 按 rune 迭代
	for i, r := range s {
		fmt.Printf("index=%d, rune=%c, codepoint=%U\n", i, r, r)
	}

	// strings 包常用函数
	fmt.Println(strings.Contains(s, "世界"))       // true
	fmt.Println(strings.HasPrefix(s, "Hello"))    // true
	fmt.Println(strings.Split("a,b,c", ","))      // [a b c]
	fmt.Println(strings.Join([]string{"a","b"}, "-")) // a-b
	fmt.Println(strings.TrimSpace("  hi  "))      // hi
	fmt.Println(strings.Replace("aaa", "a", "b", 2)) // bba

	// 字符串构建 — 避免频繁拼接
	var b strings.Builder
	for i := 0; i < 1000; i++ {
		b.WriteString("go")
	}
	result := b.String() // 高效, 内部只扩容一次或几次

	// string ↔ []byte
	bytes := []byte("hello")
	str := string(bytes)
	fmt.Println(bytes, str)
}

为什么重要

  • 文本处理的基础:几乎所有程序都涉及字符串操作,理解 UTF-8 和字节 vs 字符的区别能避免截断乱码等常见 bug。
  • 性能关键:在日志、序列化、模板渲染等热路径中,字符串拼接方式(+ 循环 vs Builder vs fmt.Sprintf)性能差异可达数十倍。
  • 国际化就绪:Go 原生 UTF-8 支持使得处理中日韩等多字节语言自然且正确,但前提是开发者理解 rune 与 byte 的区别。
  • 标准库丰富stringsstrconvunicodeunicode/utf8bytesregexp 等包覆盖了绝大多数文本处理需求。

边界与易混淆点

  • s[i] 拿到的是 byte 不是字符:对 "世界"s[0] 得到 0xe4(“世” 的第一个字节),不是 “世”。要按字符访问需用 []rune(s)range
  • string[]byte 转换有拷贝开销:虽然 Go 编译器对某些场景做了优化(如直接将 []byte 传给 string 类型的 map key),但一般情况下会复制内存。高频场景用 unsafe.String / unsafe.Slice 做零拷贝需谨慎。
  • strconv vs fmt.Sprintfstrconv.Itoa(42)fmt.Sprintf("%d", 42) 快约 5-10 倍,类型转换优先用 strconv
  • 字符串比较是字节级比较"abc" < "abd" 按 UTF-8 字节逐字节比较,不是按 Unicode 排序规则。需要本地化排序应使用 golang.org/x/text/collate
  • strings.Builder 只能用一次Reset() 后可复用,但不能并发写入。并发场景考虑 bytes.Buffer + 锁,或按分区拼接后合并。
创建于 2026/6/25 更新于 2026/6/25