Skip to content

1.7 字符串与字节处理 ​

string 不可变性 ​

go
package main

import (
    "fmt"
)

func main() {
    // 字符串是不可变的
    s := "hello"
    // s[0] = 'H'  // 编译错误!不能修改字符串

    // 要"修改"字符串,需要创建新字符串
    s = "H" + s[1:]
    fmt.Println(s)  // Hello

    // 字符串底层是字节数组
    // 字符串的零值是空字符串 ""
    var empty string
    fmt.Println(empty == "")  // true

    // 字符串长度 (字节数)
    fmt.Println(len("hello"))  // 5

    // 中文字符串
    zh := "你好"
    fmt.Println(len(zh))  // 6 (UTF-8 编码,每个中文 3 字节)

    // 获取字符数 (rune 数)
    fmt.Println(len([]rune(zh)))  // 2
}

字符串内存布局 ​

字符串 = 指针 + 长度
- 指针指向底层字节数组
- 长度表示字节数 (不是字符数)

字符串不可变的原因:
1. 安全性:避免意外修改
2. 共享:多个变量可以安全共享同一底层数据
3. 哈希:map 的 key 可以安全使用字符串

strings 包常用函数 ​

go
package main

import (
    "fmt"
    "strings"
)

func main() {
    s := "Hello, World! Go is awesome."

    // === 查找 ===
    // Contains: 是否包含子串
    fmt.Println(strings.Contains(s, "World"))      // true
    fmt.Println(strings.ContainsAny(s, "abc"))     // false

    // HasPrefix/HasSuffix: 前缀/后缀
    fmt.Println(strings.HasPrefix(s, "Hello"))     // true
    fmt.Println(strings.HasSuffix(s, "awesome."))  // true

    // Index: 返回子串位置 (字节索引)
    fmt.Println(strings.Index(s, "World"))  // 7
    fmt.Println(strings.IndexAny(s, "abc")) // -1 (未找到)

    // LastIndex: 最后一次出现的位置
    fmt.Println(strings.LastIndex(s, "o"))  // 24

    // Count: 子串出现次数
    fmt.Println(strings.Count(s, "o"))  // 4

    // === 大小写转换 ===
    fmt.Println(strings.ToLower(s))   // hello, world! go is awesome.
    fmt.Println(strings.ToUpper(s))   // HELLO, WORLD! GO IS AWESOME.
    // strings.Title 已在 Go 1.18 废弃,推荐使用 golang.org/x/text/cases
    // c := cases.Title(language.English)
    // fmt.Println(c.String("hello world"))  // Hello World

    // === 修剪 ===
    fmt.Println(strings.TrimSpace("  hello  "))      // "hello"
    fmt.Println(strings.Trim("!!!hello!!!", "!"))    // "hello"
    fmt.Println(strings.TrimLeft("  hello  ", " "))  // "hello  "
    fmt.Println(strings.TrimRight("  hello  ", " ")) // "  hello"
    fmt.Println(strings.TrimPrefix(s, "Hello, "))    // "World! Go is awesome."
    fmt.Println(strings.TrimSuffix(s, "."))          // "Hello, World! Go is awesome"

    // === 替换 ===
    fmt.Println(strings.Replace(s, "o", "0", 1))    // 只替换 1 个
    fmt.Println(strings.Replace(s, "o", "0", -1))   // 替换所有
    fmt.Println(strings.ReplaceAll(s, "o", "0"))    // 替换所有 (更简洁)

    // === 分割 ===
    parts := strings.Split("a,b,c", ",")
    fmt.Println(parts)  // [a b c]

    // SplitN: 限制分割数量
    parts2 := strings.SplitN("a,b,c,d", ",", 2)
    fmt.Println(parts2)  // [a b,c,d]

    // SplitAfter: 分割后保留分隔符
    parts3 := strings.SplitAfter("a,b,c", ",")
    fmt.Println(parts3)  // [a, b, c]

    // Fields: 按空白字符分割
    fields := strings.Fields("  hello   world  ")
    fmt.Println(fields)  // [hello world]

    // === 连接 ===
    result := strings.Join([]string{"a", "b", "c"}, "-")
    fmt.Println(result)  // a-b-c

    // === 重复 ===
    fmt.Println(strings.Repeat("ab", 3))  // ababab

    // === 填充 ===
    fmt.Println(strings.Repeat(" ", 4) + "hello")  // 左填充 (手动)

    // === 比较 ===
    fmt.Println(strings.EqualFold("Hello", "hello"))  // true (忽略大小写)
    fmt.Println(strings.Compare("a", "b"))            // -1 (字典序)

    // === 其他 ===
    // Map: 对每个字符应用函数
    mapper := func(r rune) rune {
        if r >= 'a' && r <= 'z' {
            return r - 32  // 转大写
        }
        return r
    }
    fmt.Println(strings.Map(mapper, "hello"))  // HELLO
}

