本文介绍Go语言中的string类型、strings包和bytes.Buffer类型,介绍几种字符串拼接方法。

目录

  • string类型
  • strings包
    • strings.Builder类型
    • strings.Reader类型
  • bytes.Buffer
    • bytes.Buffer:写数据
    • bytes.Buffer:读数据
  • 字符串拼接
    • 直接相加
    • strings.Builder
    • strings.Join()
    • bytes.Buffer
    • append方法
    • fmt.Sprintf
  • 字符串拼接性能测试
  • 系列文章

string类型

string类型的值可以拆分为一个包含多个字符(rune类型)的序列,也可以被拆分为一个包含多个字节 (byte类型) 的序列。其中一个rune类型值代表一个Unicode 字符,一个rune类型值占用四个字节,底层就是一个 UTF-8 编码值,它其实是int32类型的一个别名类型。

package mainimport ("fmt"
)func main() {str := "你好world"fmt.Printf("The string: %q\n", str)fmt.Printf("runes(char): %q\n", []rune(str))fmt.Printf("runes(hex): %x\n", []rune(str))fmt.Printf("bytes(hex): [% x]\n", []byte(str))
}

执行结果:

The string: "你好world"
runes(char): ['你' '好' 'w' 'o' 'r' 'l' 'd']
runes(hex): [4f60 597d 77 6f 72 6c 64]
bytes(hex): e4 bd a0 e5 a5 bd 77 6f 72 6c 64

可以看到,英文字符使用一个字节,而中文字符需要三个字节。下面使用 for range 语句对上面的字符串进行遍历:

for index, value := range str {fmt.Printf("%d: %q [% x]\n", index, value, []byte(string(value)))
}

执行结果如下:

0: '你' [e4 bd a0]
3: '好' [e5 a5 bd]
6: 'w' [77]
7: 'o' [6f]
8: 'r' [72]
9: 'l' [6c]
10: 'd' [64]

index索引值不是0-6,相邻Unicode 字符的索引值不一定是连续的,因为中文字符占用了3个字节,宽度为3。

strings包

strings.Builder类型

strings.Builder的优势主要体现在字符串拼接上,相比使用+拼接,效率更高。

  • strings.Builder已存在的值不可改变,只能重置(Reset()方法)或者拼接更多的内容。
  • 一旦调用了Builder值,就不能再以任何方式对其进行复制,比如函数间值传递、通道传递值、把值赋予变量等。
  • 在进行拼接时,Builder值会自动地对自身的内容容器进行扩容,也可以使用Grow方法进行手动扩容。
