Go Examples
{Back to Index}  

Table of Contents

1. 基础语法与类型

1.1. Hello World

这是 Go 的最小程序:使用 package main 声明可执行程序,导入 fmt=,并在 =main 中调用 fmt.Println 输出文本;可以用 go run 直接运行,也可以用 go build 生成独立二进制文件,体现 Go 从源码到可执行程序的基本流程。

package main
import "fmt"
func main() {
    fmt.Println("hello world")
}

1.2. Values

Go 直接支持字符串、整数、浮点数和布尔值等基本类型;字符串可以用 + 拼接,数值可以参与算术运算,布尔值可以使用 &&=、=||! 等逻辑运算。这个示例重点是熟悉字面量和基本值,而不是展示复杂的类型转换。

package main
import "fmt"
func main() {
    fmt.Println("go" + "lang")
    fmt.Println("1+1 =", 1+1)
    fmt.Println("7.0/3.0 =", 7.0/3.0)
    fmt.Println(true && false)
    fmt.Println(true || false)
    fmt.Println(!true)
}

1.3. Variables

变量可以用 var 显式声明,也可以在函数内部使用 := 同时声明和初始化;Go 编译器会检查变量类型,未显式初始化的变量得到对应类型的零值。需要注意,短变量声明只能在函数体内使用,并且至少要声明一个新变量。

package main
import "fmt"
func main() {
    var a = "initial"
    fmt.Println(a)
    var b, c int = 1, 2
    fmt.Println(b, c)
    var d = true
    fmt.Println(d)
    var e int
    fmt.Println(e)
    f := "apple"
    fmt.Println(f)
}

1.4. Constants

常量使用 const 声明,可以是字符、字符串、布尔值或数值;常量表达式支持高精度计算,而且无类型数值常量会在赋值或传参时根据上下文获得类型。常量适合表示编译期不会变化的配置和枚举值。

package main
import (
    "fmt"
    "math"
)
const s string = "constant"
func main() {
    fmt.Println(s)
    const n = 500000000
    const d = 3e20 / n
    fmt.Println(d)
    fmt.Println(int64(d))
    fmt.Println(math.Sin(n))
}

1.5. For

Go 只有 for 这一种循环关键字,但它可以写成传统的初始化、条件、递增形式,也可以省略部分内容实现类似 while 的循环,甚至省略全部内容形成无限循环;=break= 和 continue 用于控制循环流程。遍历集合时通常会使用后面的 range 形式。

package main
import "fmt"
func main() {
    i := 1
    for i <= 3 {
        fmt.Println(i)
        i = i + 1
    }
    for j := 0; j < 3; j++ {
        fmt.Println(j)
    }
    for i := range 3 {
        fmt.Println("range", i)
    }
    for {
        fmt.Println("loop")
        break
    }
    for n := range 6 {
        if n%2 == 0 {
            continue
        }
        fmt.Println(n)
    }
}

1.6. If/Else

条件分支使用 if 和 =else=,条件表达式不需要括号;Go 还允许在条件前放一个只在该分支作用域内有效的初始化语句。多个条件可以使用 =else if=,但条件必须是布尔表达式,不能把整数等值直接当作真值使用。

package main
import "fmt"
func main() {
    if 7%2 == 0 {
        fmt.Println("7 is even")
    } else {
        fmt.Println("7 is odd")
    }
    if 8%4 == 0 {
        fmt.Println("8 is divisible by 4")
    }
    if 8%2 == 0 || 7%2 == 0 {
        fmt.Println("either 8 or 7 are even")
    }
    if num := 9; num < 0 {
        fmt.Println(num, "is negative")
    } else if num < 10 {
        fmt.Println(num, "has 1 digit")
    } else {
        fmt.Println(num, "has multiple digits")
    }
}

1.7. Switch

switch 用于表达多分支条件,普通形式按值匹配,也可以在一个 case 中写多个表达式;省略表达式时,它可以代替一串 =if/else=。类型 switch 则用于判断接口值的动态类型,适合处理一个接口可能承载多种具体类型的情况。

package main
import (
    "fmt"
    "time"
)
func main() {
    i := 2
    fmt.Print("Write ", i, " as ")
    switch i {
    case 1:
        fmt.Println("one")
    case 2:
        fmt.Println("two")
    case 3:
        fmt.Println("three")
    }
    switch time.Now().Weekday() {
    case time.Saturday, time.Sunday:
        fmt.Println("It's the weekend")
    default:
        fmt.Println("It's a weekday")
    }
    t := time.Now()
    switch {
    case t.Hour() < 12:
        fmt.Println("It's before noon")
    default:
        fmt.Println("It's after noon")
    }
    whatAmI := func(i any) {
        switch t := i.(type) {
        case bool:
            fmt.Println("I'm a bool")
        case int:
            fmt.Println("I'm an int")
        default:
            fmt.Printf("Don't know type %T\n", t)
        }
    }
    whatAmI(true)
    whatAmI(1)
    whatAmI("hey")
}

1.8. Arrays

数组是长度固定且长度属于类型的一组同类型元素,未初始化元素使用零值;可以按下标访问,也可以用字面量、=…= 自动推导长度和带下标的初始化方式创建。数组可以组合成多维数组,但日常 Go 代码中更常使用长度可变的 slice。

package main
import "fmt"
func main() {
    var a [5]int
    fmt.Println("emp:", a)
    a[4] = 100
    fmt.Println("set:", a)
    fmt.Println("get:", a[4])
    fmt.Println("len:", len(a))
    b := [5]int{1, 2, 3, 4, 5}
    fmt.Println("dcl:", b)
    b = [...]int{1, 2, 3, 4, 5}
    fmt.Println("dcl:", b)
    b = [...]int{100, 3: 400, 500}
    fmt.Println("idx:", b)
    var twoD [2][3]int
    for i := range 2 {
        for j := range 3 {
            twoD[i][j] = i + j
        }
    }
    fmt.Println("2d: ", twoD)
    twoD = [2][3]int{
        {1, 2, 3},
        {1, 2, 3},
    }
    fmt.Println("2d: ", twoD)
}

1.9. Slices

slice 是建立在底层数组之上的动态视图,包含指针、长度和容量;可以用字面量或 make 创建,用切片表达式截取,并用 append 增长。slice 共享底层数组,扩容时可能更换底层存储,因此传递和修改 slice 时要区分“视图变化”和“底层数组变化”。

package main
import (
    "fmt"
    "slices"
)
func main() {
    var s []string
    fmt.Println("uninit:", s, s == nil, len(s) == 0)
    s = make([]string, 3)
    fmt.Println("emp:", s, "len:", len(s), "cap:", cap(s))
    s[0] = "a"
    s[1] = "b"
    s[2] = "c"
    fmt.Println("set:", s)
    fmt.Println("get:", s[2])
    fmt.Println("len:", len(s))
    s = append(s, "d")
    s = append(s, "e", "f")
    fmt.Println("apd:", s)
    c := make([]string, len(s))
    copy(c, s)
    fmt.Println("cpy:", c)
    l := s[2:5]
    fmt.Println("sl1:", l)
    l = s[:5]
    fmt.Println("sl2:", l)
    l = s[2:]
    fmt.Println("sl3:", l)
    t := []string{"g", "h", "i"}
    fmt.Println("dcl:", t)
    t2 := []string{"g", "h", "i"}
    if slices.Equal(t, t2) {
        fmt.Println("t == t2")
    }
    twoD := make([][]int, 3)
    for i := range 3 {
        innerLen := i + 1
        twoD[i] = make([]int, innerLen)
        for j := range innerLen {
            twoD[i][j] = i + j
        }
    }
    fmt.Println("2d: ", twoD)
}

1.10. Maps

map 是键到值的无序映射,使用 make 或字面量创建,通过 m[key]=value 写入、下标读取和 delete 删除;读取时可以用双返回值形式判断键是否存在。遍历 map 的顺序不固定,因此不能依赖 range 的输出顺序。

package main
import (
    "fmt"
    "maps"
)
func main() {
    m := make(map[string]int)
    m["k1"] = 7
    m["k2"] = 13
    fmt.Println("map:", m)
    v1 := m["k1"]
    fmt.Println("v1:", v1)
    v3 := m["k3"]
    fmt.Println("v3:", v3)
    fmt.Println("len:", len(m))
    delete(m, "k2")
    fmt.Println("map:", m)
    clear(m)
    fmt.Println("map:", m)
    _, prs := m["k2"]
    fmt.Println("prs:", prs)
    n := map[string]int{"foo": 1, "bar": 2}
    fmt.Println("map:", n)
    n2 := map[string]int{"foo": 1, "bar": 2}
    if maps.Equal(n, n2) {
        fmt.Println("n == n2")
    }
}

1.11. Functions

函数使用 func 声明,可以接收参数并返回结果;Go 函数是一等值,能够赋给变量、作为参数传递或作为返回值。函数参数和返回值的类型必须明确,多个参数可以共享类型声明,良好的函数拆分有助于控制程序复杂度。

package main
import "fmt"
func plus(a int, b int) int {
    return a + b
}
func plusPlus(a, b, c int) int {
    return a + b + c
}
func main() {
    res := plus(1, 2)
    fmt.Println("1+2 =", res)
    res = plusPlus(1, 2, 3)
    fmt.Println("1+2+3 =", res)
}

1.12. Multiple Return Values

Go 函数可以返回多个值,常用于同时返回计算结果和错误;调用者可以把多个结果分别赋给变量,也可以使用空白标识符 _ 忽略其中一项。这种设计让错误处理显式出现在调用点,是 Go API 中非常常见的习惯。

package main
import "fmt"
func vals() (int, int) {
    return 3, 7
}
func main() {
    a, b := vals()
    fmt.Println(a)
    fmt.Println(b)
    _, c := vals()
    fmt.Println(c)
}

1.13. Variadic Functions

可变参数函数在参数类型前使用 ...=,调用时可以传入任意数量的尾部参数;函数内部它表现为一个 slice,也可以用 =slice... 把已有 slice 展开传入。=fmt.Println= 等标准库函数就是这种设计的典型例子。

package main
import "fmt"
func sum(nums ...int) {
    fmt.Print(nums, " ")
    total := 0
    for _, num := range nums {
        total += num
    }
    fmt.Println(total)
}
func main() {
    sum(1, 2)
    sum(1, 2, 3)
    nums := []int{1, 2, 3, 4}
    sum(nums...)
}

1.14. Closures