bytes 包与字节操作 ​

go
package main

import (
    "bytes"
    "fmt"
)

func main() {
    // bytes 包提供与 strings 类似的功能,但操作的是 []byte

    b := []byte("Hello, World!")

    // === 查找 ===
    fmt.Println(bytes.Contains(b, []byte("World")))    // true
    fmt.Println(bytes.ContainsAny(b, "abc"))           // false
    fmt.Println(bytes.HasPrefix(b, []byte("Hello")))   // true
    fmt.Println(bytes.HasSuffix(b, []byte("!")))       // true
    fmt.Println(bytes.Index(b, []byte("World")))       // 7
    fmt.Println(bytes.IndexAny(b, "abc"))              // -1
    fmt.Println(bytes.LastIndexByte(b, 'o'))           // 22

    // === 替换 ===
    fmt.Println(bytes.ReplaceAll(b, []byte("o"), []byte("0")))

    // === 分割 ===
    parts := bytes.Split([]byte("a,b,c"), []byte(","))
    fmt.Println(parts)  // [[97] [98] [99]]

    // === 连接 ===
    result := bytes.Join([][]byte{[]byte("a"), []byte("b")}, []byte("-"))
    fmt.Println(string(result))  // a-b

    // === Trim ===
    fmt.Println(string(bytes.TrimSpace([]byte("  hello  "))))
    fmt.Println(string(bytes.Trim([]byte("!!!hello!!!"), "!")))

    // === 大小写 ===
    fmt.Println(string(bytes.ToLower([]byte("HELLO"))))
    fmt.Println(string(bytes.ToUpper([]byte("hello"))))
    // bytes.Title 已在 Go 1.18 废弃,推荐使用 golang.org/x/text/cases
    // c := cases.Title(language.English)
    // fmt.Println(c.String("hello world"))  // Hello World
}

bytes.Buffer (可增长的字节缓冲区) ​

go
package main

import (
    "bytes"
    "fmt"
)

func main() {
    // 创建 Buffer
    var buf bytes.Buffer

    // 写入数据
    buf.WriteString("Hello")
    buf.WriteByte(' ')      // 写入单个字节
    buf.WriteRune('世')      // 写入 rune
    buf.Write([]byte("界"))  // 写入字节 slice
    buf.WriteRune('!')

    // 读取
    fmt.Println("长度:", buf.Len())
    fmt.Println("内容:", buf.String())
    fmt.Println("字节:", buf.Bytes())

    // 读取并消耗
    data := buf.Next(5)  // 读取前 5 个字节
    fmt.Println("Next:", string(data))

    // 读取第一个字节
    b, _ := buf.ReadByte()
    fmt.Println("ReadByte:", string(b))

    // 读取到分隔符
    line, _ := buf.ReadBytes('!')
    fmt.Println("ReadBytes:", string(line))

    // 重置缓冲区
    buf.Reset()

    // 预分配容量 (性能优化)
    buf2 := bytes.NewBuffer(make([]byte, 0, 1024))
    buf2.WriteString("预分配 1024 字节容量")
}

bytes.Reader (从字节读取) ​

go
package main

import (
    "bytes"
    "fmt"
    "io"
)

