Common Go Mistakes(IV 字符串)

导航

  • [常见的 Go 错误](#常见的 Go 错误)
    • 引言
    • [Ⅳ 字符串](#Ⅳ 字符串)
      • [36. 没有理解 rune](#36. 没有理解 rune)
      • [37. 错误的遍历字符串](#37. 错误的遍历字符串)
      • [38. 误用 Trim 函数](#38. 误用 Trim 函数)
      • [39. 低效的字符串连接](#39. 低效的字符串连接)
      • [40. 无用的字符串转换](#40. 无用的字符串转换)
      • [41. 子字符串和内存泄漏](#41. 子字符串和内存泄漏)

常见的 Go 错误

参考 100go

引言

  • GO 语言中的 rune
  • 避免低效的字符串拼接

Ⅳ 字符串

36. 没有理解 rune

什么是rune?

  • rune 是 Go 语言中表示 Unicode 字符的类型。
  • 它是 int32 的别名,用于表示一个 Unicode 码点(code point)。
  • 一个 rune 可以表示任何 Unicode 字符,包括中文、日文、表情符号等。

len(s) 返回字节数,非字符数。

go 复制代码
package main

import "fmt"

func main() {
	s := "hello"
	fmt.Println(len(s))	// 5

	s = "汉"
	fmt.Println(len(s))	// 3

	s = string([]byte{0xE6, 0xB1, 0x89})
	fmt.Printf("%s\n", s)	// 汉
}

37. 错误的遍历字符串

在 Go 语言中,不应该使用基于索引的传统 for 循环来遍历字符串,因为这样遍历的是"字节"而不是"字符"。例如:

go 复制代码
package main

import "fmt"

func main() {
        s := "hêllo"
        for i := range s {
                fmt.Printf("position %d: %c\n", i, s[i])
        }
        fmt.Printf("len=%d\n", len(s))	// 6

        for i, r := range s {
                fmt.Printf("position %d: %c\n", i, r)
        }

        runes := []rune(s)
        for i, r := range runes {
                fmt.Printf("position %d: %c\n", i, r)
        }

        s2 := "hello"
        fmt.Printf("%c\n", rune(s2[4]))
}
问题 错误做法 正确做法
遍历字符串 for i := 0; i < len(s); i++ for _, r := range s
获取字符数 len(s) utf8.RuneCountInString(s)len([]rune(s))
字符串切片 s[3:6](字节切片) string([]rune(s)[2:4])(rune 切片)

38. 误用 Trim 函数

strings.Trim 系列函数移除的是字符集,而不是字符串前缀/后缀。例如:

go 复制代码
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println(strings.TrimRight("123oxo", "xo"))	// 123

	fmt.Println(strings.TrimSuffix("123oxo", "xo"))	// 123o

	fmt.Println(strings.TrimLeft("oxo123", "ox"))	// 123
	fmt.Println(strings.TrimPrefix("oxo123", "ox"))	// o123

	fmt.Println(strings.Trim("oxo123oxo", "ox"))	// 123
}
函数 作用
strings.Trim(s, cutset string) s开头和结尾 删除 cutset 中的字符。
strings.TrimLeft(s, cutset string) s开头 删除 cutset 中的字符。
strings.TrimRight(s, cutset string) s结尾 删除 cutset 中的字符。
strings.TrimSpace(s string) s开头和结尾 删除 所有空白字符 (如 \t, \n, \v, \f, \r, )。
strings.TrimPrefix(s, prefix string) s开头 删除 prefix 字符串。
strings.TrimSuffix(s, suffix string) s结尾 删除 suffix 字符串。

39. 低效的字符串连接

Go 语言中的字符串是不可变的(immutable),每次使用 + 连接字符串都会创建一个新的字符串对象。因此,频繁的字符串连接可能会导致内存分配和复制开销。

当有很多的字符串要进行拼接时,建议使用标准库中的 strings.Builder :

go 复制代码
package main

import "strings"

func concat1(values []string) string {
	s := ""
	for _, value := range values {
		s += value
	}
	return s
}

func concat2(values []string) string {
	sb := strings.Builder{}
	for _, value := range values {
		_, _ = sb.WriteString(value)
	}
	return sb.String()
}

func concat3(values []string) string {
	total := 0
	for i := 0; i < len(values); i++ {
		total += len(values[i])
	}

	sb := strings.Builder{}
	sb.Grow(total)
	for _, value := range values {
		_, _ = sb.WriteString(value)
	}
	return sb.String()
}

40. 无用的字符串转换

许多开发者在处理 I/O 时,会不断地在 string 和 \[\]byte 之间来回转换。会严重降低程序性能,并浪费内存。

bytes 包提供了一些和 strings 包相似的操作,可以帮助避免 \[\]byte/string 之间的转换。例如:

go 复制代码
package main

import (
	"bytes"
	"io"
	"strings"
)

func getBytes1(reader io.Reader) ([]byte, error) {
	b, err := io.ReadAll(reader)
	if err != nil {
		return nil, err
	}
	return []byte(sanitize1(string(b))), nil
}

func sanitize1(s string) string {
	return strings.TrimSpace(s)
}

func getBytes2(reader io.Reader) ([]byte, error) {
	b, err := io.ReadAll(reader)
	if err != nil {
		return nil, err
	}
	return sanitize2(b), nil
}

func sanitize2(b []byte) []byte {
	return bytes.TrimSpace(b)
}

41. 子字符串和内存泄漏

Go 中的子串操作(如 s[start:end])会共享原始字符串的底层数组,而不是创建新的副本。这就会导致:

  • 子串与原始字符串共享相同的内存空间
  • 如果子串被长期持有,原始字符串的内存无法被垃圾回收(GC)

解决方案:

  • 使用 string() 强制复制
go 复制代码
s := readLargeFile()
sub := string([]byte(s[:10]))  // 强制复制,断开引用
  • 使用 strings.Clone
go 复制代码
s := readLargeFile()
sub := strings.Clone(s[:10])  // 复制子串,断开引用

性能对比:

方法 内存分配 性能 适用场景
s[:10] 最快 短期使用子串
string([]byte(s[:10])) 较慢 长期持有子串
strings.Clone(s[:10]) Go 1.18+,长期持有子串
相关推荐
萧瑟余晖9 分钟前
JDK 26 新特性详解
java·开发语言
马优晨43 分钟前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
weixin_446260852 小时前
HACO:面向动态部署环境的对冲式智能计算可靠多智能体调度框架
后端·python·flask
人邮异步社区3 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大3 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai3 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
用户8356290780513 小时前
Python 实现 Excel 页面布局与打印设置自动化
后端·python
用户9931441579843 小时前
微服务框架中获取用户信息
后端
এ慕ོ冬℘゜3 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript