#450 Go 练手小项目

2020-12-21

开始在学习 Go 语言了,先在 GitHub 上找一批小项目用来学习。

  1. julienschmidt/httprouter shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    A high performance HTTP request router that scales well
  2. golang/groupcache shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    groupcache is a caching and cache-filling library, intended as a replacement for memcached in many cases.
  3. robfig/cron shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    a cron library for go
  4. jroimartin/gocui shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    Minimalist Go package aimed at creating Console User Interfaces.
  5. tylertreat/comcast shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    Simulating shitty network connections so you can build better systems.
  6. pkg/errors shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    Simple error handling primitives
  7. schachmat/wego shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    weather app for the terminal
  8. teh-cmc/go-internals shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w
    A book about the internals of the Go programming language.
  9. simeji/jid shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    json incremental digger
  10. patrickmn/go-cache shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    An in-memory key:value store/cache (similar to Memcached) library for Go, suitable for single-machine applications.
  11. wercker/stern shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    ⎈ Multi pod and container log tailing for Kubernetes
  12. allegro/bigcache shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    Efficient cache for gigabytes of data written in Go.
  13. microsoft/ethr shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    Ethr is a Comprehensive Network Measurement Tool for TCP, UDP & ICMP.
  14. samber/lo shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    💥 A Lodash-style Go library based on Go 1.18+ Generics (map, filter, contains, find...)
  15. goproxyio/goproxy shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    A global proxy for Go modules.
  16. eranyanay/1m-go-websockets shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    handling 1M websockets connections in Go
  17. fogleman/nes shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    NES emulator written in Go.
  1. inconshreveable/ngrok shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w
    Introspected tunnels to localhost
  2. sirupsen/logrus shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    Structured, pluggable logging for Go.
  3. tmrts/go-patterns shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    Curated list of Go design patterns, recipes and idioms
  4. gorilla/websocket shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    A fast, well-tested and widely used WebSocket implementation for Go.
  5. gorilla/mux shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    A powerful HTTP router and URL matcher for building Go web servers with 🦍
  6. julienschmidt/httprouter shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    A high performance HTTP request router that scales well
  7. rakyll/hey shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    HTTP load generator, ApacheBench (ab) replacement
  8. ahmetb/kubectx shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    Faster way to switch between clusters and namespaces in kubectl
  9. charmbracelet/bubbletea shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    A powerful little TUI framework 🏗
  10. jmoiron/sqlx shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    general purpose extensions to golang's database/sql
  11. emirpasic/gods shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w
    GoDS (Go Data Structures) - Sets, Lists, Stacks, Maps, Trees, Queues, and much more
  12. PuerkitoBio/goquery shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    A little like that j-thing, only in Go.
  13. go-martini/martini shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    Classy web framework for Go
  14. golang/groupcache shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    groupcache is a caching and cache-filling library, intended as a replacement for memcached in many cases.
  15. FiloSottile/age shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    A simple, modern and secure encryption tool (and Go library) with small explicit keys, no config options, and UNIX-style composability.
  16. dgrijalva/jwt-go shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    ARCHIVE - Golang implementation of JSON Web Tokens (JWT). This project is now maintained at:
  17. tidwall/gjson shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    Get JSON values quickly - JSON parser for Go

#449 gvm: Golang 版本管理

2020-12-19

https://github.com/moovweb/gvm stars license language

安装

bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer)

使用

source ~/.gvm/scripts/gvm

gvm listall

# gvm 默认从 github 克隆代码到本地,然后进行编译
# 可以采用 gitee 仓库加速
git clone git@gitee.com:mirrors/go ~/.gvm/archive/go

gvm install go1.16

gvm list
gvm use go1.16

go version
# go version go1.16 linux/amd64

#448 Golang 结构体

2020-12-18
package main