func main() {
    data := []byte("Hello, World!")
    reader := bytes.NewReader(data)

    // 读取
    buf := make([]byte, 5)
    n, _ := reader.Read(buf)
    fmt.Println("读取:", n, "字节:", string(buf))

    // 定位
    reader.Seek(7, io.SeekStart)  // 移动到位置 7
    b := make([]byte, 5)
    reader.Read(b)
    fmt.Println("Seek 后读取:", string(b))

    // 获取位置
    pos, _ := reader.Seek(0, io.SeekCurrent)
    fmt.Println("当前位置:", pos)
}

strconv 类型转换 ​

go
package main

import (
    "fmt"
    "strconv"
)

func main() {
    // === 字符串转其他类型 ===

    // 转 int
    i, _ := strconv.Atoi("123")
    fmt.Println(i)  // 123

    // 转 int64 (指定进制)
    i64, _ := strconv.ParseInt("123", 10, 64)  // 十进制,64 位
    fmt.Println(i64)

    // 转 uint64
    u64, _ := strconv.ParseUint("123", 10, 64)
    fmt.Println(u64)

    // 转 float64
    f64, _ := strconv.ParseFloat("3.14", 64)
    fmt.Println(f64)

    // 转 bool
    b, _ := strconv.ParseBool("true")
    fmt.Println(b)  // true

    b2, _ := strconv.ParseBool("1")  // "1", "t", "T", "true", "TRUE", "True"
    fmt.Println(b2)

    // === 其他类型转字符串 ===

    // int 转字符串
    s := strconv.Itoa(123)
    fmt.Println(s)  // "123"

    // int64 转字符串
    s2 := strconv.FormatInt(123, 10)  // 十进制
    fmt.Println(s2)

    s3 := strconv.FormatInt(255, 16)  // 十六进制
    fmt.Println(s3)  // "ff"

    // uint64 转字符串
    s4 := strconv.FormatUint(123, 10)
    fmt.Println(s4)

    // float64 转字符串
    s5 := strconv.FormatFloat(3.14159, 'f', 2, 64)  // 保留 2 位小数
    fmt.Println(s5)  // "3.14"

    s6 := strconv.FormatFloat(3.14159, 'e', 2, 64)  // 科学计数法
    fmt.Println(s6)  // "3.14e+00"

    // bool 转字符串
    s7 := strconv.FormatBool(true)
    fmt.Println(s7)  // "true"

    // === 引号相关 ===

    // Quote: 添加双引号并转义
    q := strconv.Quote("Hello\nWorld")
    fmt.Println(q)  // "Hello\nWorld" (带双引号)

    // Unquote: 去除引号并还原
    u, _ := strconv.Unquote(`"Hello\nWorld"`)
    fmt.Println(u)  // Hello\nWorld (实际换行)

    // QuoteToASCII: 转义为非 ASCII
    qa := strconv.QuoteToASCII("你好")
    fmt.Println(qa)  // "\u4f60\u597d"

    // Append: 直接追加到字节 slice
    bs := strconv.AppendInt([]byte("num="), 123, 10)
    fmt.Println(string(bs))  // "num=123"
}

字符串性能优化 ​

1. 使用 strings.Builder 拼接字符串 ​

go
package main

import (
    "fmt"
    "strings"
)

// 不好:O(n²) 时间复杂度
func badConcat(parts []string) string {
    result := ""
    for _, p := range parts {
        result += p  // 每次都创建新字符串
    }
    return result
}

// 好:O(n) 时间复杂度
func goodConcat(parts []string) string {
    var sb strings.Builder
    sb.Grow(len(parts) * 10)  // 预分配容量 (可选)
    for _, p := range parts {
        sb.WriteString(p)
    }
    return sb.String()
}

func main() {
    parts := make([]string, 1000)
    for i := range parts {
        parts[i] = "hello"
    }

    s := goodConcat(parts)
    fmt.Println(len(s))
}

2. 使用 bytes.Buffer 构建大字符串 ​

go
func buildLargeString(data []string) string {
    var buf bytes.Buffer
    buf.Grow(len(data) * 100)  // 预分配

    for _, d := range data {
        buf.WriteString(d)
        buf.WriteByte('\n')
    }

    return buf.String()
}

3. 避免不必要的字符串转换 ​

