最近跟着青训营,学了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 对象放回内存池。