go语言:string字符串 作者:马育民 • 2026-10-05 09:38 • 阅读:10000 # 介绍 Go 的字符串(`string`)是**原生类型**,底层是**只读字节切片**,默认采用 UTF-8 编码。 **注意:**字符串不可修改,存储的是字节序列,不是字符。 # 1. 定义 ```go var s string = "hello" s2 := `这是原始字符串,可以换行,不转义\n` ``` 两种字面量: 1. **双引号 `" "`**:解析转义字符 `\n \t \"`,不能直接换行 2. **反引号 `` ` ``**:原始字符串,原样输出,不转义,支持多行 ### 底层结构(runtime/string.go) ```go type string struct { ptr unsafe.Pointer // 指向字节数组的指针 len int // 字节长度 } ``` - string 本身是一个结构体值,赋值时拷贝 `ptr` 和 `len`,底层字节数组**共享** - **底层字节数组只读**:不能 `s[0] = 'a'`,编译报错 # 2. 字节 vs 字符 Go string 存**字节**。 - 英文字母:UTF-8 占 1 字节 - 中文汉字:UTF-8 占 3 字节 ```go s := "Go语言" fmt.Println(len(s)) // 输出 5:G(1)+o(1)+语(3)+言(3) = 8 ``` `len(s)` 返回**字节长度**,不是字符个数。 ### rune字符 `rune`:Go 的字符类型,`int32` 别名,代表一个 Unicode 码点。 把字符串转 rune 切片获取字符数量: ```go r := []rune(s) fmt.Println(len(r)) // 4,一共4个字符 ``` ### 遍历字符串两种方式 1. **普通 for 循环(按字节索引)** ```go s := "语言" for i := 0; i < len(s); i++ { fmt.Printf("%d: %c\n", i, s[i]) // s[i] 是 byte,字节 } ``` 中文会输出乱码,因为索引定位到字节,不是完整字符。 2. **for range(按 Unicode 码点遍历,推荐)** ```go s := "语言" for idx, r := range s { fmt.Printf("字符位置%d, rune:%c\n", idx, r) } ``` > range 返回的 `idx` 是**字节偏移量**,不是字符序号。 # 3. 字符串不可变性 ```go s := "abc" s[0] = 'x' // ❌ 编译错误:string is immutable ``` 想要修改字符串:转成 `[]byte` 修改,再转回 string ```go s := "abc" b := []byte(s) b[0] = 'x' s = string(b) ``` > 注意:转换会**拷贝底层数据**,产生新内存。 # 4. 常用操作 ### 4.1 拼接 1. `+` 最简单,少量拼接用;每次 `+` 生成新字符串,大量拼接性能差 ```go a := "hello" + " " + "world" ``` 2. `strings.Builder` ✅ **大量拼接首选**,内部维护字节切片,减少内存拷贝 ```go var sb strings.Builder sb.WriteString("hello") sb.WriteString(" world") res := sb.String() ``` > 重要:`strings.Builder` 不要拷贝,一旦拷贝底层共享数组会 panic。 3. `fmt.Sprintf`:格式化拼接,性能一般,适合带格式化场景 ```go s := fmt.Sprintf("name=%s, age=%d", "张三", 20) ``` 4. `strings.Join`:字符串切片拼接 ```go arr := []string{"a", "b", "c"} res := strings.Join(arr, ",") // "a,b,c" ``` ### 4.2 字符串包 strings(标准库) ```go import "strings" ``` 常用函数: - `strings.Contains(s, substr string) bool` 是否包含子串 - `strings.HasPrefix(s, prefix string) bool` 前缀 - `strings.HasSuffix(s, suffix string) bool` 后缀 - `strings.Index(s, substr string) int` 返回子串起始字节索引,找不到返回-1 - `strings.LastIndex` 最后出现位置 - `strings.Split(s, sep string) []string` 分割 - `strings.ReplaceAll(s, old, new string) string` 全部替换 - `strings.ToUpper / ToLower` 大小写转换 - `strings.TrimSpace(s string)` 去除首尾空白(空格、换行、tab) - `strings.Trim(s, cutset string)` 去除首尾指定字符集 ### 4.3 类型转换 1. string ↔ []byte ```go s := "test" b := []byte(s) // string -> []byte,拷贝字节 s2 := string(b) // []byte -> string,拷贝字节 ``` 2. string ↔ []rune ```go r := []rune("中文") s := string(r) ``` ### 4.4 子串切片 字符串切片 `s[low:high]`,切的是**字节区间**,不是字符! ```go s := "Go语言" sub := s[0:2] // "Go" sub2 := s[2:5] // "语"(3字节) ``` ⚠️ 如果切在中文UTF-8字节中间,得到的子串是**无效UTF-8**,会出现乱码。 # 5. 内存与性能关键点 1. 字符串比较 `==`:直接比较底层字节,高效;区分大小写 2. 字符串切片 `s[a:b]`:**不拷贝底层字节**,只创建新 string 结构体(ptr+len),共享原字节数组。 > 坑:如果原字符串很大,切一个很短子串保留引用,大内存不会被GC释放。 示例: ```go big := strings.Repeat("a", 1024*1024) // 1MB字符串 small := big[:5] // small 共享big底层数组,big内存无法GC ``` 解决:需要独立内存时,转 `[]byte` 再转回 string,强制拷贝: ```go small := string([]byte(big[:5])) ``` 3. 空字符串:`""`,底层 `ptr=nil, len=0`,占用极少内存 4. 字符串常量:编译期确定,放在只读内存段 # 6. UTF-8 相关标准库 `unicode` / `unicode/utf8` ```go import "unicode/utf8" utf8.RuneCountInString(s) // 获取字符数量(等价 len([]rune(s)),但不创建切片,性能更好) utf8.DecodeRuneInString(s) // 读取第一个rune和字节长度 ``` # 7. 常见坑总结 1. `len(s)` 是字节数,不是汉字/字符数 2. `s[i]` 取的是 byte,不是 rune;中文不能用索引直接取字符 3. `s[low:high]` 按字节切片,截断在多字节字符中间 → 乱码 4. string 不可变,`s+` 大量拼接会频繁分配内存,优先 `strings.Builder` 5. 字符串切片共享底层数组,长字符串截取短子串会内存泄漏 6. range 遍历得到的 index 是字节偏移,不是字符序号 # 8. 例子 ```go package main import ( "fmt" "strings" "unicode/utf8" ) func main() { s := "Go语言" fmt.Println("字节长度:", len(s)) fmt.Println("字符数量:", utf8.RuneCountInString(s)) // range遍历 for offset, r := range s { fmt.Printf("字节偏移:%d, 字符:%c\n", offset, r) } // 拼接 var sb strings.Builder sb.WriteString("hello") sb.WriteString(" 世界") fmt.Println(sb.String()) // 判断包含 fmt.Println(strings.Contains(s, "语言")) } ``` 如果你需要,我可以单独整理一份 **Go字符串面试题清单** 或者 **strings包完整API速查表**。 原文出处:/show_1GW4AeNJoixJ.html