Go 支持匿名函数,匿名函数如果引用外部变量,就会形成闭包并保存这部分词法环境;因此函数可以携带自己的状态。多个调用同一个闭包会共享它捕获的变量,而多次创建的闭包通常拥有彼此独立的状态。

package main
import "fmt"
func intSeq() func() int {
    i := 0
    return func() int {
        i++
        return i
    }
}
func main() {
    nextInt := intSeq()
    fmt.Println(nextInt())
    fmt.Println(nextInt())
    fmt.Println(nextInt())
    newInts := intSeq()
    fmt.Println(newInts())
}

1.15. Recursion

递归函数会在函数体内调用自身,适合表达树遍历、阶乘或分治等问题;递归必须有明确的终止条件,否则会不断消耗栈空间。Go 的函数可以直接递归调用,也可以通过闭包或函数变量组织更复杂的递归逻辑。

package main
import "fmt"
func fact(n int) int {
    if n == 0 {
        return 1
    }
    return n * fact(n-1)
}
func main() {
    fmt.Println(fact(7))
    var fib func(n int) int
    fib = func(n int) int {
        if n < 2 {
            return n
        }
        return fib(n-1) + fib(n-2)
    }
    fmt.Println(fib(7))
}

1.16. Range over Built-in Types

range 可以遍历数组、slice、map、字符串和 channel,并根据被遍历对象返回索引、键、值或 rune;如果只需要一个结果,可以使用 _ 忽略另一个结果。遍历 map 的顺序不保证,遍历字符串时得到的是 UTF-8 解码后的 rune 及其字节偏移。

package main
import "fmt"
func main() {
    nums := []int{2, 3, 4}
    sum := 0
    for _, num := range nums {
        sum += num
    }
    fmt.Println("sum:", sum)
    for i, num := range nums {
        if num == 3 {
            fmt.Println("index:", i)
        }
    }
    kvs := map[string]string{"a": "apple", "b": "banana"}
    for k, v := range kvs {
        fmt.Printf("%s -> %s\n", k, v)
    }
    for k := range kvs {
        fmt.Println("key:", k)
    }
    for i, c := range "go" {
        fmt.Println(i, c)
    }
}

1.17. Pointers

指针保存变量的地址,使用 & 取地址、=*= 解引用;函数接收指针后可以修改调用方的变量,也可以用指针表达“没有值”的状态。Go 允许返回局部变量的指针,因为垃圾回收器会保证仍被引用的对象继续存活,但应避免不必要的指针共享。

package main
import "fmt"
func zeroval(ival int) {
    ival = 0
}
func zeroptr(iptr *int) {
    *iptr = 0
}
func main() {
    i := 1
    fmt.Println("initial:", i)
    zeroval(i)
    fmt.Println("zeroval:", i)
    zeroptr(&i)
    fmt.Println("zeroptr:", i)
    fmt.Println("pointer:", &i)
    p := new(42)
    fmt.Println("value at *p:", *p)
    zeroptr(p)
    fmt.Println("value at *p:", *p)
}

1.18. Strings and Runes

Go 的 string 是只读的字节序列,通常保存 UTF-8 文本;一个用户感知的字符可能占多个字节,因此字符串下标得到的是单个字节,而 range 遍历得到的是 rune。需要按 Unicode 字符计数或处理非法 UTF-8 时,应使用 unicode/utf8 等相关 API。

package main
import (
    "fmt"
    "unicode/utf8"
)
func main() {
    const s = "สวัสดี"
    fmt.Println("Len:", len(s))
    for i := 0; i < len(s); i++ {
        fmt.Printf("%x ", s[i])
    }
    fmt.Println()
    fmt.Println("Rune count:", utf8.RuneCountInString(s))
    for idx, runeValue := range s {
        fmt.Printf("%#U starts at %d\n", runeValue, idx)
    }
    fmt.Println("\nUsing DecodeRuneInString")
    for i, w := 0, 0; i < len(s); i += w {
        runeValue, width := utf8.DecodeRuneInString(s[i:])
        fmt.Printf("%#U starts at %d\n", runeValue, i)
        w = width
        examineRune(runeValue)
    }
}
func examineRune(r rune) {
    if r == 't' {
        fmt.Println("found tee")
    } else if r == 'ส' {
        fmt.Println("found so sua")
    }
}

1.19. Structs

struct 是由多个有名字字段组成的类型,适合表达记录和领域对象;可以使用位置或字段名初始化,也可以通过指针访问字段,编译器会自动完成常见的指针解引用。构造函数通常只是命名约定,例如 =newPerson=,并不是语言内置的特殊语法。

package main
import "fmt"
type person struct {
    name string
    age  int
}
func newPerson(name string) *person {
    p := person{name: name}
    p.age = 42
    return &p
}
func main() {
    fmt.Println(person{"Bob", 20})
    fmt.Println(person{name: "Alice", age: 30})
    fmt.Println(person{name: "Fred"})
    fmt.Println(&person{name: "Ann", age: 40})
    fmt.Println(newPerson("Jon"))
    s := person{name: "Sean", age: 50}
    fmt.Println(s.name)
    sp := &s
    fmt.Println(sp.age)
    sp.age = 51
    fmt.Println(sp.age)
    dog := struct {
        name   string
        isGood bool
    }{
        "Rex",
        true,
    }
    fmt.Println(dog)
}

1.20. Methods

方法是带有 receiver 的函数,可以绑定到自定义类型上;值 receiver 会复制接收者,指针 receiver 则可以修改原对象并避免复制较大的结构。方法集合还会影响一个类型是否满足接口,因此选择值 receiver 还是指针 receiver 时要考虑接口和可变性。

package main
import "fmt"
type rect struct {
    width, height int
}
func (r *rect) area() int {
    return r.width * r.height
}
func (r rect) perim() int {
    return 2*r.width + 2*r.height
}
func main() {
    r := rect{width: 10, height: 5}
    fmt.Println("area: ", r.area())
    fmt.Println("perim:", r.perim())
    rp := &r
    fmt.Println("area: ", rp.area())
    fmt.Println("perim:", rp.perim())
}

1.21. Interfaces

接口只描述方法集合,类型不需要显式声明实现某个接口,只要方法集合匹配就自动满足;这使调用方可以依赖行为而不是具体类型。接口值包含动态类型和值,空接口可以承载任意值,但使用时应注意类型断言失败和 nil 接口的区别。

package main
import (
    "fmt"
    "math"
)
type geometry interface {
    area() float64
    perim() float64
}
type rect struct {
    width, height float64
}
type circle struct {
    radius float64
}
func (r rect) area() float64 {
    return r.width * r.height
}
func (r rect) perim() float64 {
    return 2*r.width + 2*r.height
}
func (c circle) area() float64 {
    return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
    return 2 * math.Pi * c.radius
}
func measure(g geometry) {
    fmt.Println(g)
    fmt.Println(g.area())
    fmt.Println(g.perim())
}
func detectCircle(g geometry) {
    if c, ok := g.(circle); ok {
        fmt.Println("circle with radius", c.radius)
    }
}
func main() {
    r := rect{width: 3, height: 4}
    c := circle{radius: 5}
    measure(r)
    measure(c)
    detectCircle(r)
    detectCircle(c)
}

1.22. Enums

Go 没有独立的 enum 关键字,通常使用自定义整数类型、常量和 iota 组合实现有限状态集合;实现 fmt.Stringer 可以让状态打印成可读文本。自定义类型还能阻止把普通整数直接传入状态转换函数,从而获得一定的编译期类型安全。

package main
import "fmt"
type ServerState int
const (
    StateIdle ServerState = iota
    StateConnected
    StateError
    StateRetrying
)
var stateName = map[ServerState]string{
    StateIdle:      "idle",
    StateConnected: "connected",
    StateError:     "error",
    StateRetrying:  "retrying",
}
func (ss ServerState) String() string {
    return stateName[ss]
}
func main() {
    ns := transition(StateIdle)
    fmt.Println(ns)
    ns2 := transition(ns)
    fmt.Println(ns2)
}
func transition(s ServerState) ServerState {
    switch s {
    case StateIdle:
        return StateConnected
    case StateConnected, StateRetrying:
        return StateIdle
    case StateError:
        return StateError
    default:
        panic(fmt.Errorf("unknown state: %s", s))
    }
}

1.23. Struct Embedding

struct embedding 通过不带字段名的嵌入字段组合类型,嵌入类型的字段和方法可以被外层类型直接访问,方法集也可能因此满足某个接口。它是组合而不是传统继承;不要把它和 //go:embed 文件嵌入指令混淆。

package main
import "fmt"
type base struct {
    num int
}
func (b base) describe() string {
    return fmt.Sprintf("base with num=%v", b.num)
}
type container struct {
    base
    str string
}
func main() {
    co := container{
        base: base{
            num: 1,
        },
        str: "some name",
    }
    fmt.Printf("co={num: %v, str: %v}\n", co.num, co.str)
    fmt.Println("also num:", co.base.num)
    fmt.Println("describe:", co.describe())
    type describer interface {
        describe() string
    }
    var d describer = co
    fmt.Println("describer:", d.describe())
}

1.24. Generics

泛型允许函数和类型声明类型参数,从而在保持静态类型检查的同时复用适用于多种类型的算法;类型约束可以限制类型参数支持的操作。泛型适合抽象真正具有相同逻辑的代码,不应为了消除少量重复而牺牲可读性。

package main
import "fmt"
func SlicesIndex[S ~[]E, E comparable](s S, v E) int {
    for i := range s {
        if v == s[i] {
            return i
        }
    }
    return -1
}
type List[T any] struct {
    head, tail *element[T]
}
type element[T any] struct {
    next *element[T]
    val  T
}
func (lst *List[T]) Push(v T) {
    if lst.tail == nil {
        lst.head = &element[T]{val: v}
        lst.tail = lst.head
    } else {
        lst.tail.next = &element[T]{val: v}
        lst.tail = lst.tail.next
    }
}
func (lst *List[T]) AllElements() []T {
    var elems []T
    for e := lst.head; e != nil; e = e.next {
        elems = append(elems, e.val)
    }
    return elems
}
func main() {
    var s = []string{"foo", "bar", "zoo"}
    fmt.Println("index of zoo:", SlicesIndex(s, "zoo"))
    _ = SlicesIndex[[]string, string](s, "zoo")
    lst := List[int]{}
    lst.Push(10)
    lst.Push(13)
    lst.Push(23)
    fmt.Println("list:", lst.AllElements())
}

1.25. Range over Iterators