import (
    "encoding/json"
    "fmt"
    "reflect"
)

type Address struct {
    City    string `json:"city"`
    Country string `json:"country"`
}

type User struct {
    Username string `json:"username"`
    Password string `json:"password"`
}

type Person struct {
    Name    string  `json:"name"`
    Age     int     `json:"age"`
    Address Address `json:"address"`
    // 这么写,JSON 会多一层
    // User    `json:"user"`
    User
}

func (p Person) SayHello() {
    fmt.Println("Hello, my name is", p.Name)
}

func main() {
    person := Person{
        Name: "Bob",
        Age:  30,
        Address: Address{
            City:    "London",
            Country: "UK",
        },
        User: User{
            Username: "bob1999",
            Password: "pa55w0rd",
        },
    }

    // 属性操作
    fmt.Println("Name:", person.Name)
    fmt.Println("Age:", person.Age)
    fmt.Println("City:", person.Address.City)
    fmt.Println("Country:", person.Address.Country)
    fmt.Println("Username:", person.Username)
    fmt.Println("Password:", person.Password)
    fmt.Printf("%+v\n", person)
    fmt.Printf("%#v\n", person)

    // 方法调用
    person.SayHello()

    // 通过反射获取标签内容
    t := reflect.TypeOf(person)
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        tag := field.Tag.Get("json")
        fmt.Println(field.Name, ":", tag)
    }

    // JSON 序列化
    jsonData, err := json.Marshal(person)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println("JSON:", string(jsonData))
}

// Name: Bob
// Age: 30
// City: London
// Country: UK
// Username: bob1999
// Password: pa55w0rd
// main.Person{name:"Bob", age:30, address:main.Address{city:"London", country:"UK"}, User:main.User{username:"bob1999", password:"pa55w0rd"}}

其他知识点:

  1. 字段名大小写问题:首字母小写的话,只有本包可以访问

  2. Zero-value Struct

type Fruit struct {
    name string
}
var apple Fruit
fmt.Println(apple) // {}
  1. new 关键字
package main

import "fmt"

type Employee struct {
    Name string
    Age  int
}

func main() {
    p := new(Employee)
    fmt.Println(p) // &{ 0}

    var p2 Employee
    fmt.Println(p2) // { 0}

    p3 := Employee{"Me", 30}
    fmt.Println(p3) // {Me 30}
}
  1. struct 字面量
type Person struct {
    Name  string
    Age   int
    Email string
}

p1 := Person{
    Name:  "Alice",
    Age:   25,
    Email: "alice@example.com",
}
p2 := Person {"Alice", 25, "alice@example.com"}
  1. 指针

  2. 匿名结构体

package main

import (
    "fmt"
)

func main() {
    user := struct {
        username string
    }{
        username: "admin",
    }
    fmt.Printf("%+v\n", user)
    // {username:admin}
    fmt.Printf("%#v\n", user)
    // struct { username string }{username:"admin"}
    fmt.Printf("%v\n", user)
    // {admin}
    fmt.Println(user)
    // {admin}
}

场景:

  1. 定义一个临时结构体接收反序列化数据
  2. 内嵌结构体

  3. 匿名属性(或者应该叫啥,我也不知道)

  4. 内嵌类型不能重复,

  5. 前面的例子可以看出,可以直接访问匿名属性(结构体)内部属性,
  6. 但是如果同名就只能老老实实通过类型名字来访问了
package main

import (
    "fmt"
)

type Book struct {
    string // book name (anno field)

    // syntax error: unexpected [, expected field name or embedded type
    // []Author
    Authors []Author // slice of authors

    Publisher
}

type Author struct {
    string // author name (anno field)
}

type Publisher struct {
    string // publisher name (anno field)
}

