golang字符串拼接大比拼
拼接字符串大对比
直接+拼接
func StringsAdd() string {
var s string
for _, v := range StrData {
s += v
}
return s
}
使用fmt包进行组装
func StringsFmt() string {
var s string = fmt.Sprint(StrData)
return s
}
使用strings包的join方法
func StringsJoin() string {
var s string = strings.Join(StrData, "")
return s
}
strings.Join方法是实现效果最好的方法,耗时是最低的,内存占用也最低,额外内存分配次数也只有1次,我们查看strings.Join的方法内部的实现代码。
func Join(elems []string, sep string) string {
switch len(elems) {
case 0:
return ""
case 1:
return elems[0]
}
n := len(sep) * (len(elems) - 1)
for i := 0; i < len(elems); i++ {
n += len(elems[i])
}
var b Builder
b.Grow(n)
b.WriteString(elems[0])
for _, s := range elems[1:] {
b.WriteString(sep)
b.WriteString(s)
}
return b.String()
}
使用bytes.Buffer拼接
func StringsBuffer() string {
var s bytes.Buffer
for _, v := range StrData {
s.WriteString(v)
}
return s.String()
}
使用strings.Builder拼接
func StringsBuilder() string {
var b strings.Builder
for _, v := range StrData {
b.WriteString(v)
}
return b.String()
}
来看下builder内部细节
type Builder struct {
addr *Builder // of receiver, to detect copies by value
buf []byte
}
func (b *Builder) copyCheck() {
if b.addr == nil {
// This hack works around a failing of Go's escape analysis
// that was causing b to escape and be heap allocated.
// See issue 23382.
// TODO: once issue 7921 is fixed, this should be reverted to
// just "b.addr = b".
b.addr = (*Builder)(noescape(unsafe.Pointer(b)))
} else if b.addr != b {
panic("strings: illegal use of non-zero Builder copied by value")
}
}
// String returns the accumulated string.
func (b *Builder) String() string {
return *(*string)(unsafe.Pointer(&b.buf))
}
// WriteString appends the contents of s to b's buffer.
// It returns the length of s and a nil error.
func (b *Builder) WriteString(s string) (int, error) {
b.copyCheck()
b.buf = append(b.buf, s...)
return len(s), nil
}
// grow copies the buffer to a new, larger buffer so that there are at least n
// bytes of capacity beyond len(b.buf).
func (b *Builder) grow(n int) {
buf := make([]byte, len(b.buf), 2*cap(b.buf)+n)
copy(buf, b.buf)
b.buf = buf
}
+拼接会造成大量的临时字符串,效率最低。 fmt.Sprintf内部的逻辑比较复杂,有很多额外的判断,还用到了 interface,所以性能也不是很好 bytes.buffer有缓冲,效果好一点,如果能预估字符串的长度,还可以用 buffer.Grow() 接口来设置 capacity strings.join内部其实使用的strings.builder,并且对builder使用了grow(n)方法,效率比较高 在Go 1.10开始,Go官方将strings.Builder作为一个feature引入,其能较大程度的提高字符串拼接的效率,为了解决bytes.Buffer.String()存在的[]byte -> string类型转换和内存拷贝问题,这里使用了一个unsafe.Pointer的内存指针转换操作,实现了直接将buf []byte转换为 string类型,同时避免了内存充分配的问题。如果我们也像strings.join那样使用 b.Grow(n)方法,效率也很快
总结
官方是建议使用strings.Builder的方式,其实strings.join也是使用的它,如果提前知道总字节的大小,设置容量效率很快的。
较大的字符串拼接时,五种方式的拼接效率由高到低排序是:
strings.Builder ≈ strings.Join > strings.Buffer > “+” > fmt
总结
1.io库属于底层接口定义库,其作用是是定义一些基本接口和一些基本常量,并对这些接口的作用给出说明,常见的接口有Reader、Writer等。一般用这个库只是为了调用它的一些常量,比如io.EOF。 2.ioutil库包含在io目录下,它的主要作用是作为一个工具包,里面有一些比较实用的函数,比如 ReadAll(从某个源读取数据)、ReadFile(读取文件内容)、WriteFile(将数据写入文件)、ReadDir(获取目录) 3.os库主要是跟操作系统打交道,所以文件操作基本都会跟os库挂钩,比如创建文件、打开一个文件等。这个库往往会和ioutil库、bufio库等配合使用 4.bufio库可以理解为在io库上再封装一层,加上了缓存功能。它可能会和ioutil库和bytes.Buffer搞混。 4.1 bufio VS ioutil库:两者都提供了对文件的读写功能,唯一的不同就是bufio多了一层缓存的功能,这个优势主要体现读取大文件的时候(ioutil.ReadFile是一次性将内容加载到内存,如果内容过大,很容易爆内存) 4.2 bufio VS bytes.Buffer:两者都提供一层缓存功能,它们的不同主要在于 bufio 针对的是文件到内存的缓存,而 bytes.Buffer 的针对的是内存到内存的缓存(个人感觉有点像channel,你也可以发现 bytes.Buffer 并没有提供接口将数据写到文件)。 5.bytes和strings库:这两个库有点迷,首先它们都实现了Reader接口,所以它们的不同主要在于针对的对象不同,bytes针对的是字节,strings针对的是字符串(它们的方法实现原理很相似)。另一个区别就是 bytes还带有Buffer的功能,但是 strings没提供。