Skip to content
Go
bytes & strings — 字节与字符串操作

bytes & strings — 字节与字符串操作

bytesstrings 是 API 几乎完全对称的两个包。strings 操作 UTF-8 字符串,bytes 操作字节切片。选择标准:如果你处理的是文本,用 strings;如果处理的是二进制数据,用 bytes


1. 字符串拼接性能对比

Go 有四种主要拼接方式,性能差异很大:

// 方式 1:+ 操作符(少量拼接)
s := "hello" + " " + "world"

// 方式 2:strings.Builder(大量拼接,最快)
var builder strings.Builder
builder.Grow(1024)    // 预分配,避免重复扩容
for i := 0; i < 1000; i++ {
    builder.WriteString("hello")
}
result := builder.String()

// 方式 3:bytes.Buffer(类似 Builder,但更通用)
var buf bytes.Buffer
for i := 0; i < 1000; i++ {
    buf.WriteString("hello")
}
result := buf.String()

// 方式 4:strings.Join(拼接切片)
parts := []string{"a", "b", "c"}
result := strings.Join(parts, ", ")

// 方式 5:fmt.Sprintf(最慢,但有格式化)
result := fmt.Sprintf("%s-%d", "user", 42)
方式 速度 内存分配 场景
+(少量) < 5 次拼接
strings.Builder 最快 最低 循环中大量拼接
bytes.Buffer 需要同时操作 string 和 []byte
strings.Join 拼接已有切片
fmt.Sprintf 需要格式化时

性能提示strings.Builder.Grow() 预分配是性能关键。如果不预分配,Builder 内部需要多次扩容复制。


2. 🔬 深入原理:为什么 strings.Builder+ 快?

// 操作符 + 每次都会创建新字符串
s := "a" + "b" + "c" + "d"
// 实际执行:("a"+"b") → temp1; (temp1+"c") → temp2; (temp2+"d") → s
// 每次 + 都分配新内存并复制 → O(n²) 内存分配

// strings.Builder 使用内部 buffer,追加 +

3. 常用的查找与操作

3.1 包含判断

strings.Contains(s, "substr")        // 是否包含
strings.ContainsAny(s, "abc")       // 是否包含任意字符
strings.ContainsRune(s, '你')       // 是否包含指定 rune
strings.HasPrefix(s, "http://")     // 前缀
strings.HasSuffix(s, ".go")         // 后缀

3.2 索引查找

strings.Index(s, "sub")          // 第一个匹配的位置
strings.LastIndex(s, "sub")      // 最后一个匹配的位置
strings.IndexByte(s, 'a')        // 查找单字节(比 Index 快)
strings.IndexRune(s, '你')       // 查找 rune
strings.IndexAny(s, "abc")       // 查找任意字符

3.3 替换与分割

strings.Replace(s, "old", "new", 1)    // 仅替换第 1 次
strings.ReplaceAll(s, "old", "new")    // 替换所有

strings.Split(s, ",")                   // 按分隔符分割
strings.SplitN(s, ",", 2)              // 分割为 N 部分
strings.SplitAfter(s, ",")             // 保留分隔符
strings.Fields(s)                       // 按空白字符分割

strings.Trim(s, " \t\n")               // 修剪两端
strings.TrimSpace(s)                    // 修剪空白(等同于 Trim + 空白集)
strings.TrimPrefix(s, "http://")       // 修剪前缀

3.4 大小写转换

strings.ToUpper(s)     // 全大写
strings.ToLower(s)     // 全小写
strings.ToTitle(s)     // 标题格式(不推荐,对 Unicode 处理有问题)

// Go 1.18+:Unicode 大小写(更准确但更慢)
cases.Title(language.English).String(s)

4. strings.Replacer — 高效多替换

// 批量替换 HTML 实体
replacer := strings.NewReplacer(
    "&", "&amp;",
    "<", "&lt;",
    ">", "&gt;",
    `"`, "&quot;",
)
escaped := replacer.Replace(`<div class="test">Hello</div>`)
// → &lt;div class=&quot;test&quot;&gt;Hello&lt;/div&gt;

strings.Replacer 内部使用 Trie 树,比多次 strings.Replace 调用快得多。


5. strings.Builder 进阶用法

// 复用 Builder
var builder strings.Builder
for _, item := range items {
    builder.Reset()    // 重置内容(保留底层 buffer)
    builder.WriteString(item.Prefix)
    builder.WriteString(item.Name)
    result := builder.String()
    // …
}

// Grow 预分配
builder.Grow(estimateSize)

// 直接写入 byte/rune
builder.WriteByte('a')
builder.WriteRune('你')

// 查看当前内容(不消费)
fmt.Println(builder.String())

sync.Pool + strings.Builder

var builderPool = sync.Pool{
    New: func() interface{} {
        return &strings.Builder{}
    },
}

func buildString(items []string) string {
    b := builderPool.Get().(*strings.Builder)
    defer builderPool.Put(b)

    b.Reset()
    for _, item := range items {
        b.WriteString(item)
    }
    return b.String()
}

6. bytes.Buffer 特有功能

var buf bytes.Buffer

buf.WriteString("hello")
buf.WriteByte('!')
buf.Write([]byte(" world"))

// 读取
data := make([]byte, buf.Len())
buf.Read(data)

// Next — 读取前 N 字节并移动指针
chunk := buf.Next(5)

// Truncate — 保留前 N 字节
buf.Truncate(10)

// ReadFrom / WriteTo — 高效的 IO(可能使用零拷贝)
buf.ReadFrom(file)    // 从 Reader 读取所有内容
buf.WriteTo(file)     // 写入到 Writer

7. 字符串与 []byte 转换的成本

// string → []byte:分配新内存 + 复制
b := []byte("hello")     // 有成本

// []byte → string:分配新内存 + 复制
s := string(b)           // 有成本

// 热路径避免频繁转换
// 如果不需要修改,直接用 string
// 如果需要修改,一直用 []byte,最后转 string

🔬 深入原理:Go 编译器在某些场景下会优化:string([]byte)map key 查找和 string 比较中会做优化,不分配。但不要依赖这种优化——语义上每次转换都是复制的。


8. 🚨 常见陷阱

陷阱 说明
string[i] 是 byte 不是 rune 中文等 Unicode 字符占用多个字节
len(s) 是字节数不是字符数 utf8.RuneCountInString(s) 获取字符数
range s 按 rune 迭代 索引 i 是字节位置不是字符位置
Split("", ",") 返回 [""] 空字符串分割得到一个包含空字符串的切片
Builder 写入后忘记 Reset 下一次使用会追加到旧内容后面