func main() {
    p := Book{
        string: "Learning Python",
        Authors: []Author{
            {string: "Stanley B.Lippman"},
            {string: "Josée LaJoie"},
            {"Barbara E.Moo"},
        },
        Publisher: Publisher{"人民邮电出版社"},
    }
    p.string = "C++ Primer" // change attr

    fmt.Println(p)
    fmt.Printf("%v\n", p)
    fmt.Printf("%+v\n", p)
    fmt.Printf("%#v\n", p)
    fmt.Printf("%v\n", p.string)
    fmt.Printf("%v\n", p.Authors[0].string)
    fmt.Printf("%v\n", p.Publisher)
    fmt.Printf("%v\n", p.Publisher.string)
}

// {C++ Primer [{Stanley B.Lippman} {Josée LaJoie} {Barbara E.Moo}] {人民邮电出版社}}
// {C++ Primer [{Stanley B.Lippman} {Josée LaJoie} {Barbara E.Moo}] {人民邮电出版社}}
// {string:C++ Primer Authors:[{string:Stanley B.Lippman} {string:Josée LaJoie} {string:Barbara E.Moo}] Publisher:{string:人民邮电出版社}}
// main.Book{string:"C++ Primer", Authors:[]main.Author{main.Author{string:"Stanley B.Lippman"}, main.Author{string:"Josée LaJoie"}, main.Author{string:"Barbara E.Moo"}}, Publisher:main.Publisher{string:"人民邮电出版社"}}
// C++ Primer
// Stanley B.Lippman
// {人民邮电出版社}
// 人民邮电出版社
  1. 将函数通过属性的方式定义
type FoodNameGetter func(string) string

type Food struct {
    name   string
    getter FoodNameGetter // declare function
}

pizza := Food{
    name: "Pizza",
    getter: func(name string) string { // declare function body
        return "This is " + name + "."
    },
}
  1. 结构体比较

如果类型和值相同,就等于。

package main

import "fmt"

type Teacher struct {
    name string
}

type Student struct {
    name string
}

func main() {
    s1 := Student{"John"}
    s2 := Student{"John"}
    fmt.Println(s1 == s2) // true

    // invalid operation: s1 == t1 (mismatched types Student and Teacher)
    // t1 := Teacher{"John"}
    // fmt.Println(s1 == t1)
}

参考资料与拓展阅读

#447 Go 几个特殊用法

2020-12-18

点后直接括号

  1. type assertion 类型断言
  2. type switch 类型判断

Golang 是强类型的语言,这两种语法都是只对 interface{} 有效。

package main

import "fmt"

func test(value interface{}) {
    switch value.(type) {
    case string:
        fmt.Printf("%#v\n", value)
    }
}

func test2(value interface{}) {
    v1, ok := value.([]byte)
    fmt.Printf("%#v, %#v\n", v1, ok)

    v2, ok := value.(string)
    fmt.Printf("%#v, %#v\n", v2, ok)
}

func main() {
    a := "abc"
    test(a)
    test2(a)
}

#445 Golang 数组操作

2020-12-15

声明

# array
[length]Type
[length]Type{v1, v2, ..., vn}
[...]Type{v1, v2, ..., vn}

# slice
[]Type
[]Type{v1, v2, ..., vn}
make([]Type, length)
make([]Type, length, capacity)

我想尝试动态创建数组(通过 arrLen 变量),没有找到方法,可能这就是静态类型语言吧。
所以,数组必须在写代码的时候就明确长度。否则就应该用切片。

此外,数组应该算是值类型。赋值的时候,会完全复制一份,传参也是一样。

Slice 等于数组指针 + 长度 + 容量。

package main

import "fmt"