go
// 不好:多次转换
func badConversion(data []byte) {
    s := string(data)
    if strings.Contains(s, "hello") {  // 转换
        if strings.HasPrefix(s, "hello") {  // 再次转换
            // ...
        }
    }
}

// 好:使用 bytes
func goodConversion(data []byte) {
    if bytes.Contains(data, []byte("hello")) {
        if bytes.HasPrefix(data, []byte("hello")) {
            // ...
        }
    }
}

4. 字符串比较优化 ​

go
// 使用 bytes.Compare 代替字典序比较
func compare(a, b string) int {
    return strings.Compare(a, b)  // 内部使用 bytes.Compare
}

// 忽略大小写比较
func equalFold(a, b string) bool {
    return strings.EqualFold(a, b)  // 比 ToLower 后比较更高效
}

5. 使用字符串切片而不是拼接 ​

go
// 不好
func badApproach(data []string) string {
    result := ""
    for _, d := range data {
        result += d + ","
    }
    return result
}

// 好
func goodApproach(data []string) string {
    return strings.Join(data, ",")
}

6. 子串操作使用切片 ​

go
s := "Hello, World!"

// 获取前 5 个字符
first5 := s[:5]  // "Hello"

// 获取最后 6 个字符
last6 := s[len(s)-6:]  // "World!"

// 获取中间部分
middle := s[7:12]  // "World"

// 注意:这是字节切片,中文需要转 rune
zh := "你好世界"
// zh[:3] 只会得到 "你" (3 字节)
// 正确做法:
runes := []rune(zh)
firstChar := string(runes[:1])  // "你"

编码转换 (base64, hex) ​

Base64 编码 ​

go
package main

import (
    "encoding/base64"
    "fmt"
)

func main() {
    data := "Hello, World!"

    // 标准 Base64 编码
    encoded := base64.StdEncoding.EncodeToString([]byte(data))
    fmt.Println("Base64:", encoded)

    // Base64 解码
    decoded, _ := base64.StdEncoding.DecodeString(encoded)
    fmt.Println("解码:", string(decoded))

    // URL 安全的 Base64 (替换 + 为 -, / 为 _)
    urlEncoded := base64.URLEncoding.EncodeToString([]byte(data))
    fmt.Println("URL 安全:", urlEncoded)

    // 原始 Base64 (不带填充)
    rawEncoded := base64.RawStdEncoding.EncodeToString([]byte(data))
    fmt.Println("不带填充:", rawEncoded)
}

Hex 编码 ​

go
package main

import (
    "encoding/hex"
    "fmt"
)

func main() {
    data := []byte("Hello")

    // Hex 编码
    encoded := hex.EncodeToString(data)
    fmt.Println("Hex:", encoded)  // "48656c6c6f"

    // Hex 解码
    decoded, _ := hex.DecodeString(encoded)
    fmt.Println("解码:", string(decoded))

    // 直接编码到字节 slice
    dst := make([]byte, hex.EncodedLen(len(data)))
    hex.Encode(dst, data)
    fmt.Println("直接编码:", string(dst))

    // 直接解码到字节 slice
    dst2 := make([]byte, hex.DecodedLen(len(encoded)))
    hex.Decode(dst2, []byte(encoded))
    fmt.Println("直接解码:", string(dst2))
}

字符串格式化 (fmt 包) ​

go
package main

import (
    "fmt"
    "math"
)

