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+,长期持有子串
相关推荐
【建模先锋】2 小时前
基于Python的智能故障诊断系统 | SmartDiag AI (基础版)V1.0 正式发布!
开发语言·人工智能·python·故障诊断·智能分析平台·大数据分析平台·智能故障诊断系统
我命由我123452 小时前
微信小程序 - 避免在 data 初始化中引用全局变量
开发语言·前端·javascript·微信小程序·小程序·前端框架·js
盒马盒马2 小时前
Rust:Trait 标签 & 常见特征
开发语言·后端·rust
liulilittle2 小时前
C++ SSE/AVX/SHA/AES指令集检查,用于程序定向优化。
开发语言·c++·cpu·asm·detect·il·features
小龙在山东2 小时前
基于C++空项目运行汇编语言
开发语言·c++
MM_MS2 小时前
WinForm+C#小案例--->写一个记事本程序
开发语言·计算机视觉·c#·visual studio
韩立学长2 小时前
基于Springboot儿童福利院规划管理系统o292y1v8(程序、源码、数据库、调试部署方案及开发环境)系统界面展示及获取方式置于文档末尾,可供参考。
数据库·spring boot·后端
y1y1z2 小时前
Spring国际化
java·后端·spring
郝学胜-神的一滴2 小时前
Linux信号屏蔽字详解:原理、应用与实践
linux·服务器·开发语言·c++·程序人生