最近跟着青训营,学了go的优化,虽然不是很懂,以下是自己的一点笔记。
示例一 字符串反转
下面是一个示例的 Go 程序,用于将一个字符串中的每个单词进行反转:
package main
import (
"fmt"
"strings"
)
func reverseWords(s string) string {
words := strings.Split(s, " ")
for i, word := range words {
words[i] = reverseString(word)
}
return strings.Join(words, " ")
}
func reverseString(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 main() {
s := "Hello World! This is a sample string."
result := reverseWords(s)
fmt.Println(result)
}
这个程序使用了 strings.Split
和 strings.Join
函数来分割和拼接字符串,以及 reverseString
函数来反转每个单词。现在我们来对这份代码进行优化。
首先,我们可以使用 strings.Builder
来优化字符串的拼接过程,避免每次拼接都创建新的字符串对象:
func reverseWords(s string) string {
words := strings.Split(s, " ")
var builder strings.Builder
for i, word := range words {
words[i] = reverseString(word)
builder.WriteString(words[i])
builder.WriteString(" ")
}
return strings.TrimSpace(builder.String())
}
这里我们使用了 strings.Builder
来代替字符串拼接,通过 WriteString
方法将反转后的单词逐个写入到 builder
中,并在每个单词之间加上空格。最后通过 TrimSpace
函数去除首尾的空格。
接下来,我们可以对 reverseString
函数进行优化,避免使用 []rune
进行字符反转:
func reverseString(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)
}
这个优化可以避免每次都创建 []rune
的开销,而是直接在原字符串上进行字符交换。
最后,我们可以使用 strings.Builder
的 Grow
方法来预分配足够的内存空间,以减少内存分配和拷贝的次数:
func reverseWords(s string) string {
words := strings.Split(s, " ")
var builder strings.Builder
builder.Grow(len(s))
for i, word := range words {
words[i] = reverseString(word)
builder.WriteString(words[i])
builder.WriteString(" ")
}
return strings.TrimSpace(builder.String())
}
通过调用 builder.Grow(len(s))
,我们预分配了足够的内存空间,避免了字符串拼接过程中的内存分配和拷贝。
示例二 统计每个单词出现的次数
package main
import (
"fmt"
"strings"
)
func countWords(text string) map[string]int {
words := strings.Fields(text)
counts := make(map[string]int)
for _, word := range words {
counts[word]++
}
return counts
}
func main() {
text := "Hello World! This is a sample text. Hello World!"
counts := countWords(text)
fmt.Println(counts)
}
这个程序使用了 strings.Fields
函数将文本分割成单词,并使用 map[string]int
来统计每个单词出现的次数。现在我们来对这份代码进行优化。
首先,我们可以使用空结构体 struct{}
作为 map
的值类型,来节省内存占用:
func countWords(text string) map[string]struct{} {
words := strings.Fields(text)
counts := make(map[string]struct{})
for _, word := range words {
counts[word] = struct{}{}
}
return counts
}
这里我们将 map[string]int
改为 map[string]struct{}
,并将 counts[word]++
改为 counts[word] = struct{}{}
。这样做的好处是,空结构体不占用任何内存空间,只需要一个零字节的占位符,可以节省大量的内存。
接下来,我们可以使用 sync.Pool
来管理 map
对象的内存池(当然也用了map预分配内存),避免频繁的内存分配和垃圾回收。(sync.Pool
是一个线程安全的对象池,它维护了一个存放对象的池子。)
var countsPool = sync.Pool{
New: func() interface{} {
return make(map[string]struct{})
},
}
func countWords(text string) map[string]struct{} {
words := strings.Fields(text)
counts := make(map[string]struct{}, len(words))
for _, word := range words {
counts[word] = struct{}{}
}
return counts
}
这里我们通过 sync.Pool
创建了一个 map
对象的内存池,通过 New
方法来创建新的 map
对象。在 countWords
函数中,我们通过 countsPool.Get()
来获取一个空闲的 map
对象,然后进行统计操作。最后通过 defer countsPool.Put(counts)
将 map
对象放回内存池。