package mainimport ("fmt""strings"
)
func main() {var builder1 strings.Builderbuilder1.WriteString("hello")builder1.WriteByte(' ')builder1.WriteString("world")builder1.Write([]byte{' ', '!'})fmt.Println(builder1.String())  f1 := func(b strings.Builder) {// b.WriteString("world !")  //会报错}f1(builder1)builder1.Reset()fmt.Printf("The length 0f builder1: %d\n", builder1.Len())}

执行结果:

hello world !
The length 0f builder1: 0

strings.Reader类型

strings.Reader类型可以用于高效地读取字符串,它通过使用已读计数机制来实现了高效读取,已读计数保存了已读取的字节数,也代表了下一次读取的起始索引位置。

package mainimport ("fmt""strings"
)
func main() {   reader1 := strings.NewReader("hello world!")buf1 := make([]byte, 6)fmt.Printf("reading index: %d\n", reader1.Size()-int64(reader1.Len()))reader1.Read(buf1)fmt.Println(string(buf1))fmt.Printf("reading index: %d\n", reader1.Size()-int64(reader1.Len()))reader1.Read(buf1)fmt.Println(string(buf1))fmt.Printf("reading index: %d\n", reader1.Size()-int64(reader1.Len()))
}

执行结果:

reading index: 0
hello
reading index: 6
world!
reading index: 12

可以看到,每读取一次之后,已读计数就会增加。

strings包的ReadAt方法不会依据已读计数进行读取,也不会更新已读计数。它可以根据偏移量来自由地读取Reader值中的内容。

package mainimport ("fmt""strings"
)
func main() {reader1 := strings.NewReader("hello world!")buf1 := make([]byte, 6)offset1 := int64(6)n, _ := reader1.ReadAt(buf1, offset1)  fmt.Println(string(buf2))
}

执行结果:

world!

也可以使用Seek方法来指定下一次读取的起始索引位置。

package mainimport ("fmt""strings""io"
)
func main() {reader1 := strings.NewReader("hello world!")buf1 := make([]byte, 6)offset1 := int64(6)readingIndex, _ := reader2.Seek(offset1, io.SeekCurrent)fmt.Printf("reading index: %d\n", readingIndex)reader1.Read(buf1)fmt.Printf("reading index: %d\n", reader1.Size()-int64(reader1.Len()))fmt.Println(string(buf1))
}

执行结果:

reading index: 6
reading index: 12
world!

bytes.Buffer

bytes包和strings包类似,strings包主要面向的是 Unicode 字符和经过 UTF-8 编码的字符串,而bytes包面对的则主要是字节和字节切片,主要作为字节序列的缓冲区。bytes.Buffer数据的读写都使用到了已读计数。

bytes.Buffer具有读和写功能,下面分别介绍他们的简单使用方法。

bytes.Buffer:写数据

和strings.Builder一样,bytes.Buffer可以用于拼接字符串,strings.Builder也会自动对内容容器进行扩容。请看下面的代码:

package mainimport ("bytes""fmt"
)func DemoBytes() {var buffer bytes.Bufferbuffer.WriteString("hello ")buffer.WriteString("world !")fmt.Println(buffer.String())
}

执行结果:

hello world !

bytes.Buffer:读数据

bytes.Buffer读数据也使用了已读计数,需要注意的是,进行读取操作后,Len方法返回的是未读内容的长度。下面直接来看代码:

package mainimport ("bytes""fmt"
)func DemoBytes() {var buffer bytes.Bufferbuffer.WriteString("hello ")buffer.WriteString("world !")p1 := make([]byte, 5)n, _ := buffer.Read(p1)fmt.Println(string(p1))fmt.Println(buffer.String())fmt.Printf("The length of buffer: %d\n", buffer.Len())
}

执行结果:

helloworld !
The length of buffer: 8

字符串拼接

简单了解了string类型、strings包和bytes.Buffer类型后,下面来介绍golang中的字符串拼接方法。

https://zhuanlan.zhihu.com/p/349672248

go test -bench=. -run=^BenchmarkDemoBytes$

直接相加

最简单的方法是直接相加,由于string类型的值是不可变的,进行字符串拼接时会生成新的字符串,将拼接的字符串依次拷贝到一个新的连续内存空间中。如果存在大量字符串拼接操作,使用这种方法非常消耗内存。

package mainimport ("bytes""fmt""time"
)func main() {str1 := "hello "str2 := "world !"str3 := str1 + str2fmt.Println(str3)
}

strings.Builder

前面介绍了strings.Builder可以用于拼接字符串:

var builder1 strings.Builder
builder1.WriteString("hello ")
builder1.WriteString("world !")

strings.Join()

也可以使用strings.Join方法,其实Join()调用了WriteString方法;

str1 := "hello "
str2 := "world !"
str3 := ""str3 = strings.Join([]string{str3,str1},"")
str3 = strings.Join([]string{str3,str2},"")

bytes.Buffer

bytes.Buffer也可以用于拼接:

var buffer bytes.Bufferbuffer.WriteString("hello ")
buffer.WriteString("world !")

append方法

也可以使用Go内置函数append方法,用于拼接切片:

package mainimport ("fmt"
)func DemoAppend(n int) {str1 := "hello "str2 := "world !"var str3 []bytestr3 = append(str3, []byte(str1)...)str3 = append(str3, []byte(str2)...)fmt.Println(string(str3))
}

执行结果:

hello world !

fmt.Sprintf

fmt包中的Sprintf方法也可以用来拼接字符串:

str1 := "hello "
str2 := "world !"
str3 := fmt.Sprintf("%s%s", str1, str2)

字符串拼接性能测试

下面来测试一下这6种方法的性能,编写测试源码文件strcat_test.go:

package benchmarkimport ("bytes""fmt""strings""testing"
)func DemoBytesBuffer(n int) {var buffer bytes.Bufferfor i := 0; i < n; i++ {buffer.WriteString("hello ")buffer.WriteString("world !")}
}func DemoWriteString(n int) {var builder1 strings.Builderfor i := 0; i < n; i++ {builder1.WriteString("hello ")builder1.WriteString("world !")}
}func DemoStringsJoin(n int) {str1 := "hello "str2 := "world !"str3 := ""for i := 0; i < n; i++ {str3 = strings.Join([]string{str3, str1}, "")str3 = strings.Join([]string{str3, str2}, "")}}func DemoPlus(n int) {str1 := "hello "str2 := "world !"str3 := ""for i := 0; i < n; i++ {str3 += str1str3 += str2}
}func DemoAppend(n int) {str1 := "hello "str2 := "world !"var str3 []bytefor i := 0; i < n; i++ {str3 = append(str3, []byte(str1)...)str3 = append(str3, []byte(str2)...)}
}func DemoSprintf(n int) {str1 := "hello "str2 := "world !"str3 := ""for i := 0; i < n; i++ {str3 = fmt.Sprintf("%s%s", str3, str1)str3 = fmt.Sprintf("%s%s", str3, str2)}
}func BenchmarkBytesBuffer(b *testing.B) {for i := 0; i < b.N; i++ {DemoBytesBuffer(10000)}
}func BenchmarkWriteString(b *testing.B) {for i := 0; i < b.N; i++ {DemoWriteString(10000)}
}func BenchmarkStringsJoin(b *testing.B) {for i := 0; i < b.N; i++ {DemoStringsJoin(10000)}
}func BenchmarkAppend(b *testing.B) {for i := 0; i < b.N; i++ {DemoAppend(10000)}
}func BenchmarkPlus(b *testing.B) {for i := 0; i < b.N; i++ {DemoPlus(10000)}
}func BenchmarkSprintf(b *testing.B) {for i := 0; i < b.N; i++ {DemoSprintf(10000)}
}

执行性能测试:

$ go test -bench=. -run=^$
goos: windows
goarch: amd64
pkg: testGo/benchmark
cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
BenchmarkBytesBuffer-8              3436            326846 ns/op
BenchmarkWriteString-8              4148            271453 ns/op
BenchmarkStringsJoin-8                 3         402266267 ns/op
BenchmarkAppend-8                   1923            618489 ns/op
BenchmarkPlus-8                        3         345087467 ns/op
BenchmarkSprintf-8                     2         628330850 ns/op
PASS
ok      testGo/benchmark        9.279s

通过平均耗时可以看到WriteString方法执行效率最高。Sprintf方法效率最低。

  1. 我们看到Strings.Join方法效率也比较低,在上面的场景下它的效率比较低,它在合并已有字符串数组的场合效率是很高的。

  2. 如果要连续拼接大量字符串推荐使用WriteString方法,如果是少量字符串拼接,也可以直接使用+

  3. append方法的效率也是很高的,它主要用于切片的拼接。

  4. fmt.Sprintf方法虽然效率低,但在少量数据拼接中,如果你想拼接其它数据类型,使用它可以完美的解决:

    name := "zhangsan"
    age := 20
    str4 := fmt.Sprintf("%s is %d years old", name, age)
    fmt.Println(str4)  // zhangsan is 20 years old
    

--THE END--


系列文章

1. Go语言开发环境安装
2. Go语言基础语法(一)
3. Go语言基础语法(二):函数
4. Go语言基础语法(三):结构体及方法
5. Go语言中的字符串拼接方法介绍
6. Go语言中的通道
7. Go语言并发编程:原子操作
8. Go语言并发编程:互斥锁
9. Go语言并发编程:sync.Once


欢迎关注公众号:「测试开发小记」及时接收最新技术文章!

Go语言中的字符串拼接方法介绍相关推荐

  1. Go 语言中的字符串拼接

    目录 1. 通过 + 号连接两个字符串 2. 使用 sprintf 函数 3. 使用 Join 函数 4. 使用 bytes.Buffer 的 WriteString 函数 5. 使用 buffer. ...

  2. go语言字符串换行_Go语言中的字符串处理方法示例详解

    1 概述 字符串,string,一串固定长度的字符连接起来的字符集合.Go语言的字符串是使用UTF-8编码的.UTF-8是Unicode的实现方式之一. Go语言原生支持字符串.使用双引号(" ...

  3. c语言中的字符串拼接

    在看内核源码时,看到这样一段代码: int __init ip_vs_protocol_init(void) {char protocols[64]; #define REGISTER_PROTOCO ...

  4. c语言mktime,在C语言中转换时间的基本方法介绍

    C语言mktime()函数:将时间转换成经过的秒数头文件: #include 定义函数: time_t mktime(strcut tm * timeptr); 函数说明:mktime()用来将参数t ...

  5. atoi函数_每日干货丨C语言中的字符串处理库函数介绍与实现

    strlen函数:求字符串的长度 size_t strlen(const char *s) 头文件:#include 说明:求出s指向的字符串的长度(不包括null字符). 返回值:返回s指向的字符串 ...

  6. C语言中怎么自动生成时间,在C语言中转换时间的基本方法介绍

    C语言mktime()函数:将时间转换成经过的秒数头文件: #include 定义函数: time_t mktime(strcut tm * timeptr); 函数说明:mktime()用来将参数t ...

  7. c语言中用于获取字符串长度的函数是,C语言中求字符串长度的函数的几种实现方法...

    C语言中求字符串长度的函数的几种实现方法 1.最常用的方法是创建一个计数器,判断是否遇到'\0',不是'\0'指针就往后加一. int my_strlen(const char *str) { ass ...

  8. python字符串截取方法_如何使用python语言中的字符串方法截取字符串

    在我们使用python语言中的字符串方法时,可能会判断某个字符串是否以什么开头,可以使用什么进行截取等.下面利用几个实例说明字符串中的方法的用法,操作如下: 工具/原料 python 截图工具 方法/ ...

  9. 切割字符串长度php,C++_C语言中计算字符串长度与分割字符串的方法,C语言strlen()函数:返回字符串 - phpStudy...

    C语言中计算字符串长度与分割字符串的方法 C语言strlen()函数:返回字符串的长度头文件: #include strlen()函数用来计算字符串的长度,其原型为: unsigned int str ...

最新文章

  1. yaml-cpp介绍
  2. 【归并排序】休息(jzoj 3462)
  3. MySQL - 行锁 表锁 乐观锁 悲观锁 读锁 写锁
  4. 超分辨率技术如何发展?这6篇ECCV 18论文带你一次尽览
  5. ACM 学习笔记(六) 图论
  6. 小程序反编译 g is not defined_阅读技巧 | 如何猜中作者的小心思?
  7. 手机APP测试如何进行兼容性测试?
  8. 限流算法:滑动时间窗口算法。
  9. html链接位置移动,锚点链接点击缓慢移动到目标位置
  10. 暴力解题之公务员行测资料分析技巧
  11. 2016计算机cpu,2016年12月电脑CPU天梯图一览
  12. Java设计模式的一些积累
  13. 众多交通工具3dm Rhino资源素材一键即可获取
  14. 主线程结束子线程会跟着结束吗
  15. 异步下载小说《诡秘之主》
  16. vba按原格式批量合并word文档
  17. 我把皮小浪の的 蓝色妖姬系列做进了java窗口
  18. 免费回收站恢复软件有哪些?数据恢复软件,这三款就足够了
  19. windows开启休眠
  20. STM32实现低功耗待机(电流低至5.7uA)

热门文章

  1. 爲什麽一个标准的反相器中PMOS管的寬長比要比N管大
  2. 镍氢电池的特性和使用方法(FDK镍氢电池充电机制)
  3. 生僻字怎么用计算机的,最实用生僻字输入方案大全
  4. 【HTML】之marquee标签的使用和说明
  5. log4j 配置文件中设置相对路径
  6. 无胁科技-TVD每日漏洞情报-2022-12-13
  7. 字符串连接 (c语言)
  8. 程序员潜规则之痛——“JAVA 之父”也遭遇过
  9. 开源软件xxl-job的oracle版本
  10. 网络连通性以及网络不通解决办法