Go 支持让 range 遍历迭代器函数,迭代器可以通过回调逐步产生一个值或键值对;调用者不必显式管理迭代状态。这个模式适合懒加载、树遍历和流式数据处理,但迭代器的终止、错误传播和提前退出需要在设计时明确。

package main
import (
    "fmt"
    "iter"
    "slices"
    "strings"
)
type List[T any] struct {
    head, tail *element[T]
}
type element[T any] struct {
    next *element[T]
    val  T
}
func (lst *List[T]) Push(v T) {
    if lst.tail == nil {
        lst.head = &element[T]{val: v}
        lst.tail = lst.head
    } else {
        lst.tail.next = &element[T]{val: v}
        lst.tail = lst.tail.next
    }
}
func (lst *List[T]) All() iter.Seq[T] {
    return func(yield func(T) bool) {
        for e := lst.head; e != nil; e = e.next {
            if !yield(e.val) {
                return
            }
        }
    }
}
func genFib() iter.Seq[int] {
    return func(yield func(int) bool) {
        a, b := 0, 1
        for {
            if !yield(a) {
                return
            }
            a, b = b, a+b
        }
    }
}
func main() {
    lst := List[int]{}
    lst.Push(10)
    lst.Push(13)
    lst.Push(23)
    for e := range lst.All() {
        fmt.Println(e)
    }
    all := slices.Collect(lst.All())
    fmt.Println("all:", all)
    for part := range strings.SplitSeq("go-by-example", "-") {
        fmt.Printf("part: %s\n", part)
    }
    for n := range genFib() {
        if n >= 10 {
            break
        }
        fmt.Println(n)
    }
}

1.26. Errors

Go 通常把错误作为函数的最后一个返回值,成功时返回 nil=;可以用 =errors.New 创建错误,用 fmt.Errorf("%w") 包装错误,并通过 errors.Is 判断错误链中是否包含特定错误。错误处理应在靠近产生和调用的位置完成,而不是依赖异常机制把控制流跳到远处。

package main
import (
    "errors"
    "fmt"
)
func f(arg int) (int, error) {
    if arg == 42 {
        return -1, errors.New("can't work with 42")
    }
    return arg + 3, nil
}
var ErrOutOfTea = errors.New("no more tea available")
var ErrPower = errors.New("can't boil water")
func makeTea(arg int) error {
    if arg == 2 {
        return ErrOutOfTea
    } else if arg == 4 {
        return fmt.Errorf("making tea: %w", ErrPower)
    }
    return nil
}
func main() {
    for _, i := range []int{7, 42} {
        if r, e := f(i); e != nil {
            fmt.Println("f failed:", e)
        } else {
            fmt.Println("f worked:", r)
        }
    }
    for i := range 5 {
        if err := makeTea(i); err != nil {
            if errors.Is(err, ErrOutOfTea) {
                fmt.Println("We should buy new tea!")
            } else if errors.Is(err, ErrPower) {
                fmt.Println("Now it is dark.")
            } else {
                fmt.Printf("unknown error: %s\n", err)
            }
            continue
        }
        fmt.Println("Tea is ready!")
    }
}

1.27. Custom Errors

自定义错误类型只要实现 Error() string 就满足 error 接口,可以携带参数、状态码等结构化信息;通过 errors.AsType 或相关错误链 API 可以把错误恢复为具体类型。自定义错误应表达调用者确实需要处理的语义,普通描述信息则使用包装错误即可。

package main
import (
    "errors"
    "fmt"
)
type argError struct {
    arg     int
    message string
}
func (e *argError) Error() string {
    return fmt.Sprintf("%d - %s", e.arg, e.message)
}
func f(arg int) (int, error) {
    if arg == 42 {
        return -1, &argError{arg, "can't work with it"}
    }
    return arg + 3, nil
}
func main() {
    _, err := f(42)
    if ae, ok := errors.AsType[*argError](err); ok {
        fmt.Println(ae.arg)
        fmt.Println(ae.message)
    } else {
        fmt.Println("err doesn't match argError")
    }
}

2. 并发与同步

2.1. Goroutines

goroutine 是由 Go 运行时调度的轻量并发执行单元,只需在函数调用前加 go 就能异步运行;主 goroutine 结束时进程也会结束,因此示例使用延迟或同步手段等待工作完成。goroutine 很便宜但并非没有成本,必须设计好退出和资源释放路径。

package main
import (
    "fmt"
    "time"
)
func f(from string) {
    for i := range 3 {
        fmt.Println(from, ":", i)
    }
}
func main() {
    f("direct")
    go f("goroutine")
    go func(msg string) {
        fmt.Println(msg)
    }("going")
    time.Sleep(time.Second)
    fmt.Println("done")
}

2.2. Channels

channel 是 goroutine 之间传递类型化数据的管道,使用 ch <- value 发送、=value := <-ch= 接收;无缓冲 channel 的发送和接收会相互等待,因此天然提供同步点。channel 适合传递所有权和结果,但不应把所有共享状态都机械地改写成 channel。

package main
import "fmt"
func main() {
    messages := make(chan string)
    go func() { messages <- "ping" }()
    msg := <-messages
    fmt.Println(msg)
}

2.3. Channel Buffering

无缓冲 channel 只有在发送者和接收者同时准备好时才能完成发送,而带缓冲 channel 可以在容量范围内暂存数据;=make(chan T, n)= 创建容量为 n 的缓冲 channel。缓冲区只能吸收有限的突发流量,容量过大可能掩盖消费者变慢或 goroutine 泄漏问题。

package main
import "fmt"
func main() {
    messages := make(chan string, 2)
    messages <- "buffered"
    messages <- "channel"
    fmt.Println(<-messages)
    fmt.Println(<-messages)
}

2.4. Channel Synchronization

可以使用一个 channel 作为完成通知:工作 goroutine 完成后发送值,等待方通过接收操作阻塞直到收到通知。这个方式适合等待一个明确的异步事件;需要等待多个 goroutine 或传播错误时,通常应考虑 sync.WaitGroup 或 =errgroup=。

package main
import (
    "fmt"
    "time"
)
func worker(done chan bool) {
    fmt.Print("working...")
    time.Sleep(time.Second)
    fmt.Println("done")
    done <- true
}
func main() {
    done := make(chan bool, 1)
    go worker(done)
    <-done
}

2.5. Channel Directions

channel 类型可以限制为只发送的 chan<- T 或只接收的 =<-chan T=,作为函数参数时能够表达调用约束并获得编译期检查。把双向 channel 传给只读或只写参数是允许的,但反向操作会在编译期被拒绝。

package main
import "fmt"
func ping(pings chan<- string, msg string) {
    pings <- msg
}
func pong(pings <-chan string, pongs chan<- string) {
    msg := <-pings
    pongs <- msg
}
func main() {
    pings := make(chan string, 1)
    pongs := make(chan string, 1)
    ping(pings, "passed message")
    pong(pings, pongs)
    fmt.Println(<-pongs)
}

2.6. Select

select 同时等待多个 channel 操作,哪个 case 先准备好就执行哪个;它还可以配合 default 实现非阻塞尝试。多个 case 同时就绪时选择是伪随机的,因此不要把 case 顺序当作优先级;空的 select 会永久阻塞。

package main
import (
    "fmt"
    "time"
)
func main() {
    c1 := make(chan string)
    c2 := make(chan string)
    go func() {
        time.Sleep(1 * time.Second)
        c1 <- "one"
    }()
    go func() {
        time.Sleep(2 * time.Second)
        c2 <- "two"
    }()
    for range 2 {
        select {
        case msg1 := <-c1:
            fmt.Println("received", msg1)
        case msg2 := <-c2:
            fmt.Println("received", msg2)
        }
    }
}

2.7. Timeouts

使用 select 同时等待工作结果和 time.After 产生的超时事件,可以给外部调用设置明确的最长等待时间;示例对比了操作在 2 秒完成而超时时间分别为 1 秒和 3 秒的情况。超时后还要考虑后台 goroutine 是否能退出,避免结果无人接收导致泄漏。

package main
import (
    "fmt"
    "time"
)
func main() {
    c1 := make(chan string, 1)
    go func() {
        time.Sleep(2 * time.Second)
        c1 <- "result 1"
    }()
    select {
    case res := <-c1:
        fmt.Println(res)
    case <-time.After(1 * time.Second):
        fmt.Println("timeout 1")
    }
    c2 := make(chan string, 1)
    go func() {
        time.Sleep(2 * time.Second)
        c2 <- "result 2"
    }()
    select {
    case res := <-c2:
        fmt.Println(res)
    case <-time.After(3 * time.Second):
        fmt.Println("timeout 2")
    }
}

2.8. Non-Blocking Channel Operations

在 channel 的 select 中加入 =default=,就能在发送或接收当前无法立即完成时继续执行其他逻辑,从而实现非阻塞操作。它适合轮询、尝试发送和可选事件处理,但高频空转会消耗 CPU,必要时应加入定时器或退避。

package main
import "fmt"
func main() {
    messages := make(chan string)
    signals := make(chan bool)
    select {
    case msg := <-messages:
        fmt.Println("received message", msg)
    default:
        fmt.Println("no message received")
    }
    msg := "hi"
    select {
    case messages <- msg:
        fmt.Println("sent message", msg)
    default:
        fmt.Println("no message sent")
    }
    select {
    case msg := <-messages:
        fmt.Println("received message", msg)
    case sig := <-signals:
        fmt.Println("received signal", sig)
    default:
        fmt.Println("no activity")
    }
}

2.9. Closing Channels

关闭 channel 表示发送方不会再发送数据,接收方可以通过双返回值接收判断 channel 是否已经关闭;关闭后的 channel 仍可读取剩余数据,空且关闭的 channel 返回元素类型的零值。通常由发送方负责关闭,而且同一个 channel 不能重复关闭,否则会触发 panic。

package main
import "fmt"
func main() {
    jobs := make(chan int, 5)
    done := make(chan bool)
    go func() {
        for {
            j, more := <-jobs
            if more {
                fmt.Println("received job", j)
            } else {
                fmt.Println("received all jobs")
                done <- true
                return
            }
        }
    }()
    for j := 1; j <= 3; j++ {
        jobs <- j
        fmt.Println("sent job", j)
    }
    close(jobs)
    fmt.Println("sent all jobs")
    <-done
    _, ok := <-jobs
    fmt.Println("received more jobs:", ok)
}

2.10. Range over Channels