func main() {
    // === 动词 ===

    // %v: 默认格式
    fmt.Printf("%v\n", 42)        // 42
    fmt.Printf("%v\n", "hello")   // hello

    // %+v: 添加字段名 (结构体)
    type Point struct{ X, Y int }
    fmt.Printf("%+v\n", Point{1, 2})  // {X:1 Y:2}

    // %#v: Go 语法格式
    fmt.Printf("%#v\n", Point{1, 2})  // main.Point{X:1, Y:2}

    // %T: 类型
    fmt.Printf("%T\n", 42)  // int

    // %t: bool
    fmt.Printf("%t\n", true)  // true

    // === 整数 ===
    fmt.Printf("%d\n", 42)   // 十进制:42
    fmt.Printf("%b\n", 42)   // 二进制:101010
    fmt.Printf("%o\n", 42)   // 八进制:52
    fmt.Printf("%x\n", 42)   // 十六进制 (小写): 2a
    fmt.Printf("%X\n", 42)   // 十六进制 (大写): 2A

    // === 浮点数 ===
    fmt.Printf("%f\n", math.Pi)    // 3.141593
    fmt.Printf("%.2f\n", math.Pi)  // 3.14 (保留 2 位)
    fmt.Printf("%e\n", math.Pi)    // 3.141593e+00
    fmt.Printf("%g\n", math.Pi)    // 自动选择

    // === 字符串 ===
    fmt.Printf("%s\n", "hello")    // hello
    fmt.Printf("%q\n", "hello")    // "hello" (带引号)
    fmt.Printf("%x\n", "hello")    // 68656c6c6f (hex)

    // === 指针 ===
    p := new(int)
    fmt.Printf("%p\n", p)  // 0xc000...

    // === 宽度和精度 ===
    fmt.Printf("%10d\n", 42)   // 右对齐,宽度 10
    fmt.Printf("%-10d\n", 42)  // 左对齐,宽度 10
    fmt.Printf("%010d\n", 42)  // 补零,宽度 10

    // === 特殊 ===
    fmt.Printf("%%\n")  // 输出 %
}

实用字符串工具函数 ​

go
package utils

import (
    "strings"
    "unicode"
)

// 驼峰转下划线
func CamelToSnake(s string) string {
    var result strings.Builder
    for i, r := range s {
        if unicode.IsUpper(r) {
            if i > 0 {
                result.WriteRune('_')
            }
            result.WriteRune(unicode.ToLower(r))
        } else {
            result.WriteRune(r)
        }
    }
    return result.String()
}

// 下划线转驼峰
func SnakeToCamel(s string) string {
    parts := strings.Split(s, "_")
    for i, part := range parts {
        if len(part) > 0 {
            parts[i] = strings.ToUpper(part[:1]) + part[1:]
        }
    }
    return strings.Join(parts, "")
}

// 首字母大写
func Capitalize(s string) string {
    if len(s) == 0 {
        return s
    }
    runes := []rune(s)
    runes[0] = unicode.ToUpper(runes[0])
    return string(runes)
}

// 首字母小写
func Uncapitalize(s string) string {
    if len(s) == 0 {
        return s
    }
    runes := []rune(s)
    runes[0] = unicode.ToLower(runes[0])
    return string(runes)
}

// 反转字符串
func Reverse(s string) string {
    runes := []rune(s)
    for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
        runes[i], runes[j] = runes[j], runes[i]
    }
    return string(runes)
}

// 截断字符串 (按字符数)
func Truncate(s string, maxLen int) string {
    runes := []rune(s)
    if len(runes) <= maxLen {
        return s
    }
    return string(runes[:maxLen])
}

// 截断并添加省略号
func TruncateWithEllipsis(s string, maxLen int) string {
    if maxLen <= 3 {
        return Truncate(s, maxLen)
    }
    runes := []rune(s)
    if len(runes) <= maxLen {
        return s
    }
    return string(runes[:maxLen-3]) + "..."
}

// 是否是空字符串
func IsEmpty(s string) bool {
    return strings.TrimSpace(s) == ""
}

// 是否只包含空白字符
func IsBlank(s string) bool {
    for _, r := range s {
        if !unicode.IsSpace(r) {
            return false
        }
    }
    return true
}

// 移除所有空白
func RemoveWhitespace(s string) string {
    var result strings.Builder
    for _, r := range s {
        if !unicode.IsSpace(r) {
            result.WriteRune(r)
        }
    }
    return result.String()
}

字符串处理检查清单 ​

[ ] 字符串拼接使用 strings.Builder
[ ] 大量字节操作使用 bytes.Buffer
[ ] 避免在循环中进行字符串转换
[ ] 使用 bytes 包处理 []byte 数据
[ ] 中文字符串操作先转 rune
[ ] 使用 strings.Join 代替循环拼接
[ ] 大字符串预分配容量
[ ] 使用 EqualFold 进行不区分大小写比较
[ ] Base64/Hex 编码使用标准库
[ ] 格式化字符串使用 fmt.Sprintf