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