对 channel 使用 range 会持续接收数据,直到 channel 被关闭;这让生产者和消费者之间的循环更自然,也避免手动反复检查关闭状态。生产者必须最终关闭 channel,否则消费者会一直等待;关闭 channel 不会自动终止仍在运行的发送 goroutine。

package main
import "fmt"
func main() {
    queue := make(chan string, 2)
    queue <- "one"
    queue <- "two"
    close(queue)
    for elem := range queue {
        fmt.Println(elem)
    }
}

2.11. Timers

timer 表示未来只发生一次的事件,通过它的 C channel 等待触发;与 time.Sleep 不同,timer 可以在触发前使用 Stop 取消。需要复用 timer 时还要正确处理已触发事件和 channel 清空,否则可能读到旧事件或产生竞态。

package main
import (
    "fmt"
    "time"
)
func main() {
    timer1 := time.NewTimer(2 * time.Second)
    <-timer1.C
    fmt.Println("Timer 1 fired")
    timer2 := time.NewTimer(time.Second)
    go func() {
        <-timer2.C
        fmt.Println("Timer 2 fired")
    }()
    stop2 := timer2.Stop()
    if stop2 {
        fmt.Println("Timer 2 stopped")
    }
    time.Sleep(2 * time.Second)
}

2.12. Tickers

ticker 按固定间隔重复向 channel 发送时间事件,适合周期性任务;调用 Stop 后不会再产生新的 tick。ticker 持有运行时资源,任务结束时应显式停止,且不应把每次 tick 都当作一定会被逐个处理的可靠消息。

package main
import (
    "fmt"
    "time"
)
func main() {
    ticker := time.NewTicker(500 * time.Millisecond)
    done := make(chan bool)
    go func() {
        for {
            select {
            case <-done:
                return
            case t := <-ticker.C:
                fmt.Println("Tick at", t)
            }
        }
    }()
    time.Sleep(1600 * time.Millisecond)
    ticker.Stop()
    done <- true
    fmt.Println("Ticker stopped")
}

2.13. Worker Pools

worker pool 用多个 goroutine 从 jobs channel 取任务,再把结果写入 results channel;任务数量可以多于 worker 数量,而并发度由 worker 数量控制。关闭 jobs channel 表示没有新任务,收集所有结果也同时保证 worker 能够结束,适合批量任务和受控并发。

package main
import (
    "fmt"
    "time"
)
func worker(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        fmt.Println("worker", id, "started  job", j)
        time.Sleep(time.Second)
        fmt.Println("worker", id, "finished job", j)
        results <- j * 2
    }
}
func main() {
    const numJobs = 5
    jobs := make(chan int, numJobs)
    results := make(chan int, numJobs)
    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }
    for j := 1; j <= numJobs; j++ {
        jobs <- j
    }
    close(jobs)
    for a := 1; a <= numJobs; a++ {
        <-results
    }
}

2.14. WaitGroups

sync.WaitGroup 用于等待一组 goroutine 全部完成,启动任务前增加计数,任务结束时调用 Done=,主流程用 =Wait 阻塞等待。计数器必须在 goroutine 启动前正确增加,并且 WaitGroup 通常通过指针传递;它只负责等待,不负责传递错误或结果。

package main
import (
    "fmt"
    "sync"
    "time"
)
func worker(id int) {
    fmt.Printf("Worker %d starting\n", id)
    time.Sleep(time.Second)
    fmt.Printf("Worker %d done\n", id)
}
func main() {
    var wg sync.WaitGroup
    for i := 1; i <= 5; i++ {
        wg.Go(func() {
            worker(i)
        })
    }
    wg.Wait()
}

2.15. Rate Limiting

可以用 ticker 作为定时令牌源,对每次操作设置固定间隔,从而限制请求速率;也可以使用带缓冲 channel 模拟令牌桶,允许有限的突发请求。限速策略应区分单个 goroutine、整个进程和分布式系统三个范围,示例只展示进程内的基础机制。

package main
import (
    "fmt"
    "time"
)
func main() {
    requests := make(chan int, 5)
    for i := 1; i <= 5; i++ {
        requests <- i
    }
    close(requests)
    limiter := time.Tick(200 * time.Millisecond)
    for req := range requests {
        <-limiter
        fmt.Println("request", req, time.Now())
    }
    burstyLimiter := make(chan time.Time, 3)
    for range 3 {
        burstyLimiter <- time.Now()
    }
    go func() {
        for t := range time.Tick(200 * time.Millisecond) {
            burstyLimiter <- t
        }
    }()
    burstyRequests := make(chan int, 5)
    for i := 1; i <= 5; i++ {
        burstyRequests <- i
    }
    close(burstyRequests)
    for req := range burstyRequests {
        <-burstyLimiter
        fmt.Println("request", req, time.Now())
    }
}

2.16. Atomic Counters

sync/atomic 提供无需互斥锁的原子读写和加法操作,适合多个 goroutine 共享简单计数器;示例启动多个 goroutine 并发增加操作次数,最后用原子读取获得稳定结果。原子操作只保证特定变量访问的原子性,复杂状态仍需要 mutex 或更高层的同步设计。

package main
import (
    "fmt"
    "sync"
    "sync/atomic"
)
func main() {
    var ops atomic.Uint64
    var wg sync.WaitGroup
    for range 50 {
        wg.Go(func() {
            for range 1000 {
                ops.Add(1)
            }
        })
    }
    wg.Wait()
    fmt.Println("ops:", ops.Load())
}

2.17. Mutexes

互斥锁用来保护多个 goroutine 共享的临界区,=sync.Mutex= 的 LockUnlock 应成对出现,常见写法是在成功加锁后立即 =defer Unlock=。锁的保护范围要覆盖所有相关读写,避免把受保护数据的一部分暴露给无锁访问。

package main
import (
    "fmt"
    "sync"
)
type Container struct {
    mu       sync.Mutex
    counters map[string]int
}
func (c *Container) inc(name string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.counters[name]++
}
func main() {
    c := Container{
        counters: map[string]int{"a": 0, "b": 0},
    }
    var wg sync.WaitGroup
    doIncrement := func(name string, n int) {
        for range n {
            c.inc(name)
        }
    }
    wg.Go(func() {
        doIncrement("a", 10000)
    })
    wg.Go(func() {
        doIncrement("a", 10000)
    })
    wg.Go(func() {
        doIncrement("b", 10000)
    })
    wg.Wait()
    fmt.Println(c.counters)
}

2.18. Stateful Goroutines

除了锁和原子操作,还可以让一个专门的 goroutine 独占状态,其他 goroutine 通过 channel 发送查询或更新请求;这样状态访问顺序由该 goroutine 串行决定。这个模型能减少共享内存竞态,但需要设计请求协议、退出机制和高负载下的队列行为。

package main
import (
    "fmt"
    "math/rand"
    "sync/atomic"
    "time"
)
type readOp struct {
    key  int
    resp chan int
}
type writeOp struct {
    key  int
    val  int
    resp chan bool
}
func main() {
    var readOps uint64
    var writeOps uint64
    reads := make(chan readOp)
    writes := make(chan writeOp)
    go func() {
        var state = make(map[int]int)
        for {
            select {
            case read := <-reads:
                read.resp <- state[read.key]
            case write := <-writes:
                state[write.key] = write.val
                write.resp <- true
            }
        }
    }()
    for range 100 {
        go func() {
            for {
                read := readOp{
                    key:  rand.Intn(5),
                    resp: make(chan int)}
                reads <- read
                <-read.resp
                atomic.AddUint64(&readOps, 1)
                time.Sleep(time.Millisecond)
            }
        }()
    }
    for range 10 {
        go func() {
            for {
                write := writeOp{
                    key:  rand.Intn(5),
                    val:  rand.Intn(100),
                    resp: make(chan bool)}
                writes <- write
                <-write.resp
                atomic.AddUint64(&writeOps, 1)
                time.Sleep(time.Millisecond)
            }
        }()
    }
    time.Sleep(time.Second)
    readOpsFinal := atomic.LoadUint64(&readOps)
    fmt.Println("readOps:", readOpsFinal)
    writeOpsFinal := atomic.LoadUint64(&writeOps)
    fmt.Println("writeOps:", writeOpsFinal)
}

3. 控制流与文本处理

3.1. Sorting

Go 的 slicessort 相关 API 可以对基本类型集合进行排序,也可以检查集合是否已经有序;排序通常会直接修改传入的 slice。排序前应确认是否允许改变原数据,并注意字符串排序按字节/Unicode 码点规则处理,不等同于自然语言排序。

package main
import (
    "fmt"
    "slices"
)
func main() {
    strs := []string{"c", "a", "b"}
    slices.Sort(strs)
    fmt.Println("Strings:", strs)
    ints := []int{7, 2, 4}
    slices.Sort(ints)
    fmt.Println("Ints:   ", ints)
    s := slices.IsSorted(ints)
    fmt.Println("Sorted: ", s)
}

3.2. Sorting by Functions

自定义排序可以提供比较函数,或者使用支持比较器的 slice 排序 API,按结构体字段、字符串长度等业务规则排序。比较函数必须建立稳定的一致顺序,否则排序结果可能不确定;需要保留原顺序或避免修改输入时,应先复制 slice。

package main
import (
    "cmp"
    "fmt"
    "slices"
)
func main() {
    fruits := []string{"peach", "banana", "kiwi"}
    lenCmp := func(a, b string) int {
        return cmp.Compare(len(a), len(b))
    }
    slices.SortFunc(fruits, lenCmp)
    fmt.Println(fruits)
    type Person struct {
        name string
        age  int
    }
    people := []Person{
        Person{name: "Jax", age: 37},
        Person{name: "TJ", age: 25},
        Person{name: "Alex", age: 72},
    }
    slices.SortFunc(people,
        func(a, b Person) int {
            return cmp.Compare(a.age, b.age)
        })
    fmt.Println(people)
}

3.3. Panic

panic 表示程序遇到无法正常恢复的异常状态,可以传入任意值并立即停止当前函数的普通执行流程;未被恢复时会沿调用栈展开并终止程序。普通输入错误、网络错误和文件错误不应滥用 panic,而应通过显式 error 返回。

package main
import (
    "os"
    "path/filepath"
)
func main() {
    panic("a problem")
    path := filepath.Join(os.TempDir(), "file")
    _, err := os.Create(path)
    if err != nil {
        panic(err)
    }
}

3.4. Defer

defer 把函数调用安排到当前函数返回前执行,常用于关闭文件、解锁和释放资源;多个 defer 按后进先出的顺序运行。资源成功获取后应尽快注册清理动作,同时不要忽略 deferred Close 等操作可能产生的错误。