func main() {
    var a []int
    fmt.Printf("%#v \t%d %d\n", a, len(a), cap(a)) // []int(nil) 0 0

    b := []int{1, 2, 3}
    fmt.Printf("%#v \t%d %d\n", b, len(b), cap(b)) // []int{1, 2, 3} 3 3

    var c [3]int
    fmt.Printf("%#v \t%d %d\n", c, len(c), cap(c)) // [3]int{0, 0, 0} 3 3

    d := [...]int{1, 2, 3}
    fmt.Printf("%#v \t%d %d\n", d, len(d), cap(d)) // [3]int{1, 2, 3} 3 3

    d2 := [...]int{3: 100}
    fmt.Printf("%#v \t%d %d\n", d2, len(d2), cap(d2)) // [4]int{0, 0, 0, 100} 4 4

    e := d[:]
    fmt.Printf("%#v \t%d %d\n", e, len(e), cap(e)) // []int{1, 2, 3} 3 3

    // 动态创建切片
    f := make([]int, 3, 10)
    fmt.Printf("%#v \t%d %d\n", f, len(f), cap(f)) // []int{0, 0, 0} 3 10

    // new 返回指针
    g := new([]int)
    fmt.Printf("%#v \t%d %d\n", g, len(*g), cap(*g)) // &[]int(nil) 0 0

    h := new([3]int)
    fmt.Printf("%#v \t%d %d\n", h, len(*h), cap(*h)) // &[]int{0, 0, 0} 3 3

    type User struct {
        Username string
        Password string
    }
    users := []User{
        {"a", "pass1"},
        {"b", "pass2"},  // 这样分行写的话,不能省略这个逗号
    }
    fmt.Printf("%+v \t%d %d\n", users, len(users), cap(users))
    // [{Username:a Password:pass1} {Username:b Password:pass2}] 2 2
}

索引

a[n]
a[n:m]
a[n:]
a[:m]
a[:]

// 索引为负数
// invalid argument: index -1 (constant of type int) must not be negative

// 索引越界
// invalid argument: array index 10 out of bounds [0:5]

遍历

for i := 0; i < len(arr); i++ {
    val := arr[i]
    // ...
}

for i, val := range arrayVar {
    // ...
}

// 如果只有一个变量接收遍历的值,那么这个变量将是 index
// 这个地方总是容易写错,要小心
for i := range arrayVar {
    val := arrayVar[i]
    // ...
}

复制

s1 := []int{1, 3, 6}
s2 := append([]int{}, s1...)
fmt.Printf("%#v %p\n", s1, s1)
fmt.Printf("%#v %p\n", s2, s2)

s3 := make([]int, len(s1))
copy(s3, s1)
fmt.Printf("%#v %p\n", s3, s3)

注意:copy 复制内容的长度以 src,dst 中较小的长度为准。

删除

a := []int{1, 2, 3, 4, 5, 6, 7}
// del a[3]
a = append(a[:3], a[4:]...)

插入

// 在尾部插入一个
a = append(a, ele)

// 在尾部插入一个数组或切片
a = append(a, arr...)

// copy 也是一种方法
a := [10]int{1, 2, 3}
b := []int{4, 5, 6}
copy(a[3:], b)

// WRONG!
a := []int{1, 2, 3, 5, 6, 7}
tmp := a[:3]
tmp = append(tmp, 4)
fmt.Println(a)  // [1 2 3 4 6 7]

func insert(slice []int, index int, num int) []int {
    rest_len := len(slice) - index
    tmp := make([]int, rest_len, len(slice)+1)
    copy(tmp, slice[:index])
    tmp[index] = num
    tmp = append(tmp, slice[index:]...)
    return tmp
}

弹出

数组长度固定无法执行“弹出”操作,但是可以删除其中的一个节点。

nums := []int{1, 2, 3, 4}
first := nums[0]
nums = nums[1:]
last := nums[len(nums) - 1]
nums := nums[:len(nums) - 1]

合并

package main

import "fmt"

