Skip to main content

go 高性能编程/002-更高效的写法

· 3 min read

字符串高效拼接

常见字符串拼接方式:5 种

1、使用 + 2、使用 fmt.Sprintf 3、使用 strings.Builder

func builderConcat(n int, str string) string {  
var builder strings.Builder
for i := 0; i < n; i++ {
builder.WriteString(str)
}
return builder.String()
}

4、使用 bytes.Buffer

func bufferConcat(n int, s string) string {  
buf := new(bytes.Buffer)
for i := 0; i < n; i++ {
buf.WriteString(s)
}
return buf.String()
}

5、使用 []byte

func byteConcat(n int, str string) string {  
buf := make([]byte, 0)
for i := 0; i < n; i++ {
buf = append(buf, str...)
}
return string(buf)
}

strings.Builderbytes.Buffer 和 []byte 的性能差距不大,而且消耗的内存也十分接近。

最后建议:一般推荐使用 strings.Builder 来大量拼接字符串。

string.Builder 也提供了预分配内存的方式 Grow

func builderConcat(n int, str string) string {  
var builder strings.Builder
builder.Grow(n * len(str))
for i := 0; i < n; i++ {
builder.WriteString(str)
}
return builder.String()
}

切片陷阱

在已有切片的基础上进行切片,不会创建新的底层数组。因为原来的底层数组没有发生变化,内存会一直占用,直到没有变量引用该数组。因此很可能出现这么一种情况,原切片由大量的元素构成,但是我们在原切片的基础上切片,虽然只使用了很小一段,但底层数组在内存中仍然占据了大量空间,得不到释放。比较推荐的做法,使用 copy 替代 re-slice

如果函数直接在原切片基础上进行切片,会导致内存得不到释放。

for vs for range

for range 是一个语法糖,range 对每个迭代值都创建了一个拷贝,如果每次迭代的值内存占用很小的情况下,for 和 range 的性能几乎没有差异,但是如果每个迭代值内存占用很大。那性能差距就很明显了。

这个时候建议用指针,指针指向这个迭代的对象。

避免用反射 reflect

反射效率很低,特别是 go 自带的 json 的 Marshal 和 Unmarshal 方法。推荐用高性能的 json 工具。

用空结构体节省内存

Go 语言中空结构体不占据内存,因此被广泛作为各种场景下的占位符使用。节省资源 + 本身具备很强语义。

fmt.Println(unsafe.Sizeof(struct{}{}))

0

空结构体不需要进行内存对齐,所以不占用空间。

注意:struct 作为结构体最后一个字段时,需要内存对齐。因为如果有指针指向该字段, 返回的地址将在结构体之外,如果此指针一直存活不释放对应的内存,就会有内存泄露的问题(该内存不因结构体释放而释放)。