package main
import (
    "fmt"
    "os"
    "path/filepath"
)
func main() {
    path := filepath.Join(os.TempDir(), "defer.txt")
    f := createFile(path)
    defer closeFile(f)
    writeFile(f)
}
func createFile(p string) *os.File {
    fmt.Println("creating")
    f, err := os.Create(p)
    if err != nil {
        panic(err)
    }
    return f
}
func writeFile(f *os.File) {
    fmt.Println("writing")
    fmt.Fprintln(f, "data")
}
func closeFile(f *os.File) {
    fmt.Println("closing")
    err := f.Close()
    if err != nil {
        panic(err)
    }
}

3.5. Recover

recover 只能在 deferred 函数中捕获当前 goroutine 的 panic,并把控制权恢复到正常返回路径;HTTP 服务或任务边界有时会用它防止单个请求拖垮整个进程。recover 不会跨 goroutine 生效,也不应掩盖所有 panic 后继续运行在未知状态的程序。

package main
import "fmt"
func mayPanic() {
    panic("a problem")
}
func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered. Error:\n", r)
        }
    }()
    mayPanic()
    fmt.Println("After mayPanic()")
}

3.6. String Functions

strings 包提供常用字符串操作,例如查找子串、计数、前后缀判断、替换、大小写转换、拆分、连接和重复;这些函数通常返回新字符串,不会修改原 string。处理 Unicode 文本时要区分字节长度、rune 数量和用户感知字符数量。

package main
import (
    "fmt"
    s "strings"
)
var p = fmt.Println
func main() {
    p("Contains:  ", s.Contains("test", "es"))
    p("Count:     ", s.Count("test", "t"))
    p("HasPrefix: ", s.HasPrefix("test", "te"))
    p("HasSuffix: ", s.HasSuffix("test", "st"))
    p("Index:     ", s.Index("test", "e"))
    p("Join:      ", s.Join([]string{"a", "b"}, "-"))
    p("Repeat:    ", s.Repeat("a", 5))
    p("Replace:   ", s.Replace("foo", "o", "0", -1))
    p("Replace:   ", s.Replace("foo", "o", "0", 1))
    p("Split:     ", s.Split("a-b-c-d-e", "-"))
    p("ToLower:   ", s.ToLower("TEST"))
    p("ToUpper:   ", s.ToUpper("test"))
}

3.7. String Formatting

fmt 包使用格式动词把值格式化为字符串或输出到 Writer,支持占位符、宽度、精度和不同进制等效果;=Sprintf= 返回字符串,=Printf= 直接输出,=Fprintf= 可以写入任意 io.Writer。格式动词与参数类型不匹配时会产生带提示的格式化结果,因此应认真检查格式串。

package main
import (
    "fmt"
    "os"
)
type point struct {
    x, y int
}
func main() {
    p := point{1, 2}
    fmt.Printf("struct1: %v\n", p)
    fmt.Printf("struct2: %+v\n", p)
    fmt.Printf("struct3: %#v\n", p)
    fmt.Printf("type: %T\n", p)
    fmt.Printf("bool: %t\n", true)
    fmt.Printf("int: %d\n", 123)
    fmt.Printf("bin: %b\n", 14)
    fmt.Printf("char: %c\n", 33)
    fmt.Printf("hex: %x\n", 456)
    fmt.Printf("float1: %f\n", 78.9)
    fmt.Printf("float2: %e\n", 123400000.0)
    fmt.Printf("float3: %E\n", 123400000.0)
    fmt.Printf("str1: %s\n", "\"string\"")
    fmt.Printf("str2: %q\n", "\"string\"")
    fmt.Printf("str3: %x\n", "hex this")
    fmt.Printf("pointer: %p\n", &p)
    fmt.Printf("width1: |%6d|%6d|\n", 12, 345)
    fmt.Printf("width2: |%6.2f|%6.2f|\n", 1.2, 3.45)
    fmt.Printf("width3: |%-6.2f|%-6.2f|\n", 1.2, 3.45)
    fmt.Printf("width4: |%6s|%6s|\n", "foo", "b")
    fmt.Printf("width5: |%-6s|%-6s|\n", "foo", "b")
    s := fmt.Sprintf("sprintf: a %s", "string")
    fmt.Println(s)
    fmt.Fprintf(os.Stderr, "io: an %s\n", "error")
}

3.8. Text Templates

text/template 用模板动作和数据生成动态文本,支持字段访问、map、条件和 range 循环;模板可以从字符串解析,也可以通过 template.Must 在初始化阶段把解析错误转为 panic。生成 HTML 时应使用拥有自动转义能力的 =html/template=,不要直接把不可信数据拼接进页面。

package main
import (
    "os"
    "text/template"
)
func main() {
    t1 := template.New("t1")
    t1, err := t1.Parse("Value is {{.}}\n")
    if err != nil {
        panic(err)
    }
    t1 = template.Must(t1.Parse("Value: {{.}}\n"))
    t1.Execute(os.Stdout, "some text")
    t1.Execute(os.Stdout, 5)
    t1.Execute(os.Stdout, []string{
        "Go",
        "Rust",
        "C++",
        "C#",
    })
    Create := func(name, t string) *template.Template {
        return template.Must(template.New(name).Parse(t))
    }
    t2 := Create("t2", "Name: {{.Name}}\n")
    t2.Execute(os.Stdout, struct {
        Name string
    }{"Jane Doe"})
    t2.Execute(os.Stdout, map[string]string{
        "Name": "Mickey Mouse",
    })
    t3 := Create("t3",
        "{{if . -}} yes {{else -}} no {{end}}\n")
    t3.Execute(os.Stdout, "not empty")
    t3.Execute(os.Stdout, "")
    t4 := Create("t4",
        "Range: {{range .}}{{.}} {{end}}\n")
    t4.Execute(os.Stdout,
        []string{
            "Go",
            "Rust",
            "C++",
            "C#",
        })
}

3.9. Regular Expressions

regexp 包使用正则表达式进行匹配、查找、替换和捕获分组;=MustCompile= 适合固定且已知正确的模式,运行时模式则应显式处理编译错误。正则表达式适合局部文本模式,不适合替代完整的 HTML、JSON 或复杂编程语言解析器。

package main
import (
    "bytes"
    "fmt"
    "regexp"
)
func main() {
    match, _ := regexp.MatchString("p([a-z]+)ch", "peach")
    fmt.Println(match)
    r, _ := regexp.Compile("p([a-z]+)ch")
    fmt.Println(r.MatchString("peach"))
    fmt.Println(r.FindString("peach punch"))
    fmt.Println("idx:", r.FindStringIndex("peach punch"))
    fmt.Println(r.FindStringSubmatch("peach punch"))
    fmt.Println(r.FindStringSubmatchIndex("peach punch"))
    fmt.Println(r.FindAllString("peach punch pinch", -1))
    fmt.Println("all:", r.FindAllStringSubmatchIndex(
        "peach punch pinch", -1))
    fmt.Println(r.FindAllString("peach punch pinch", 2))
    fmt.Println(r.Match([]byte("peach")))
    r = regexp.MustCompile("p([a-z]+)ch")
    fmt.Println("regexp:", r)
    fmt.Println(r.ReplaceAllString("a peach", "<fruit>"))
    in := []byte("a peach")
    out := r.ReplaceAllFunc(in, bytes.ToUpper)
    fmt.Println(string(out))
}

4. 数据、时间与编码

4.1. JSON

encoding/json 可以把 struct、map 和 slice 编码成 JSON,也能把 JSON 解码到结构体或通用值;struct tag 如 json:"field" 控制字段名和忽略规则。编码和解码都可能失败,生产代码应检查错误,并注意数字精度、未知字段以及 nil 与空集合的区别。

package main
import (
    "bytes"
    "encoding/json/v2"
    "fmt"
    "strings"
)
type response1 struct {
    Page   int
    Fruits []string
}
type response2 struct {
    Page   int      `json:"page"`
    Fruits []string `json:"fruits"`
}
func main() {
    bolB, _ := json.Marshal(true)
    fmt.Println(string(bolB))
    intB, _ := json.Marshal(1)
    fmt.Println(string(intB))
    fltB, _ := json.Marshal(2.34)
    fmt.Println(string(fltB))
    strB, _ := json.Marshal("gopher")
    fmt.Println(string(strB))
    slcD := []string{"apple", "peach", "pear"}
    slcB, _ := json.Marshal(slcD)
    fmt.Println(string(slcB))
    mapD := map[string]int{"apple": 5, "lettuce": 7}
    mapB, _ := json.Marshal(mapD)
    fmt.Println(string(mapB))
    res1D := &response1{
        Page:   1,
        Fruits: []string{"apple", "peach", "pear"}}
    res1B, _ := json.Marshal(res1D)
    fmt.Println(string(res1B))
    res2D := &response2{
        Page:   1,
        Fruits: []string{"apple", "peach", "pear"}}
    res2B, _ := json.Marshal(res2D)
    fmt.Println(string(res2B))
    byt := []byte(`{"num":6.13,"strs":["a","b"]}`)
    var dat map[string]any
    if err := json.Unmarshal(byt, &dat); err != nil {
        panic(err)
    }
    fmt.Println(dat)
    num := dat["num"].(float64)
    fmt.Println(num)
    strs := dat["strs"].([]any)
    str1 := strs[0].(string)
    fmt.Println(str1)
    str := `{"page": 1, "fruits": ["apple", "peach"]}`
    res := response2{}
    _ = json.Unmarshal([]byte(str), &res)
    fmt.Println(res)
    fmt.Println(res.Fruits[0])
    d := map[string]int{"apple": 5, "lettuce": 7}
    var buf bytes.Buffer
    _ = json.MarshalWrite(&buf, d)
    fmt.Println(buf.String())
    res1 := response2{}
    _ = json.UnmarshalRead(strings.NewReader(str), &res1)
    fmt.Println(res1)
}

4.2. XML

encoding/xml 提供 XML 的编码和解码,struct tag 可以控制元素、属性和嵌套路径,=XMLName= 可以指定根元素名称。XML 输入可能不符合预期结构,因此解码后的错误和字段缺失都需要处理;XML 与 JSON 一样,结构体标签是数据模型的重要一部分。