func main() {
    a := []int{1, 2, 3}
    b := []int{1, 2, 3}
    c := [3]int{4, 5, 6}
    d := [3]int{4, 5, 6}

    // 切片合并
    e := append(a, b...)
    fmt.Printf("%#v, %#v, %#v\n", a, b, e)

    f := append(c[:], d[:]...)
    fmt.Printf("%#v, %#v, %#v\n", c, d, f)

    g := [6]int{}
    for i, v := range a {
        g[i] = v
    }
    length := len(a)
    for i, v := range a {
        g[i+length] = v
    }
    fmt.Printf("%#v\n", g)

    h := [6]int{}
    // func copy(dst, src []Type) int
    copy(h[:], a)
    fmt.Printf("%#v\n", h)
    copy(h[3:], a)
    fmt.Printf("%#v\n", h)
}

排序

package main

import (
    "fmt"
    "sort"
)

func main() {
    nums := []int{9, 5, 2, 7, 1}
    sort.Ints(nums)
    fmt.Println(nums)
}
  • sort.Strings
  • sort.Float64s

#444 godoc 命令

2020-12-11
godoc -http=:6060 -index -play -timestamps -v

# 如果在 go 模块目录执行,会列出本地依赖包的文档
# using module mode; GOMOD=xxx.mod

go doc fmt
go doc fmt.Println
go doc fmt Println
go doc -src fmt Println

# 查看本地包文档
go doc github.com/astaxie/beego
go doc github.com/astaxie/beego Listen
go doc -all github.com/astaxie/bee

PS: 关于 Go 项目文档

  1. 紧挨者 package, const, type, func 等关键字的注释

问题

C:\Users\admin>godoc -http=:6060 -index -play -timestamps -v
'godoc' 不是内部或外部命令,也不是可运行的程序
或批处理文件。

新版本不再自带 godoc 工具,需要另外安装:

go install golang.org/x/tools/cmd/godoc@latest

#442 Go 运算符

2020-12-09
  • 算术运算符 Arithmetic Operators
  • + - * / %
  • ++ --
  • 比较运算符 Comparison Operators
  • > < == != >= <=
  • 逻辑运算符 Logical Operators
  • && || !
  • 按位运算符 Bitwise Operators
  • & 按位与 bitwise AND
  • | 按位或 bitwise OR
  • ^ 按位取反 bitwise NOT / 按位异或 bitwise XOR
  • 注意:其他很多语言都是采用 ~ 表示取反
  • &^ bit clear (AND NOT)
  • << 左移,>> 右移
  • 赋值运算符 Assignment Operators
  • = 以及算术运算赋值,位运算赋值
  • 其他运算符 Misc Operators
  • * 指针,& 取地址,<- 通道接收

Operator precedence

| 优先级 | 运算符 |
| ------ | ------------------------------ | ----- | --- |
| 5 | * / % << >> & &^ |
| 4 | + - | ^ |
| 3 | 关系运算符 |
| 2 | && |
| 1 | | | |

示例

package main

import "fmt"

func main() {
    var a uint8 = 0b_0011_1100 // 60
    var b uint8 = 0b_0000_1111 // 15

    fmt.Printf("      a %08b (%d)\n", a, a)
    fmt.Printf("      b %08b (%d)\n", b, b)
    fmt.Println()

    fmt.Printf("    NOT %08b 非(NOT a)\n", (^a))
    fmt.Println()

    fmt.Printf("    AND %08b 与\n", (a & b))
    fmt.Printf("     OR %08b 或\n", (a | b))
    fmt.Printf("    XOR %08b 异或\n", (a ^ b))
    fmt.Println()

    fmt.Printf("   NAND %08b 与非\n", ^(a & b))
    fmt.Printf("    NOR %08b 或非\n", ^(a | b))
    fmt.Printf("   XNOR %08b 同或(异或非)\n", ^(a ^ b))
    fmt.Println()

    fmt.Printf("AND NOT %08b BitClear\n", (a &^ b))
    // fmt.Printf("AND NOT %08b BitClear\n", (a & (^b)))

    // 格式化工具会将 ^ 和右操作数放在一起
    // 这一点和 &^ 不同,应该是由于 Golang Spec 中,将 &^ 当成一个操作符
    // 本质上,没有什么区别,都是先按位取反,再按位与或
    fmt.Printf(" OR NOT %08b Useless\n", (a | ^b))
    // fmt.Printf(" OR NOT %08b Useless\n", (a | (^b)))
}
//       a 00111100 (60)
//       b 00001111 (15)

