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+,长期持有子串
相关推荐
凤山老林5 分钟前
复杂检索引擎落地:Spring Boot + Elasticsearch 数据同步与高阶查询实战
spring boot·后端·elasticsearch
峥嵘life7 小时前
Android16 311Y3 EAP-TLS 网络连接失败分析与修复总结
android·开发语言·人工智能·php
笃行3508 小时前
SQLServer数据迁移之后,那张报表还能不能秒出
后端
LucianaiB8 小时前
别再被 AI 骗了:我把腾讯云 4 个 Skill 做成了个「AI 打假侦探」,过程的一个小配置坑惨了我
后端
candyTong9 小时前
Claude Code 如何恢复一段会话
后端·架构·ai编程
mqiqe10 小时前
AgentScope Java Harness:4. 双层记忆系统 让 Agent 拥有真正的“长期大脑“
java·开发语言
gugucoding10 小时前
46. 【Java】JUC并发工具:让并发更简单
java·开发语言
En^_^Joy10 小时前
Django项目配置全攻略:settings配置文件
后端·python·django
Nebula_g11 小时前
JavaSE基础语法:面向对象高级(代码中的成分)
java·开发语言·编程·javase·技术栈·高级语法
for_ever_love__11 小时前
python基础语法学习: 异常的传递性
开发语言·python·学习