package main
import (
    "encoding/xml"
    "fmt"
)
type Plant struct {
    XMLName xml.Name `xml:"plant"`
    Id      int      `xml:"id,attr"`
    Name    string   `xml:"name"`
    Origin  []string `xml:"origin"`
}
func (p Plant) String() string {
    return fmt.Sprintf("Plant id=%v, name=%v, origin=%v",
        p.Id, p.Name, p.Origin)
}
func main() {
    coffee := &Plant{Id: 27, Name: "Coffee"}
    coffee.Origin = []string{"Ethiopia", "Brazil"}
    out, _ := xml.MarshalIndent(coffee, " ", "  ")
    fmt.Println(string(out))
    fmt.Println(xml.Header + string(out))
    var p Plant
    if err := xml.Unmarshal(out, &p); err != nil {
        panic(err)
    }
    fmt.Println(p)
    tomato := &Plant{Id: 81, Name: "Tomato"}
    tomato.Origin = []string{"Mexico", "California"}
    type Nesting struct {
        XMLName xml.Name `xml:"nesting"`
        Plants  []*Plant `xml:"parent>child>plant"`
    }
    nesting := &Nesting{}
    nesting.Plants = []*Plant{coffee, tomato}
    out, _ = xml.MarshalIndent(nesting, " ", "  ")
    fmt.Println(string(out))
}

4.3. Time

time.Time 表示带时区位置的时间,支持构造、提取年月日时分秒、比较、计算差值和加减 duration;=time.Now= 获取当前时间,=Sub= 得到时间间隔。时间点、时区、持续时间和日期格式是不同概念,业务代码不应把它们混为一个整数处理。