//     NOT 11000011 非(NOT a)

//     AND 00001100 与
//      OR 00111111 或
//     XOR 00110011 异或

//    NAND 11110011 与非
//     NOR 11000000 或非
//    XNOR 11001100 同或(异或非)

// AND NOT 00110000 BitClear
//  OR NOT 11111100 Useless

参考资料与拓展阅读

#441 Golang strings.Builder

2020-12-08

https://github.com/golang/go/blob/master/src/strings/builder.go

主要的作用是频繁拼接字符串,相比字符串拼接操作符(+)或者 fmt.SPrintf 效率更高。

实例

package main

import (
    "fmt"
    "strings"
)

func main() {
    var builder strings.Builder
    builder.WriteString("Hello")
    builder.WriteString(", ")
    builder.WriteString("World!")
    result := builder.String()
    fmt.Println(result)
}

方法

  • func (b *Builder) String() string
  • func (b *Builder) Len() int { return len(b.buf)
  • func (b *Builder) Cap() int { return cap(b.buf)
  • func (b *Builder) Reset() 内容重置
  • func (b *Builder) Grow(n int) 预分配空间,正确使用可以提升程序执行效率
  • func (b *Builder) Write(p []byte) (int, error)
  • func (b *Builder) WriteByte(c byte) error
  • func (b *Builder) WriteRune(r rune) (int, error)
  • func (b *Builder) WriteString(s string) (int, error)

源码

实际上就是针对 []byte 的一个封装。

// A Builder is used to efficiently build a string using Write methods.
// It minimizes memory copying. The zero value is ready to use.
// Do not copy a non-zero 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")
    }
}

// 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
}
  • addr 指向自己的指针,不知道干什么用
  • buf 是 buffer,缓冲区,用于存储 build 出来的字符串
  • 通过缓冲区做存储,避免频繁创建字符串对象,减少内存分配和垃圾回收

性能测试

BenchmarkStrPlus-20             100000000               11.11 ns/op            0 B/op          0 allocs/op
BenchmarkSprintf-20             12129922               101.5 ns/op            48 B/op          3 allocs/op
BenchmarkStrJoin-20             45257742                24.47 ns/op           16 B/op          1 allocs/op
BenchmarkStrBuilder-20          38868284                36.31 ns/op           24 B/op          2 allocs/op
BenchmarkByteBuf-20             22016934                53.34 ns/op           80 B/op          2 allocs/op
BenchmarkByteSlice-20           31495318                36.13 ns/op           24 B/op          2 allocs/op
BenchmarkByteSlice2-20          39563612                32.18 ns/op           24 B/op          2 allocs/op

BenchmarkStrPlusBatch-20         7331364               170.0 ns/op           112 B/op          6 allocs/op
BenchmarkSprintfBatch-20         1481492               823.2 ns/op           328 B/op         20 allocs/op
BenchmarkStrJoinBatch-20        19598844                59.78 ns/op           32 B/op          1 allocs/op
BenchmarkStrBuilderBatch-20     13560961                87.39 ns/op           56 B/op          3 allocs/op
BenchmarkByteBufBatch-20        10693280                97.61 ns/op           96 B/op          2 allocs/op
BenchmarkByteSliceBatch-20      12115166                95.27 ns/op           56 B/op          3 allocs/op
BenchmarkByteSlice2Batch-20     13136418                90.53 ns/op           56 B/op          3 allocs/op

fmt.Sprintf 用来做字符串拼接的话,性能总是最差。
此外,如果只是简单的两个字符串相加,直接加号拼接性能最好。
否则就根据情况,选用其他方案比较好。