package main
import (
    "fmt"
    "time"
)
func main() {
    p := fmt.Println
    now := time.Now()
    p(now)
    then := time.Date(
        2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
    p(then)
    p(then.Year())
    p(then.Month())
    p(then.Day())
    p(then.Hour())
    p(then.Minute())
    p(then.Second())
    p(then.Nanosecond())
    p(then.Location())
    p(then.Weekday())
    p(then.Before(now))
    p(then.After(now))
    p(then.Equal(now))
    diff := now.Sub(then)
    p(diff)
    p(diff.Hours())
    p(diff.Minutes())
    p(diff.Seconds())
    p(diff.Nanoseconds())
    p(then.Add(diff))
    p(then.Add(-diff))
}

4.4. Epoch

Unix epoch 是从 1970-01-01 UTC 起计算的时间基准,Go 可以通过 Unix=、=UnixMilliUnixNano 获取秒、毫秒或纳秒时间戳,也能把时间戳转换回 =time.Time=。跨系统传递时间戳时必须明确单位和时区,避免把秒误当成毫秒。

package main
import (
    "fmt"
    "time"
)
func main() {
    now := time.Now()
    fmt.Println(now)
    fmt.Println(now.Unix())
    fmt.Println(now.UnixMilli())
    fmt.Println(now.UnixNano())
    fmt.Println(time.Unix(now.Unix(), 0))
    fmt.Println(time.Unix(0, now.UnixNano()))
}

4.5. Time Formatting / Parsing

Go 使用参考时间 Mon Jan 2 15:04:05 MST 2006 作为布局模板,通过 Format 格式化时间、=Parse= 解析字符串;布局中的示例时间不是随意占位符,必须使用规定的数字和文本。解析外部输入时应处理错误,并明确输入是否带时区和偏移量。

package main
import (
    "fmt"
    "time"
)
func main() {
    p := fmt.Println
    t := time.Now()
    p(t.Format(time.RFC3339))
    t1, _ := time.Parse(time.RFC3339, "2012-11-01T22:08:41+00:00")
    p(t1)
    p(t.Format("3:04PM"))
    p(t.Format("Mon Jan _2 15:04:05 2006"))
    p(t.Format("2006-01-02T15:04:05.999999-07:00"))
    form := "3 04 PM"
    t2, _ := time.Parse(form, "8 41 PM")
    p(t2)
    fmt.Printf("%d-%02d-%02dT%02d:%02d:%02d-00:00\n",
        t.Year(), t.Month(), t.Day(),
        t.Hour(), t.Minute(), t.Second())
    _, err := time.Parse("Mon Jan _2 15:04:05 2006", "8:41PM")
    p(err)
}

4.6. Random Numbers

随机数 API 可以生成整数、浮点数和指定范围内的值,也可以创建带独立随机源的生成器;需要不可预测性时应使用 =crypto/rand=,而不是普通的伪随机数。随机种子、并发使用方式和版本变化会影响可重复性,测试代码应显式控制随机源。

package main
import (
    "fmt"
    "math/rand/v2"
)
func main() {
    fmt.Print(rand.IntN(100), ",")
    fmt.Print(rand.IntN(100))
    fmt.Println()
    fmt.Println(rand.Float64())
    fmt.Print((rand.Float64()*5)+5, ",")
    fmt.Print((rand.Float64() * 5) + 5)
    fmt.Println()
    s2 := rand.NewPCG(42, 1024)
    r2 := rand.New(s2)
    fmt.Print(r2.IntN(100), ",")
    fmt.Print(r2.IntN(100))
    fmt.Println()
    s3 := rand.NewPCG(42, 1024)
    r3 := rand.New(s3)
    fmt.Print(r3.IntN(100), ",")
    fmt.Print(r3.IntN(100))
    fmt.Println()
}

4.7. Number Parsing

strconv 可以把字符串解析成整数、无符号整数或浮点数,并通过位数参数约束结果范围;解析函数同时返回错误,用于区分格式错误、溢出和非法进制。展示给用户的数字通常还需要先校验空白、符号和业务范围。

package main
import (
    "fmt"
    "strconv"
)
func main() {
    f, _ := strconv.ParseFloat("1.234", 64)
    fmt.Println(f)
    i, _ := strconv.ParseInt("123", 0, 64)
    fmt.Println(i)
    d, _ := strconv.ParseInt("0x1c8", 0, 64)
    fmt.Println(d)
    u, _ := strconv.ParseUint("789", 0, 64)
    fmt.Println(u)
    k, _ := strconv.Atoi("135")
    fmt.Println(k)
    _, e := strconv.Atoi("wat")
    fmt.Println(e)
}

4.8. URL Parsing

net/url 能拆解 URL 的 scheme、认证信息、host、port、path、query 和 fragment,并把查询参数解析为 map;用户信息应通过专门方法读取,主机和端口则使用 SplitHostPort 等 API。解析 URL 不等于验证 URL 是否允许访问,服务端仍需进行协议、域名和 SSRF 等安全校验。

package main
import (
    "fmt"
    "net"
    "net/url"
)
func main() {
    s := "postgres://user:pass@host.com:5432/path?k=v#f"
    u, err := url.Parse(s)
    if err != nil {
        panic(err)
    }
    fmt.Println(u.Scheme)
    fmt.Println(u.User)
    fmt.Println(u.User.Username())
    p, _ := u.User.Password()
    fmt.Println(p)
    fmt.Println(u.Host)
    host, port, _ := net.SplitHostPort(u.Host)
    fmt.Println(host)
    fmt.Println(port)
    fmt.Println(u.Path)
    fmt.Println(u.Fragment)
    fmt.Println(u.RawQuery)
    m, _ := url.ParseQuery(u.RawQuery)
    fmt.Println(m)
    fmt.Println(m["k"][0])
}

4.9. SHA256 Hashes

crypto/sha256 可以对字节序列计算 SHA-256 摘要,结果是固定长度的字节数组,常用十六进制格式展示。摘要适合完整性校验和内容指纹,不适合直接保存密码;密码存储应使用专门的慢哈希和随机 salt。

package main
import (
    "crypto/sha256"
    "fmt"
)
func main() {
    s := "sha256 this string"
    h := sha256.New()
    h.Write([]byte(s))
    bs := h.Sum(nil)
    fmt.Println(s)
    fmt.Printf("%x\n", bs)
}

4.10. Base64 Encoding

encoding/base64 支持标准 Base64 和 URL 兼容 Base64 的编码与解码,编码输入通常是 =[]byte=,解码会返回数据和错误。Base64 只是二进制到文本的表示方式,不提供加密或保密性,放在 URL、JSON 或邮件中时仍要考虑长度和填充规则。

package main
import (
    b64 "encoding/base64"
    "fmt"
)
func main() {
    data := "abc123!?$*&()'-=@~"
    sEnc := b64.StdEncoding.EncodeToString([]byte(data))
    fmt.Println(sEnc)
    sDec, _ := b64.StdEncoding.DecodeString(sEnc)
    fmt.Println(string(sDec))
    fmt.Println()
    uEnc := b64.URLEncoding.EncodeToString([]byte(data))
    fmt.Println(uEnc)
    uDec, _ := b64.URLEncoding.DecodeString(uEnc)
    fmt.Println(string(uDec))
}

5. 文件、构建与测试

5.1. Reading Files

读取文件可以使用 os.ReadFile 一次性获得全部内容,也可以用 os.Open 配合 reader 分块读取;读取文件、打开文件和关闭文件都可能失败。小文件适合一次性读取,大文件或流式输入则应控制缓冲和内存占用,并及时关闭句柄。

package main
import (
    "bufio"
    "fmt"
    "io"
    "os"
    "path/filepath"
)
func check(e error) {
    if e != nil {
        panic(e)
    }
}
func main() {
    path := filepath.Join(os.TempDir(), "dat")
    dat, err := os.ReadFile(path)
    check(err)
    fmt.Print(string(dat))
    f, err := os.Open(path)
    check(err)
    b1 := make([]byte, 5)
    n1, err := f.Read(b1)
    check(err)
    fmt.Printf("%d bytes: %s\n", n1, string(b1[:n1]))
    o2, err := f.Seek(6, io.SeekStart)
    check(err)
    b2 := make([]byte, 2)
    n2, err := f.Read(b2)
    check(err)
    fmt.Printf("%d bytes @ %d: ", n2, o2)
    fmt.Printf("%v\n", string(b2[:n2]))
    _, err = f.Seek(2, io.SeekCurrent)
    check(err)
    _, err = f.Seek(-4, io.SeekEnd)
    check(err)
    o3, err := f.Seek(6, io.SeekStart)
    check(err)
    b3 := make([]byte, 2)
    n3, err := io.ReadAtLeast(f, b3, 2)
    check(err)
    fmt.Printf("%d bytes @ %d: %s\n", n3, o3, string(b3))
    _, err = f.Seek(0, io.SeekStart)
    check(err)
    r4 := bufio.NewReader(f)
    b4, err := r4.Peek(5)
    check(err)
    fmt.Printf("5 bytes: %s\n", string(b4))
    f.Close()
}

5.2. Writing Files

写文件可以直接使用 os.WriteFile=,也可以打开文件后调用 =Write=、=WriteString 或通过 bufio.Writer 缓冲写入;需要持久化语义时可调用 =Sync=。打开文件后应及时 defer Close,并明确文件权限、覆盖行为和部分写入错误。

package main
import (
    "bufio"
    "fmt"
    "os"
    "path/filepath"
)
func check(e error) {
    if e != nil {
        panic(e)
    }
}
func main() {
    d1 := []byte("hello\ngo\n")
    path1 := filepath.Join(os.TempDir(), "dat1")
    err := os.WriteFile(path1, d1, 0644)
    check(err)
    path2 := filepath.Join(os.TempDir(), "dat2")
    f, err := os.Create(path2)
    check(err)
    defer f.Close()
    d2 := []byte{115, 111, 109, 101, 10}
    n2, err := f.Write(d2)
    check(err)
    fmt.Printf("wrote %d bytes\n", n2)
    n3, err := f.WriteString("writes\n")
    check(err)
    fmt.Printf("wrote %d bytes\n", n3)
    f.Sync()
    w := bufio.NewWriter(f)
    n4, err := w.WriteString("buffered\n")
    check(err)
    fmt.Printf("wrote %d bytes\n", n4)
    w.Flush()
}

5.3. Line Filters

行过滤器从标准输入逐行读取文本、根据条件处理并把结果写到标准输出,示例使用 =bufio.Scanner=;这种 Unix 风格程序可以通过管道组合成更大的处理流程。Scanner 有默认 token 大小限制,处理超长行时应调整 buffer 或改用更适合的 reader。

package main
import (
    "bufio"
    "fmt"
    "os"
    "strings"
)
func main() {
    scanner := bufio.NewScanner(os.Stdin)
    for scanner.Scan() {
        ucl := strings.ToUpper(scanner.Text())
        fmt.Println(ucl)
    }
    if err := scanner.Err(); err != nil {
        fmt.Fprintln(os.Stderr, "error:", err)
        os.Exit(1)
    }
}

5.4. File Paths

path/filepath 提供跨平台的路径拼接、清理、拆分、绝对路径和目录遍历操作,使用 Join 比手动拼接分隔符更可靠。路径字符串本身不等于安全路径,接收用户输入时仍需防范路径穿越、符号链接和访问范围越界。

package main
import (
    "fmt"
    "path/filepath"
    "strings"
)
func main() {
    p := filepath.Join("dir1", "dir2", "filename")
    fmt.Println("p:", p)
    fmt.Println(filepath.Join("dir1//", "filename"))
    fmt.Println(filepath.Join("dir1/../dir1", "filename"))
    fmt.Println("Dir(p):", filepath.Dir(p))
    fmt.Println("Base(p):", filepath.Base(p))
    fmt.Println(filepath.IsAbs("dir/file"))
    fmt.Println(filepath.IsAbs("/dir/file"))
    filename := "config.json"
    ext := filepath.Ext(filename)
    fmt.Println(ext)
    fmt.Println(strings.TrimSuffix(filename, ext))
    rel, err := filepath.Rel("a/b", "a/b/t/file")
    if err != nil {
        panic(err)
    }
    fmt.Println(rel)
    rel, err = filepath.Rel("a/b", "a/c/t/file")
    if err != nil {
        panic(err)
    }
    fmt.Println(rel)
}

5.5. Directories

目录操作包括 Mkdir=、=MkdirAll=、=ReadDir=、=Chdir 和 =WalkDir=,可以创建层级目录、列出条目或递归访问文件树;临时目录使用后应删除。递归遍历时要处理权限错误、符号链接和目录内容在遍历过程中变化等情况。

package main
import (
    "fmt"
    "io/fs"
    "os"
    "path/filepath"
)
func check(e error) {
    if e != nil {
        panic(e)
    }
}
func main() {
    err := os.Mkdir("subdir", 0755)
    check(err)
    defer os.RemoveAll("subdir")
    createEmptyFile := func(name string) {
        d := []byte("")
        check(os.WriteFile(name, d, 0644))
    }
    createEmptyFile("subdir/file1")
    err = os.MkdirAll("subdir/parent/child", 0755)
    check(err)
    createEmptyFile("subdir/parent/file2")
    createEmptyFile("subdir/parent/file3")
    createEmptyFile("subdir/parent/child/file4")
    c, err := os.ReadDir("subdir/parent")
    check(err)
    fmt.Println("Listing subdir/parent")
    for _, entry := range c {
        fmt.Println(" ", entry.Name(), entry.IsDir())
    }
    err = os.Chdir("subdir/parent/child")
    check(err)
    c, err = os.ReadDir(".")
    check(err)
    fmt.Println("Listing subdir/parent/child")
    for _, entry := range c {
        fmt.Println(" ", entry.Name(), entry.IsDir())
    }
    err = os.Chdir("../../..")
    check(err)
    fmt.Println("Visiting subdir")
    err = filepath.WalkDir("subdir", visit)
    check(err)
}
func visit(path string, d fs.DirEntry, err error) error {
    if err != nil {
        return err
    }
    fmt.Println(" ", path, d.IsDir())
    return nil
}

5.6. Temporary Files and Directories

os.CreateTempos.MkdirTemp 会在系统临时目录中创建不易冲突的临时文件或目录,适合中间结果和测试数据;使用完成后应显式删除,通常用 defer 注册清理。临时路径不应被拼接成可预测的共享文件名,也不应把敏感数据长期留在其中。

package main
import (
    "fmt"
    "os"
    "path/filepath"
)
func check(e error) {
    if e != nil {
        panic(e)
    }
}
func main() {
    f, err := os.CreateTemp("", "sample")
    check(err)
    fmt.Println("Temp file name:", f.Name())
    defer os.Remove(f.Name())
    _, err = f.Write([]byte{1, 2, 3, 4})
    check(err)
    dname, err := os.MkdirTemp("", "sampledir")
    check(err)
    fmt.Println("Temp dir name:", dname)
    defer os.RemoveAll(dname)
    fname := filepath.Join(dname, "file1")
    err = os.WriteFile(fname, []byte{1, 2}, 0666)
    check(err)
}

5.7. Embed Directive

//go:embed 在编译时把文件或目录嵌入 Go 二进制,可以得到字符串、字节切片或 =embed.FS=;路径相对于包含 Go 源文件的目录。嵌入资源便于发布单文件程序,但会增加二进制体积,且 Playground 等受限环境可能无法运行该示例。

package main
import (
    "embed"
)
//go:embed folder/single_file.txt
var fileString string
//go:embed folder/single_file.txt
var fileByte []byte
//go:embed folder/single_file.txt
//go:embed folder/*.hash
var folder embed.FS
func main() {
    print(fileString)
    print(string(fileByte))
    content1, _ := folder.ReadFile("folder/file1.hash")
    print(string(content1))
    content2, _ := folder.ReadFile("folder/file2.hash")
    print(string(content2))
}

5.8. Testing and Benchmarking

测试文件使用 _test.go 命名,测试函数以 Test 开头,断言失败可用 Error 或立即停止的 Fatal=;表驱动测试和 =t.Run 适合覆盖多组输入。Benchmark 函数以 Benchmark 开头,基准循环放在 b.N 控制的循环中,执行前应把不计入测量的准备工作放在循环外。

package main
import (
    "fmt"
    "testing"
)
func IntMin(a, b int) int {
    if a < b {
        return a
    }
    return b
}
func TestIntMinBasic(t *testing.T) {
    ans := IntMin(2, -2)
    if ans != -2 {
        t.Errorf("IntMin(2, -2) = %d; want -2", ans)
    }
}
func TestIntMinTableDriven(t *testing.T) {
    var tests = []struct {
        a, b int
        want int
    }{
        {0, 1, 0},
        {1, 0, 0},
        {2, -2, -2},
        {0, -1, -1},
        {-1, 0, -1},
    }
    for _, tt := range tests {
        testname := fmt.Sprintf("%d,%d", tt.a, tt.b)
        t.Run(testname, func(t *testing.T) {
            ans := IntMin(tt.a, tt.b)
            if ans != tt.want {
                t.Errorf("got %d, want %d", ans, tt.want)
            }
        })
    }
}
func BenchmarkIntMin(b *testing.B) {
    for b.Loop() {
        IntMin(1, 2)
    }
}

6. 命令行、网络与系统

6.1. Command-Line Arguments

os.Args 提供原始命令行参数,其中第一个元素通常是程序路径,后续元素才是用户传入的参数;参数可以用普通 slice 下标读取。它适合简单命令,复杂选项应使用 flag 或专门的 CLI 框架,并注意参数缺失和非法输入。

package main
import (
    "fmt"
    "os"
)
func main() {
    argsWithProg := os.Args
    argsWithoutProg := os.Args[1:]
    arg := os.Args[3]
    fmt.Println(argsWithProg)
    fmt.Println(argsWithoutProg)
    fmt.Println(arg)
}

6.2. Command-Line Flags

flag 包支持字符串、整数、布尔值等命令行选项,声明 flag 后调用 flag.Parse 解析,返回的指针再解引用取得值;位置参数可通过 flag.Args 获取。标准 flag 要求选项出现在位置参数之前,=–help= 等帮助信息由包自动生成。

package main
import (
    "flag"
    "fmt"
)
func main() {
    wordPtr := flag.String("word", "foo", "a string")
    numbPtr := flag.Int("numb", 42, "an int")
    forkPtr := flag.Bool("fork", false, "a bool")
    var svar string
    flag.StringVar(&svar, "svar", "bar", "a string var")
    flag.Parse()
    fmt.Println("word:", *wordPtr)
    fmt.Println("numb:", *numbPtr)
    fmt.Println("fork:", *forkPtr)
    fmt.Println("svar:", svar)
    fmt.Println("tail:", flag.Args())
}

6.3. Command-Line Subcommands

子命令可以为每个命令创建独立的 flag.FlagSet=,例如为 =foobar 分别定义自己的选项,再根据第一个参数选择解析器。这样能保持不同子命令的参数互不污染;更大型的工具还需要统一帮助、错误码和命令发现策略。

package main
import (
    "flag"
    "fmt"
    "os"
)
func main() {
    fooCmd := flag.NewFlagSet("foo", flag.ExitOnError)
    fooEnable := fooCmd.Bool("enable", false, "enable")
    fooName := fooCmd.String("name", "", "name")
    barCmd := flag.NewFlagSet("bar", flag.ExitOnError)
    barLevel := barCmd.Int("level", 0, "level")
    if len(os.Args) < 2 {
        fmt.Println("expected 'foo' or 'bar' subcommands")
        os.Exit(1)
    }
    switch os.Args[1] {
    case "foo":
        fooCmd.Parse(os.Args[2:])
        fmt.Println("subcommand 'foo'")
        fmt.Println("  enable:", *fooEnable)
        fmt.Println("  name:", *fooName)
        fmt.Println("  tail:", fooCmd.Args())
    case "bar":
        barCmd.Parse(os.Args[2:])
        fmt.Println("subcommand 'bar'")
        fmt.Println("  level:", *barLevel)
        fmt.Println("  tail:", barCmd.Args())
    default:
        fmt.Println("expected 'foo' or 'bar' subcommands")
        os.Exit(1)
    }
}

6.4. Environment Variables

环境变量是进程配置的通用方式,=os.Setenv= 设置、=os.Getenv= 读取、=os.Environ= 列出全部键值;读取不存在的变量时 Getenv 返回空字符串,因此需要区分“未设置”和“设置为空”时可使用更明确的 API。环境变量适合部署注入配置,但敏感值仍需控制日志和进程环境暴露。

package main
import (
    "fmt"
    "os"
    "strings"
)
func main() {
    os.Setenv("FOO", "1")
    fmt.Println("FOO:", os.Getenv("FOO"))
    fmt.Println("BAR:", os.Getenv("BAR"))
    fmt.Println()
    for _, e := range os.Environ() {
        pair := strings.SplitN(e, "=", 2)
        fmt.Println(pair[0])
    }
}

6.5. Logging

标准 log 包提供带时间信息的日志输出、前缀和等级之外的基础能力,并能写入指定的 =io.Writer=;日志调用通常会自动附加换行。生产服务应统一日志格式、输出目标、级别和敏感字段脱敏,不能把日志当成结构化审计系统的完整替代品。

package main
import (
    "bytes"
    "fmt"
    "log"
    "os"
    "log/slog"
)
func main() {
    log.Println("standard logger")
    log.SetFlags(log.LstdFlags | log.Lmicroseconds)
    log.Println("with micro")
    log.SetFlags(log.LstdFlags | log.Lshortfile)
    log.Println("with file/line")
    mylog := log.New(os.Stdout, "my:", log.LstdFlags)
    mylog.Println("from mylog")
    mylog.SetPrefix("ohmy:")
    mylog.Println("from mylog")
    var buf bytes.Buffer
    buflog := log.New(&buf, "buf:", log.LstdFlags)
    buflog.Println("hello")
    fmt.Print("from buflog:", buf.String())
    jsonHandler := slog.NewJSONHandler(os.Stderr, nil)
    myslog := slog.New(jsonHandler)
    myslog.Info("hi there")
    myslog.Info("hello again", "key", "val", "age", 25)
}

6.6. HTTP Client

net/http 的 Client 可以发送请求、设置方法和请求头、读取响应状态与响应体;响应体必须关闭,否则可能无法复用连接并造成资源泄漏。访问外部服务时应配置超时、检查 HTTP 状态码、限制响应大小,并避免把默认无限等待的客户端直接用于生产网络调用。

package main
import (
    "bufio"
    "fmt"
    "net/http"
)
func main() {
    resp, err := http.Get("https://gobyexample.com")
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    fmt.Println("Response status:", resp.Status)
    scanner := bufio.NewScanner(resp.Body)
    for i := 0; scanner.Scan() && i < 5; i++ {
        fmt.Println(scanner.Text())
    }
    if err := scanner.Err(); err != nil {
        panic(err)
    }
}

6.7. HTTP Server

HTTP 服务可以用 http.HandleFunc 注册路由,再通过 http.ListenAndServe 监听端口;Handler 从请求读取路径、查询参数和请求体,并向 ResponseWriter 写响应。真实服务通常应使用带超时的 =http.Server=,并验证请求方法、请求体大小和并发退出流程。

package main
import (
    "fmt"
    "net/http"
)
func hello(w http.ResponseWriter, req *http.Request) {
    fmt.Fprintf(w, "hello\n")
}
func headers(w http.ResponseWriter, req *http.Request) {
    for name, headers := range req.Header {
        for _, h := range headers {
            fmt.Fprintf(w, "%v: %v\n", name, h)
        }
    }
}
func main() {
    http.HandleFunc("/hello", hello)
    http.HandleFunc("/headers", headers)
    http.ListenAndServe(":8090", nil)
}

6.8. TCP Server

net.Listen 创建 TCP listener,服务端循环调用 Accept 获取客户端连接,并为每个连接启动 goroutine;示例使用 bufio.Reader 读取一行,再通过连接写回响应。TCP 只有字节流语义,不保证一次 Write 对应一次 Read,因此实际协议必须自行定义边界、超时和半关闭行为。

package main
import (
    "bufio"
    "fmt"
    "log"
    "net"
    "strings"
)
func main() {
    listener, err := net.Listen("tcp", ":8090")
    if err != nil {
        log.Fatal("Error listening:", err)
    }
    defer listener.Close()
    for {
        conn, err := listener.Accept()
        if err != nil {
            log.Println("Error accepting conn:", err)
            continue
        }
        go handleConnection(conn)
    }
}
func handleConnection(conn net.Conn) {
    defer conn.Close()
    reader := bufio.NewReader(conn)
    message, err := reader.ReadString('\n')
    if err != nil {
        log.Printf("Read error: %v", err)
        return
    }
    ackMsg := strings.ToUpper(strings.TrimSpace(message))
    response := fmt.Sprintf("ACK: %s\n", ackMsg)
    _, err = conn.Write([]byte(response))
    if err != nil {
        log.Printf("Server write error: %v", err)
    }
}

6.9. Context

context.Context 用于在 API 边界和 goroutine 之间传递取消信号、截止时间和请求范围的值;HTTP 请求通常自带 context,处理函数可以监听 Done 并在客户端取消时提前结束。context 不应作为普通配置容器,也不应把 nil context 传给需要它的函数。

package main
import (
    "fmt"
    "net/http"
    "time"
)
func hello(w http.ResponseWriter, req *http.Request) {
    ctx := req.Context()
    fmt.Println("server: hello handler started")
    defer fmt.Println("server: hello handler ended")
    select {
    case <-time.After(10 * time.Second):
        fmt.Fprintf(w, "hello\n")
    case <-ctx.Done():
        err := ctx.Err()
        fmt.Println("server:", err)
        internalError := http.StatusInternalServerError
        http.Error(w, err.Error(), internalError)
    }
}
func main() {
    http.HandleFunc("/hello", hello)
    http.ListenAndServe(":8090", nil)
}

6.10. Spawning Processes

os/exec 可以启动外部程序,使用 Command 创建命令,通过 Output 获取合并输出,或使用 Stdout=、=Stderr 连接到指定流。启动外部程序时应检查错误、设置超时和退出回收策略,涉及用户输入时还要避免把未经处理的字符串拼成 Shell 命令。

package main
import (
    "errors"
    "fmt"
    "io"
    "os/exec"
)
func main() {
    dateCmd := exec.Command("date")
    dateOut, err := dateCmd.Output()
    if err != nil {
        panic(err)
    }
    fmt.Println("> date")
    fmt.Println(string(dateOut))
    _, err = exec.Command("date", "-x").Output()
    if err != nil {
        if e, ok := errors.AsType[*exec.Error](err); ok {
            fmt.Println("failed executing:", e)
        } else if e, ok := errors.AsType[*exec.ExitError](err); ok {
            exitCode := e.ExitCode()
            fmt.Println("command exit rc =", exitCode)
        } else {
            panic(err)
        }
    }
    grepCmd := exec.Command("grep", "hello")
    grepIn, _ := grepCmd.StdinPipe()
    grepOut, _ := grepCmd.StdoutPipe()
    grepCmd.Start()
    grepIn.Write([]byte("hello grep\ngoodbye grep"))
    grepIn.Close()
    grepBytes, _ := io.ReadAll(grepOut)
    grepCmd.Wait()
    fmt.Println("> grep hello")
    fmt.Println(string(grepBytes))
    lsCmd := exec.Command("bash", "-c", "ls -a -l -h")
    lsOut, err := lsCmd.Output()
    if err != nil {
        panic(err)
    }
    fmt.Println("> ls -a -l -h")
    fmt.Println(string(lsOut))
}

6.11. Exec'ing Processes

Unix 的 syscall.Exec 会用另一个程序替换当前进程映像,而不是创建一个仍由父进程管理的子进程;成功后原程序不会继续执行。它适合实现启动器、容器入口等场景,但平台兼容性、文件描述符继承和环境变量传递都需要明确处理。

package main
import (
    "os"
    "os/exec"
    "syscall"
)
func main() {
    binary, lookErr := exec.LookPath("ls")
    if lookErr != nil {
        panic(lookErr)
    }
    args := []string{"ls", "-a", "-l", "-h"}
    env := os.Environ()
    execErr := syscall.Exec(binary, args, env)
    if execErr != nil {
        panic(execErr)
    }
}

6.12. Signals

os/signal 可以通过 channel 接收操作系统信号,例如用 signal.Notify 监听中断信号并执行清理;服务通常要在收到终止信号后停止接收新请求,再等待已有工作结束。signal channel 必须有合理容量和退出逻辑,不能把所有信号都简单地忽略。

package main
import (
    "context"
    "fmt"
    "os/signal"
    "syscall"
)
func main() {
    ctx, stop := signal.NotifyContext(
        context.Background(), syscall.SIGINT, syscall.SIGTERM)
    defer stop()
    fmt.Println("awaiting signal")
    <-ctx.Done()
    fmt.Println()
    fmt.Println(context.Cause(ctx))
    fmt.Println("exiting")
}

6.13. Exit

os.Exit 会立即以指定状态码结束进程,不会执行 defer,因此只适合在顶层处理最终退出状态;普通函数更适合返回 error,让 main 统一记录错误并退出。退出码应保持稳定而有意义,方便 Shell、CI 和进程管理器判断成功或失败。

package main
import (
    "fmt"
    "os"
)
func main() {
    defer fmt.Println("!")
    os.Exit(3)
}

Author: Hao Ruan (ruanhao1116@gmail.com)

Created: 2026-06-30 Tue 00:00

Updated: 2026-09-02 Wed 11:20

Emacs 30.1 (Org mode 9.7.11)