Go 学习完整指南

本文档整合了 Go 官方学习资源 (go.dev/learn/)Go by Example (gobyexample.com) 的全部内容,提供完整、可用、可重复学习的 Go 语言参考资料。


第一部分:Go 官方学习资源

1.1 Go 入门教程 (Getting Started)

来源:https://go.dev/doc/tutorial/getting-started

本教程快速介绍 Go 编程基础,包括安装 Go、编写 “Hello, World”、使用 go 命令运行代码、调用外部模块函数。

前提条件

  • 有一定的编程经验
  • 代码编辑器(VSCode、GoLand、Vim 等)
  • 命令行终端

安装 Go

https://go.dev/dl 下载并安装最新版本(Go 1.26.5)。

编写第一个程序

# 创建工作目录
mkdir hello
cd hello

# 初始化模块
$ go mod init example/hello
go: creating new go.mod: module example/hello

hello.go — 第一个 Go 程序:

package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}
# 运行程序
$ go run .
Hello, World!

调用外部包

使用 rsc.io/quote 包中的函数生成更有趣的输出:

package main

import "fmt"

import "rsc.io/quote"

func main() {
    fmt.Println(quote.Go())
}
# 添加依赖
$ go mod tidy
go: finding module for package rsc.io/quote
go: found rsc.io/quote in rsc.io/quote v1.5.2

# 运行
$ go run .
Don't communicate by sharing memory, share memory by communicating.

关键命令

命令 用途
go mod init <module> 初始化新模块,创建 go.mod
go run . 编译并运行当前包
go build 编译生成可执行文件
go mod tidy 添加缺失的模块依赖并移除未使用的
go help 查看所有 Go 命令

1.2 创建 Go 模块 (Create a Module)

来源:https://go.dev/doc/tutorial/create-module

本教程通过构建两个模块(一个可导入的库模块和一个调用者应用)介绍 Go 基础语言特性,包括包导出、错误处理、随机数、单元测试、编译安装等,共七个步骤。

先决条件

  • 一定的编程经验
  • 文本编辑器
  • 命令行终端

步骤1:创建库模块

$ mkdir greetings
$ cd greetings
$ go mod init example.com/greetings
go: creating new go.mod: module example.com/greetings

greetings.go — 问候库:

package greetings

import "fmt"

// Hello 返回对指定人员的问候语
// 函数名首字母大写表示导出(对其他包可见)
func Hello(name string) string {
    // := 是声明并初始化变量的简写
    // %v 是格式化动词,用于插入变量值
    message := fmt.Sprintf("Hi, %v. Welcome!", name)
    return message
}

步骤2:从另一个模块调用代码

$ cd ..
$ mkdir hello
$ cd hello
$ go mod init example.com/hello

hello.go — 调用 greetings 包的调用者应用:

package main

import (
    "fmt"
    "example.com/greetings"
)

func main() {
    message := greetings.Hello("Gladys")
    fmt.Println(message)
}

连接本地模块并使用 replace 指令:

$ go mod edit -replace example.com/greetings=../greetings
$ go mod tidy
$ go run .
Hi, Gladys. Welcome!

此时 hello/go.mod 内容为:

module example.com/hello

go 1.16

replace example.com/greetings => ../greetings

require example.com/greetings v0.0.0-00010101000000-000000000000

步骤3:错误处理

greetings.go — 返回错误而非字符串:

package greetings

import (
    "errors"
    "fmt"
)

// Hello 返回问候语和错误
func Hello(name string) (string, error) {
    // 名称为空时返回错误
    if name == "" {
        return "", errors.New("empty name")
    }

    // 正常返回问候语
    message := fmt.Sprintf("Hi, %v. Welcome!", name)
    return message, nil
}

hello.go — 检查错误:

package main

import (
    "fmt"
    "log"
    "example.com/greetings"
)

func main() {
    log.SetPrefix("greetings: ")
    log.SetFlags(0)

    // 多返回值:结果 + 错误
    message, err := greetings.Hello("")
    if err != nil {
        log.Fatal(err)  // 记录错误并退出程序
    }
    fmt.Println(message)
}
$ go run .
greetings: empty name
exit status 1

步骤4:返回随机问候

使用 math/rand/v2 从多个问候模板中随机选择:

package greetings

import (
    "fmt"
    "math/rand/v2"
)

func Hello(name string) (string, error) {
    if name == "" {
        return name, fmt.Errorf("empty name")
    }
    // 调用 randomFormat 获取随机问候模板
    message := fmt.Sprintf(randomFormat(), name)
    return message, nil
}

// Hellos 返回将人名映射到问候语的 map
func Hellos(names []string) (map[string]string, error) {
    messages := make(map[string]string)
    for _, name := range names {
        message, err := Hello(name)
        if err != nil {
            return nil, err
        }
        messages[name] = message
    }
    return messages, nil
}

// randomFormat 从一组问候模板中随机返回一个
func randomFormat() string {
    formats := []string{
        "Hi, %v. Welcome!",
        "Great to see you, %v!",
        "Hail, %v! Well met!",
    }
    return formats[rand.IntN(len(formats))]
}
$ go run .
Great to see you, Gladys!
$ go run .
Hail, Gladys! Well met!

步骤5:测试

greetings_test.go — 单元测试文件:

package greetings

import (
    "testing"
    "regexp"
)

// TestHelloName 验证 Hello 返回正确的问候语
func TestHelloName(t *testing.T) {
    name := "Gladys"
    want := regexp.MustCompile(`\b` + name + `\b`)
    msg, err := Hello("Gladys")
    if !want.MatchString(msg) || err != nil {
        t.Fatalf(`Hello("Gladys") = %q, %v, want match for %#q`, msg, err, want)
    }
}

// TestHelloEmpty 验证空名称时返回错误
func TestHelloEmpty(t *testing.T) {
    msg, err := Hello("")
    if msg != "" || err == nil {
        t.Fatalf(`Hello("") = %q, %v, want "", error`, msg, err)
    }
}
$ go test
PASS
ok      example.com/greetings   0.364s

$ go test -v  # 详细输出
=== RUN   TestHelloName
--- PASS: TestHelloName (0.00s)
=== RUN   TestHelloEmpty
--- PASS: TestHelloEmpty (0.00s)
PASS

步骤6:编译与安装

# 编译为可执行文件
$ go build

# 安装到 $GOPATH/bin(默认为 ~/go/bin)
$ go install

1.3 依赖管理 (Managing Dependencies)

来源:https://go.dev/doc/modules/managing-dependencies

Go 通过内置的 go 命令提供依赖管理。模块由 go.mod 文件定义,追踪提供所需包的模块。

关键命令

# 初始化模块
go mod init example.com/mymodule

# 添加依赖
go get example.com/theirmodule@v1.3.4

# 获取最新版本
go get example.com/theirmodule@latest

# 同步依赖(添加缺失、移除未用的)
go mod tidy

# 列出所有依赖及最新版本
go list -m -u all

# 移除依赖
go mod tidy
go get example.com/theirmodule@none

# 使用本地未发布代码(replace 指令)
go mod edit -replace example.com/theirmodule=../theirmodule

模块代理

# 使用私有代理
GOPROXY="https://proxy.example.com,direct"

默认从 proxy.golang.org(Google 运行的镜像)下载模块。

工具依赖 (Go 1.24+)

# 添加工具依赖
go get -tool golang.org/x/tools/cmd/stringer

# 运行工具
go tool stringer

模块路径命名

  • 使用仓库 URL:github.com/user/repo
  • v2+ 在路径末尾加主版本号:example.com/mymodule/v2
  • 保留前缀:testexample

最佳实践

  1. 始终将 go.modgo.sum 提交到版本控制
  2. 依赖变更后运行 go mod tidy
  3. 生产环境使用显式版本而非分支名
  4. 定期更新依赖获取安全修复:go get -u

1.4 开发 Web 服务 (RESTful API with Gin)

来源:https://go.dev/doc/tutorial/web-service-gin

使用 Gin Web 框架构建管理唱片集的 RESTful API。

完整代码

package main

import (
    "net/http"
    "github.com/gin-gonic/gin"
)

// album 表示唱片数据
type album struct {
    ID     string  `json:"id"`
    Title  string  `json:"title"`
    Artist string  `json:"artist"`
    Price  float64 `json:"price"`
}

// albums 初始化唱片数据
var albums = []album{
    {ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
    {ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
    {ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
}

func main() {
    router := gin.Default()
    router.GET("/albums", getAlbums)        // 获取所有唱片
    router.GET("/albums/:id", getAlbumByID) // 按 ID 获取唱片
    router.POST("/albums", postAlbums)      // 添加新唱片

    router.Run("localhost:8080")
}

// getAlbums 以 JSON 返回所有唱片
func getAlbums(c *gin.Context) {
    c.IndentedJSON(http.StatusOK, albums)
}

// postAlbums 从请求体中解析 JSON 添加到唱片集
func postAlbums(c *gin.Context) {
    var newAlbum album
    if err := c.BindJSON(&newAlbum); err != nil {
        return
    }
    albums = append(albums, newAlbum)
    c.IndentedJSON(http.StatusCreated, newAlbum)
}

// getAlbumByID 按 ID 查找并返回唱片
func getAlbumByID(c *gin.Context) {
    id := c.Param("id")
    for _, a := range albums {
        if a.ID == id {
            c.IndentedJSON(http.StatusOK, a)
            return
        }
    }
    c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"})
}
# 初始化并运行
$ go mod init example/web-service-gin
$ go get .
$ go run .

# 使用 curl 测试
$ curl http://localhost:8080/albums
$ curl http://localhost:8080/albums/2

# 添加新唱片
$ curl http://localhost:8080/albums \
    --include \
    --header "Content-Type: application/json" \
    --request "POST" \
    --data '{"id": "4","title": "The Modern Sound of Betty Carter","artist": "Betty Carter","price": 49.99}'

API 端点

方法 端点 说明
GET /albums 返回所有唱片 JSON
POST /albums 从请求体 JSON 添加新唱片
GET /albums/:id 按 ID 返回特定唱片

1.5 A Tour of Go

来源:https://go.dev/tour/

A Tour of Go 是 Go 官方交互式导览教程,可在浏览器中直接运行代码。分为四大模块:

模块结构

模块 内容
Basics(基础) 包、变量、函数、流程控制(for/if/else/switch/defer)、结构体、切片、映射
Methods and Interfaces(方法和接口) 方法接收器、接口(隐式实现)
Generics(泛型) 类型参数的泛型编程
Concurrency(并发) Goroutines、Channels 和并发模式

交互特性

  • 点击 Run(或 Shift+Enter)在远程沙箱编译运行代码
  • 代码可修改并重新运行
  • Format 按钮使用 gofmt 格式化代码
  • 支持简体中文等多语言翻译

本地运行

go tool tour

沙箱限制

  • 时间起始于 2009-11-10 23:00:00 UTC
  • 执行时间/CPU/内存有上限
  • 无法访问外部网络

1.6 Effective Go 要点

来源:https://go.dev/doc/effective_go

Effective Go 是编写清晰、高效、惯用 Go 代码的官方指南。以下为最核心的惯例摘要:

格式化

  • 缩进使用制表符(tab),始终运行 gofmt
  • 大括号与控制语句在同一行
  • imports 按字母排序,标准库与第三方库之间用空行分隔

命名规范

元素 规范
包名 简短、小写、单单词、无下划线
导出名 MixedCaps(首字母大写)
非导出名 mixedCaps(首字母小写)
Getter 不加 Get 前缀,字段 owner 的 getter 为 Owner()
单方法接口 方法名 + -erReaderWriterStringer

控制结构

// if 支持初始化子句
if err := file.Open("data.txt"); err != nil {
    log.Fatal("Failed to open file:", err)
}

// 不要悬挂 else — if 以 return 结束时省略 else
if err != nil {
    return err
}
// happy path 在左对齐位置

错误处理

  • 错误是 — 返回并检查,而非抛出异常
  • 使用 fmt.Errorf%w 包装错误
  • panic 仅用于不可恢复的情况
  • 使用 errors.Iserrors.As 检查错误链
data, err := os.ReadFile(path)
if err != nil {
    return nil, fmt.Errorf("read file failed: %w", err)
}

并发

核心箴言:“不要通过共享内存来通信,而要通过通信来共享内存。”

// goroutine + channel 并发模式
func producer(ch chan<- int) {
    for i := 0; i < 5; i++ {
        ch <- i
    }
    close(ch)
}

func consumer(ch <-chan int) {
    for num := range ch {
        fmt.Println("Consumed:", num)
    }
}

func main() {
    ch := make(chan int)
    go producer(ch)
    consumer(ch)
}

接口

  • 隐式满足 — 类型从不声明实现了哪些接口
  • 保持接口小(1-3 个方法)
  • 接受接口,返回结构体 — 参数用接口获得灵活性,返回值用具体类型

零值

每个类型都有有用的零值

  • int → 0,string"",指针/切片/映射/channel → nil
  • nil 切片可以被追加,空 bytes.Buffer 无需初始化即可写入

1.7 Go 文档总览

来源:https://go.dev/doc/

核心文档资源

资源 链接 说明
Go 语言规范 /ref/spec 完整语法和语义定义
Go 用户手册 /doc 安装、教程、指南汇总
标准库 pkg.go.dev/std 所有标准包文档
发行说明 /doc/devel/release 各版本变更记录
Effective Go /doc/effective_go 编程最佳实践
FAQ /doc/faq 常见问题解答

教程列表

  • Getting Started — Go 入门
  • Create a Module — 创建 Go 模块
  • Developing a RESTful API with Gin — Web 服务开发
  • Accessing a relational database — 数据库访问
  • Generating gRPC code — gRPC 代码生成
  • Getting started with generics — 泛型入门
  • Getting started with fuzzing — 模糊测试

模块参考


1.8 Go 学习路径推荐

新手入门路径

  1. Tour of Go — 交互式学习 Go 基础(https://go.dev/tour/
  2. Getting Started 教程 — 安装并编写第一个程序
  3. Create a Module 教程 — 理解模块和包
  4. Go by Example — 通过示例学习具体语法
  5. Effective Go — 学习 Go 惯用写法

进阶学习资源

  • Web 开发: Go Web Examples
  • CLI 应用: Building an Awesome CLI App in Go
  • Go 标准库参考: 详见 go_standard_library_reference.md
  • 推荐书籍: “The Go Programming Language” (Donovan & Kernighan)、“Concurrency in Go” (Katherine Cox-Buday)

第二部分:Go by Example 完整代码示例

来源:Go by Example — 通过带注释的示例程序学习 Go(作者:Mark McGranaghan & Eli Bendersky)


2.1 Hello World

第一个程序,打印经典的 “hello world” 消息。

package main

import "fmt"

func main() {
    fmt.Println("hello world")
}
# 直接运行
$ go run hello-world.go
hello world

# 编译为二进制文件
$ go build hello-world.go
$ ls
hello-world    hello-world.go

# 直接执行二进制文件
$ ./hello-world
hello world

2.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)
}
$ go run values.go
golang
1+1 = 2
7.0/3.0 = 2.3333333333333335
false
true
false

2.3 Variables(变量)

Go 中变量需要显式声明,编译器用于检查函数调用的类型正确性。

package main

import "fmt"

func main() {
    // var 声明一个或多个变量
    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)
}
$ go run variables.go
initial
1 2
true
0
apple

2.4 Constants(常量)

Go 支持字符、字符串、布尔值和数值常量。

package main

import (
    "fmt"
    "math"
)

// const 声明常量值
const s string = "constant"

func main() {
    fmt.Println(s)

    // const 语句也可以出现在函数体内
    const n = 500000000

    // 常量表达式以任意精度执行算术运算
    const d = 3e20 / n
    fmt.Println(d)

    // 数值常量在赋值时确定类型
    fmt.Println(int64(d))

    // 在需要特定类型的上下文中使用时会获得类型
    fmt.Println(math.Sin(n))
}
$ go run constant.go
constant
6e+11
600000000000
-0.28470407323754404

2.5 For(循环)

for 是 Go 唯一的循环结构。

package main

import "fmt"

func main() {
    // 最基本的:单条件循环(类似 while)
    i := 1
    for i <= 3 {
        fmt.Println(i)
        i = i + 1
    }

    // 经典的 初始化/条件/后续 for 循环
    for j := 0; j < 3; j++ {
        fmt.Println(j)
    }

    // range 遍历整数(Go 1.22+ 特性)
    for i := range 3 {
        fmt.Println("range", i)
    }

    // 不带条件的无限循环,直到 break
    for {
        fmt.Println("loop")
        break
    }

    // continue 跳过当前迭代
    for n := range 6 {
        if n%2 == 0 {
            continue
        }
        fmt.Println(n)
    }
}
$ go run for.go
1
2
3
0
1
2
range 0
range 1
range 2
loop
1
3
5

2.6 If/Else(条件分支)

Go 的 ifelse 条件分支。

package main

import "fmt"

func main() {
    // 基本用法
    if 7%2 == 0 {
        fmt.Println("7 is even")
    } else {
        fmt.Println("7 is odd")
    }

    // 可以没有 else
    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")
    }
}
$ go run if-else.go
7 is odd
8 is divisible by 4
either 8 or 7 are even
9 has 1 digit

注意: Go 中条件不需要括号,但大括号是必需的。Go 没有三元运算符(?:)。


2.7 Switch(分支语句)

switch 表达多个分支的条件判断。

package main

import (
    "fmt"
    "time"
)

func main() {
    // 基本 switch
    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")
    }

    // 不带表达式的 switch(替代 if/else 链)
    t := time.Now()
    switch {
    case t.Hour() < 12:
        fmt.Println("It's before noon")
    default:
        fmt.Println("It's after noon")
    }

    // 类型 switch:比较类型而非值
    whatAmI := func(i interface{}) {
        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")
}
$ go run switch.go
Write 2 as two
It's a weekday
It's after noon
I'm a bool
I'm an int
Don't know type string

2.8 Arrays(数组)

在 Go 中,array 是固定长度的、编号的元素序列。

package main

import "fmt"

func main() {
    // 创建包含 5 个 int 的数组,默认零值
    var a [5]int
    fmt.Println("emp:", a)

    // 通过索引设置和获取值
    a[4] = 100
    fmt.Println("set:", a)
    fmt.Println("get:", a[4])

    // 内置 len 函数返回数组长度
    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)
}
$ go run arrays.go
emp: [0 0 0 0 0]
set: [0 0 0 0 100]
get: 100
len: 5
dcl: [1 2 3 4 5]
dcl: [1 2 3 4 5]
idx: [100 0 0 400 500]
2d:  [[0 1 2] [1 2 3]]
2d:  [[1 2 3] [1 2 3]]

2.9 Slices(切片)

Slice 是 Go 中重要的数据类型,提供比数组更强大的序列操作接口。

package main

import (
    "fmt"
    "slices"
)

func main() {
    // 未初始化的 slice 为 nil,长度为 0
    var s []string
    fmt.Println("uninit:", s, s == nil, len(s) == 0)

    // 使用 make 创建非零长度的 slice
    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))

    // append 追加元素
    s = append(s, "d")
    s = append(s, "e", "f")
    fmt.Println("apd:", s)

    // copy 复制 slice
    c := make([]string, len(s))
    copy(c, s)
    fmt.Println("cpy:", c)

    // 切片操作 slice[low:high]
    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)

    // slices 包提供的工具函数
    t2 := []string{"g", "h", "i"}
    if slices.Equal(t, t2) {
        fmt.Println("t == t2")
    }

    // 二维 slice(内层长度可以不同)
    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)
}
$ go run slices.go
uninit: [] true true
emp: [  ] len: 3 cap: 3
set: [a b c]
get: c
len: 3
apd: [a b c d e f]
cpy: [a b c d e f]
sl1: [c d e]
sl2: [a b c d e]
sl3: [c d e f]
dcl: [g h i]
t == t2
2d:  [[0] [1 2] [2 3 4]]

2.10 Maps(映射)

Map 是 Go 内置的关联数据类型(其他语言中称为哈希或字典)。

package main

import (
    "fmt"
    "maps"
)

func main() {
    // 使用 make 创建空 map
    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)

    // len 返回键值对数量
    fmt.Println("len:", len(m))

    // delete 删除键值对
    delete(m, "k2")
    fmt.Println("map:", m)

    // clear 清空 map
    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)

    // maps 包提供的工具函数
    n2 := map[string]int{"foo": 1, "bar": 2}
    if maps.Equal(n, n2) {
        fmt.Println("n == n2")
    }
}
$ go run maps.go
map: map[k1:7 k2:13]
v1: 7
v3: 0
len: 2
map: map[k1:7]
map: map[]
prs: false
map: map[bar:2 foo:1]
n == n2

2.11 Functions(函数)

函数是 Go 的核心组成部分。

package main

import "fmt"

// plus 接受两个 int,返回它们的和
func plus(a int, b int) int {
    // Go 需要显式 return
    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)
}
$ go run functions.go
1+2 = 3
1+2+3 = 6

2.12 Multiple Return Values(多返回值)

Go 内置支持多返回值,常用于返回结果和错误值。

package main

import "fmt"

// vals 返回两个 int
func vals() (int, int) {
    return 3, 7
}

func main() {
    // 多赋值接收多个返回值
    a, b := vals()
    fmt.Println(a)
    fmt.Println(b)

    // 使用空白标识符 _ 忽略不需要的值
    _, c := vals()
    fmt.Println(c)
}
$ go run multiple-return-values.go
3
7
7

2.13 Variadic Functions(可变参数函数)

可变参数函数可以用任意数量的尾随参数调用。

package main

import "fmt"

// sum 接受任意数量的 int 参数
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)

    // 展开 slice 传入
    nums := []int{1, 2, 3, 4}
    sum(nums...)
}
$ go run variadic-functions.go
[1 2] 3
[1 2 3] 6
[1 2 3 4] 10

2.14 Closures(闭包)

Go 支持匿名函数,可以形成闭包。

package main

import "fmt"

// intSeq 返回一个闭包,每次调用递增 i
func intSeq() func() int {
    i := 0
    return func() int {
        i++
        return i
    }
}

func main() {
    nextInt := intSeq()

    // 每次调用更新捕获的 i
    fmt.Println(nextInt())
    fmt.Println(nextInt())
    fmt.Println(nextInt())

    // 新闭包有独立的 i
    newInts := intSeq()
    fmt.Println(newInts())
}
$ go run closures.go
1
2
3
1

2.15 Recursion(递归)

Go 支持递归函数。

package main

import "fmt"

// fact 递归计算阶乘
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))
}
$ go run recursion.go
5040
13

2.16 Range over Built-in Types(遍历内置类型)

range 迭代各种内置数据结构中的元素。

package main

import "fmt"

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

    // range 提供索引和值
    for i, num := range nums {
        if num == 3 {
            fmt.Println("index:", i)
        }
    }

    // 遍历 map 的键值对
    kvs := map[string]string{"a": "apple", "b": "banana"}
    for k, v := range kvs {
        fmt.Printf("%s -> %s\n", k, v)
    }

    // 只遍历 map 的键
    for k := range kvs {
        fmt.Println("key:", k)
    }

    // 遍历字符串的 Unicode 码点
    for i, c := range "go" {
        fmt.Println(i, c)
    }
}
$ go run range-over-built-in-types.go
sum: 9
index: 1
a -> apple
b -> banana
key: a
key: b
0 103
1 111

2.17 Pointers(指针)

Go 支持指针,允许在程序中传递值和记录的引用。

package main

import "fmt"

// zeroval 通过值传递,不改变原值
func zeroval(ival int) {
    ival = 0
}

// zeroptr 通过指针传递,改变原值
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)  // 变为 0

    fmt.Println("pointer:", &i)  // 内存地址
}
$ go run pointers.go
initial: 1
zeroval: 1
zeroptr: 0
pointer: 0x42131100

2.18 Strings and Runes(字符串与字符)

Go 字符串是只读的字节切片。Go 中的字符称为 rune(Unicode 码点)。

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()

    // 实际字符数(rune 数)
    fmt.Println("Rune count:", utf8.RuneCountInString(s))

    // range 自动解码 UTF-8
    for idx, runeValue := range s {
        fmt.Printf("%#U starts at %d\n", runeValue, idx)
    }

    // 使用 utf8.DecodeRuneInString 显式解码
    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")
    }
}
$ go run strings-and-runes.go
Len: 18
e0 b8 aa e0 b8 a7 e0 b8 b1 e0 b8 aa e0 b8 94 e0 b8 b5
Rune count: 6
U+0E2A 'ส' starts at 0
U+0E27 'ว' starts at 3
U+0E31 'ั' starts at 6
U+0E2A 'ส' starts at 9
U+0E14 'ด' starts at 12
U+0E35 'ี' starts at 15

2.19 Structs(结构体)

Go 的 struct 是字段的类型化集合,用于将数据组合为记录。

package main

import "fmt"

// person 结构体包含 name 和 age 字段
type person struct {
    name string
    age  int
}

// newPerson 使用给定名称构造一个新的 person 结构体
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)
}
$ go run structs.go
{Bob 20}
{Alice 30}
{Fred 0}
&{Ann 40}
&{Jon 42}
Sean
50
51

2.20 Methods(方法)

Go 支持在结构体上定义方法。

package main

import "fmt"

// rect 结构体
type rect struct {
    width, height int
}

// area 方法接收器为 rect 值类型
func (r rect) area() int {
    return r.width * r.height
}

// perim 方法接收器为 rect 指针类型
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())

    // Go 自动处理值和指针的转换
    rp := &r
    fmt.Println("area: ", rp.area())
    fmt.Println("perim:", rp.perim())
}
$ go run methods.go
area:  50
perim: 30
area:  50
perim: 30

2.21 Interfaces(接口)

接口是命名的方法集合,Go 的接口实现是隐式的。

package main

import (
    "fmt"
    "math"
)

// geometry 接口定义了面积和周长方法
type geometry interface {
    area() float64
    perim() float64
}

// rect 实现 geometry 接口
type rect struct {
    width, height float64
}

func (r rect) area() float64 {
    return r.width * r.height
}

func (r rect) perim() float64 {
    return 2*r.width + 2*r.height
}

// circle 也实现 geometry 接口
type circle struct {
    radius float64
}

func (c circle) area() float64 {
    return math.Pi * c.radius * c.radius
}

func (c circle) perim() float64 {
    return 2 * math.Pi * c.radius
}

// measure 接受任何实现了 geometry 接口的类型
func measure(g geometry) {
    fmt.Println(g)
    fmt.Println(g.area())
    fmt.Println(g.perim())
}

func main() {
    r := rect{width: 3, height: 4}
    c := circle{radius: 5}

    measure(r)
    measure(c)
}
$ go run interfaces.go
{3 4}
12
14
{5}
78.53981633974483
31.41592653589793

2.22 Enums(枚举)

Go 没有内置枚举类型,但可以使用常量配合 iota 实现枚举模式。

package main

import "fmt"

// ServerState 定义为整数类型
type ServerState int

// 使用 iota 定义枚举常量
const (
    StateIdle ServerState = iota
    StateConnected
    StateError
    StateRetrying
)

// stateTransition 定义状态名称映射
var stateName = map[ServerState]string{
    StateIdle:      "idle",
    StateConnected: "connected",
    StateError:     "error",
    StateRetrying:  "retrying",
}

// String 方法使枚举可打印
func (s ServerState) String() string {
    return stateName[s]
}

func main() {
    ns := transition(StateIdle)
    fmt.Println(ns)

    ns2 := transition(ns)
    fmt.Println(ns2)
}

// transition 模拟状态转换
func transition(s ServerState) ServerState {
    switch s {
    case StateIdle:
        return StateConnected
    case StateConnected, StateRetrying:
        return StateIdle
    case StateError:
        return StateError
    default:
        return StateIdle
    }
}
$ go run enums.go
connected
idle

2.23 Struct Embedding(结构体嵌入)

Go 支持结构体嵌入,实现组合而非继承。

package main

import "fmt"

// base 基础结构体
type base struct {
    num int
}

func (b base) describe() string {
    return fmt.Sprintf("base with num=%v", b.num)
}

// container 嵌入了 base
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())
}
$ go run struct-embedding.go
co={num: 1, str: some name}
also num: 1
describe: base with num=1
describer: base with num=1

2.24 Generics(泛型)

从 Go 1.18 开始支持泛型,允许类型参数化的函数和类型。

package main

import "fmt"

// MapKeys 泛型函数:返回 map 中所有键的切片
func MapKeys[K comparable, V any](m map[K]V) []K {
    r := make([]K, 0, len(m))
    for k := range m {
        r = append(r, k)
    }
    return r
}

// List 泛型类型:单链表
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]) GetAll() []T {
    var elems []T
    for e := lst.head; e != nil; e = e.next {
        elems = append(elems, e.val)
    }
    return elems
}

func main() {
    var m = map[int]string{1: "one", 2: "two"}
    fmt.Println("keys:", MapKeys(m))

    _ = MapKeys(m)

    lst := List[int]{}
    lst.Push(10)
    lst.Push(13)
    lst.Push(23)
    fmt.Println("list:", lst.GetAll())
}
$ go run generics.go
keys: [1 2]
list: [10 13 23]

2.25 Range over Iterators(遍历迭代器)

从 Go 1.23 开始,支持在 range 循环中使用迭代器。

package main

import (
    "fmt"
    "iter"
    "slices"
    "strings"
)

// List 链表类型
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
    }
}

// All 返回一个迭代器
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
            }
        }
    }
}

// genFib 生成斐波那契数列迭代器
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)

    // 使用迭代器进行 range 循环
    for e := range lst.All() {
        fmt.Println(e)
    }

    // slices.Collect 从迭代器收集所有值到 slice
    all := slices.Collect(lst.All())
    fmt.Println("all:", all)

    // strings.SplitSeq 返回迭代器
    for part := range strings.SplitSeq("go-by-example", "-") {
        fmt.Printf("part: %s\n", part)
    }

    // 斐波那契迭代器
    for n := range genFib() {
        if n >= 10 {
            break  // break 时 yield 返回 false
        }
        fmt.Println(n)
    }
}
$ go run range-over-iterators.go
10
13
23
all: [10 13 23]
part: go
part: by
part: example
0
1
1
2
3
5
8

2.26 Errors(错误)

Go 通过显式的错误返回值来表示错误。

package main

import (
    "errors"
    "fmt"
)

// f 返回错误,当参数为 -1 时
func f(arg int) (int, error) {
    if arg == -1 {
        return -1, errors.New("can't work with -1")
    }
    return arg + 3, nil
}

func main() {
    // 检查错误
    for _, i := range []int{7, -1} {
        if r, e := f(i); e != nil {
            fmt.Println("f failed:", e)
        } else {
            fmt.Println("f worked:", r)
        }
    }
}
$ go run errors.go
f worked: 10
f failed: can't work with -1

2.27 Custom Errors(自定义错误)

可以通过实现 Error() 方法来自定义错误类型。

package main

import (
    "fmt"
    "time"
)

// MyError 自定义错误类型
type MyError struct {
    When time.Time
    What string
}

func (e *MyError) Error() string {
    return fmt.Sprintf("at %v, %s", e.When, e.What)
}

// run 返回自定义错误
func run() error {
    return &MyError{
        time.Now(),
        "it didn't work",
    }
}

func main() {
    if err := run(); err != nil {
        fmt.Println(err)
    }
}
$ go run custom-errors.go
at 2024-01-01 15:04:05, it didn't work

2.28 Goroutines(协程)

Goroutine 是轻量级的执行线程。

package main

import (
    "fmt"
    "time"
)

// f 循环打印内容
func f(from string) {
    for i := range 3 {
        fmt.Println(from, ":", i)
    }
}

func main() {
    // 直接同步调用
    f("direct")

    // 使用 go 关键字启动 goroutine 并发执行
    go f("goroutine")

    // 为匿名函数启动 goroutine
    go func(msg string) {
        fmt.Println(msg)
    }("going")

    // 等待 goroutine 执行完成
    time.Sleep(time.Second)
    fmt.Println("done")
}
$ go run goroutines.go
direct : 0
direct : 1
direct : 2
goroutine : 0
going
goroutine : 1
goroutine : 2
done

2.29 Channels(通道)

通道是连接并发 goroutine 的管道。

package main

import "fmt"

func main() {
    // 使用 make(chan val-type) 创建新通道
    messages := make(chan string)

    // 在 goroutine 中向通道发送值
    go func() {
        messages <- "ping"
    }()

    // 从通道接收值
    msg := <-messages
    fmt.Println(msg)
}
$ go run channels.go
ping

2.30 Channel Buffering(通道缓冲)

默认情况下通道是无缓冲的。带缓冲的通道允许在没有接收方的情况下发送有限的数值。

package main

import "fmt"

func main() {
    // 创建缓冲大小为 2 的字符串通道
    messages := make(chan string, 2)

    // 可以发送值而无需对应接收方
    messages <- "buffered"
    messages <- "channel"

    // 后续正常接收
    fmt.Println(<-messages)
    fmt.Println(<-messages)
}
$ go run channel-buffering.go
buffered
channel

2.31 Channel Synchronization(通道同步)

使用通道同步 goroutine 之间的执行。

package main

import (
    "fmt"
    "time"
)

// worker 在通道上发送信号通知完成
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)

    // 阻塞等待 worker 完成
    <-done
}
$ go run channel-synchronization.go
working...done

2.32 Channel Directions(通道方向)

函数参数中可以指定通道方向,提高类型安全性。

package main

import "fmt"

// ping 只发送通道
func ping(pings chan<- string, msg string) {
    pings <- msg
}

// pong 接收通道和只发送通道
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)
}
$ go run channel-directions.go
passed message

2.33 Select(多路选择)

select 让 goroutine 等待多个通道操作。

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"
    }()

    // select 同时等待两个通道
    for range 2 {
        select {
        case msg1 := <-c1:
            fmt.Println("received", msg1)
        case msg2 := <-c2:
            fmt.Println("received", msg2)
        }
    }
}
$ go run select.go
received one
received two

2.34 Timeouts(超时)

使用通道和 select 实现超时。

package main

import (
    "fmt"
    "time"
)

func main() {
    c1 := make(chan string, 1)
    go func() {
        time.Sleep(2 * time.Second)
        c1 <- "result 1"
    }()

    // select 实现超时:等待结果或等待 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")
    }
}
$ go run timeouts.go
timeout 1
result 2

2.35 Non-Blocking Channel Operations(非阻塞通道操作)

使用 selectdefault 分支实现非阻塞的通道操作。

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
    select {
    case msg := <-messages:
        fmt.Println("received message", msg)
    case sig := <-signals:
        fmt.Println("received signal", sig)
    default:
        fmt.Println("no activity")
    }
}
$ go run non-blocking-channel-operations.go
no message received
no message sent
no activity

2.36 Closing Channels(关闭通道)

关闭通道表示不再有值发送。使用 range 遍历已关闭的通道。

package main

import "fmt"

func main() {
    jobs := make(chan int, 5)
    done := make(chan bool)

    // 启动 worker goroutine
    go func() {
        for {
            j, more := <-jobs
            if more {
                fmt.Println("received job", j)
            } else {
                fmt.Println("received all jobs")
                done <- true
                return
            }
        }
    }()

    // 发送 3 个任务
    for j := 1; j <= 3; j++ {
        jobs <- j
        fmt.Println("sent job", j)
    }
    close(jobs)  // 关闭通道
    fmt.Println("sent all jobs")

    <-done  // 等待 worker 完成
}
$ go run closing-channels.go
sent job 1
received job 1
sent job 2
received job 2
sent job 3
received job 3
sent all jobs
received all jobs

2.37 Range over Channels(遍历通道)

使用 for range 遍历通道中的值。

package main

import "fmt"

func main() {
    queue := make(chan string, 2)
    queue <- "one"
    queue <- "two"
    close(queue)

    // range 遍历通道直到关闭
    for elem := range queue {
        fmt.Println(elem)
    }
}
$ go run range-over-channels.go
one
two

2.38 Timers(定时器)

定时器表示未来的单一事件。

package main

import (
    "fmt"
    "time"
)

func main() {
    // timer1 将在 2 秒后触发
    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)
}
$ go run timers.go
Timer 1 fired
Timer 2 stopped

2.39 Tickers(周期定时器)

Ticker 按指定间隔重复触发。

package main

import (
    "fmt"
    "time"
)

func main() {
    // 创建每 500ms 触发一次的 ticker
    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()  // 停止 ticker
    done <- true
    fmt.Println("Ticker stopped")
}
$ go run tickers.go
Tick at 2024-01-01 15:04:05.123 ...
Tick at 2024-01-01 15:04:05.623 ...
Tick at 2024-01-01 15:04:06.123 ...
Ticker stopped

2.40 Worker Pools(工作池)

使用 goroutine 和通道实现工作池模式。

package main

import (
    "fmt"
    "time"
)

// worker 从 jobs 通道接收工作,结果发送到 results 通道
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)

    // 启动 3 个 worker
    for w := range 3 {
        go worker(w, jobs, results)
    }

    // 发送 5 个任务
    for j := 1; j <= numJobs; j++ {
        jobs <- j
    }
    close(jobs)

    // 收集结果
    for range numJobs {
        <-results
    }
}
$ go run worker-pools.go
worker 0 started job 1
worker 1 started job 2
worker 2 started job 3
worker 2 finished job 3
worker 2 started job 4
worker 1 finished job 2
worker 1 started job 5
worker 0 finished job 1
worker 2 finished job 4
worker 1 finished job 5

2.41 WaitGroups(等待组)

使用 sync.WaitGroup 等待多个 goroutine 完成。

package main

import (
    "fmt"
    "sync"
    "time"
)

// worker 执行任务,完成后通知 WaitGroup
func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done()  // 函数返回时通知完成

    fmt.Printf("Worker %d starting\n", id)
    time.Sleep(time.Second)
    fmt.Printf("Worker %d done\n", id)
}

func main() {
    var wg sync.WaitGroup

    // 启动多个 worker 并增加计数
    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go worker(i, &wg)
    }

    // 等待所有 worker 完成
    wg.Wait()
}
$ go run waitgroups.go
Worker 5 starting
Worker 3 starting
Worker 4 starting
Worker 1 starting
Worker 2 starting
Worker 2 done
Worker 3 done
Worker 4 done
Worker 1 done
Worker 5 done

2.42 Rate Limiting(速率限制)

使用通道和 ticker 实现速率限制。

package main

import (
    "fmt"
    "time"
)

func main() {
    requests := make(chan int, 5)
    for i := 1; i <= 5; i++ {
        requests <- i
    }
    close(requests)

    // limiter 每 200ms 接收一个值
    limiter := time.Tick(200 * time.Millisecond)

    for req := range requests {
        <-limiter  // 每次请求前等待限速
        fmt.Println("request", req, time.Now())
    }

    // 突发速率限制:允许前 3 个立即处理
    burstyLimiter := make(chan time.Time, 3)
    for i := 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())
    }
}
$ go run rate-limiting.go
request 1 2024-01-01 15:04:05.200 ...
request 2 2024-01-01 15:04:05.400 ...
request 3 2024-01-01 15:04:05.600 ...
request 4 2024-01-01 15:04:05.800 ...
request 5 2024-01-01 15:04:06.000 ...
request 1 2024-01-01 15:04:06.000 ...
request 2 2024-01-01 15:04:06.000 ...
request 3 2024-01-01 15:04:06.000 ...
request 4 2024-01-01 15:04:06.200 ...
request 5 2024-01-01 15:04:06.400 ...

2.43 Atomic Counters(原子计数器)

使用 sync/atomic 包管理跨 goroutine 的原子计数器。

package main

import (
    "fmt"
    "sync/atomic"
    "time"
)

func main() {
    var ops uint64

    // 50 个 goroutine 同时增加计数器
    for range 50 {
        go func() {
            for {
                atomic.AddUint64(&ops, 1)
                time.Sleep(time.Millisecond)
            }
        }()
    }

    time.Sleep(time.Second)

    // 安全地读取最终值
    opsFinal := atomic.LoadUint64(&ops)
    fmt.Println("ops:", opsFinal)
}
$ go run atomic-counters.go
ops: 40921

2.44 Mutexes(互斥锁)

使用 sync.Mutex 安全地访问共享状态。

package main

import (
    "fmt"
    "sync"
)

// Container 持有计数器映射
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.Done()
    }

    wg.Add(3)
    go doIncrement("a", 10000)
    go doIncrement("a", 10000)
    go doIncrement("b", 10000)

    wg.Wait()
    fmt.Println(c.counters)
}
$ go run mutexes.go
map[a:20000 b:10000]

2.45 Stateful Goroutines(有状态协程)

使用 goroutine 和通道管理状态,而非互斥锁。

package main

import (
    "fmt"
    "math/rand"
    "sync/atomic"
    "time"
)

// readOp 封装读取操作
type readOp struct {
    key  int
    resp chan int
}

// writeOp 封装写入操作
type writeOp struct {
    key  int
    val  int
    resp chan bool
}

func main() {
    var readOps, writeOps uint64

    reads := make(chan readOp)
    writes := make(chan writeOp)

    // 状态 owner goroutine
    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
            }
        }
    }()

    // 100 个读取 goroutine
    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)
            }
        }()
    }

    // 10 个写入 goroutine
    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)
}
$ go run stateful-goroutines.go
readOps: 71708
writeOps: 7177

2.46 Sorting(排序)

Go 的 slices 包内置排序支持。

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)
}
$ go run sorting.go
Strings: [a b c]
Ints:    [2 4 7]
Sorted:  true

2.47 Sorting by Functions(按函数排序)

使用 slices.SortFunc 实现自定义排序。

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{
        {name: "Jax", age: 37},
        {name: "TJ", age: 25},
        {name: "Alex", age: 72},
    }

    // 按年龄排序
    slices.SortFunc(people, func(a, b Person) int {
        return cmp.Compare(a.age, b.age)
    })
    fmt.Println(people)
}
$ go run sorting-by-functions.go
[kiwi peach banana]
[{TJ 25} {Jax 37} {Alex 72}]

2.48 Panic(异常)

panic 用于表示程序无法恢复的错误。

package main

import "os"

func main() {
    // 会在第一处 panic 处停止
    panic("a problem")

    // 以下代码不会执行
    _, err := os.Create("/tmp/file")
    if err != nil {
        panic(err)
    }
}
$ go run panic.go
panic: a problem

goroutine 1 [running]:
main.main()
    /.../panic.go:7 +0x39
...

2.49 Defer(延迟执行)

defer 确保函数调用在函数结束时执行,常用于资源清理。

package main

import (
    "fmt"
    "os"
)

func main() {
    // 创建文件
    f := createFile("/tmp/defer.txt")
    defer closeFile(f)  // 在 main 结束时执行
    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")
    f.Close()
}
$ go run defer.go
creating
writing
closing

2.50 Recover(恢复)

recover 可以捕获 panic,使程序继续执行。

package main

import "fmt"

// mayPanic 触发 panic
func mayPanic() {
    panic("a problem")
}

func main() {
    // defer 中的 recover 捕获 panic
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered. Error:\n", r)
        }
    }()

    mayPanic()

    fmt.Println("After mayPanic()")  // 不会执行
}
$ go run recover.go
Recovered. Error:
 a problem

2.51 String Functions(字符串函数)

strings 包提供了丰富的字符串处理函数。

package main

import (
    "fmt"
    s "strings"
)

var p = fmt.Println

func main() {
    p("Contains:  ", s.Contains("test", "es"))          // true
    p("Count:     ", s.Count("test", "t"))               // 2
    p("HasPrefix: ", s.HasPrefix("test", "te"))          // true
    p("HasSuffix: ", s.HasSuffix("test", "st"))          // true
    p("Index:     ", s.Index("test", "e"))               // 1
    p("Join:      ", s.Join([]string{"a", "b"}, "-"))    // "a-b"
    p("Repeat:    ", s.Repeat("a", 5))                   // "aaaaa"
    p("Replace:   ", s.Replace("foo", "o", "0", -1))     // "f00"
    p("Replace:   ", s.Replace("foo", "o", "0", 1))      // "f0o"
    p("Split:     ", s.Split("a-b-c-d", "-"))            // [a b c d]
    p("ToLower:   ", s.ToLower("TEST"))                  // "test"
    p("ToUpper:   ", s.ToUpper("test"))                  // "TEST"
}
$ go run string-functions.go
Contains:   true
Count:      2
HasPrefix:  true
HasSuffix:  true
Index:      1
Join:       a-b
Repeat:     aaaaa
Replace:    f00
Replace:    f0o
Split:      [a b c d]
ToLower:    test
ToUpper:    TEST

2.52 String Formatting(字符串格式化)

Go 提供通过 fmt.Printf 进行传统的 printf 格式化。

package main

import (
    "fmt"
    "os"
)

type point struct {
    x, y int
}

func main() {
    p := point{1, 2}

    fmt.Printf("struct1: %v\n", p)     // {1 2}
    fmt.Printf("struct2: %+v\n", p)    // {x:1 y:2}
    fmt.Printf("struct3: %#v\n", p)    // main.point{x:1, y:2}
    fmt.Printf("type: %T\n", p)        // main.point
    fmt.Printf("bool: %t\n", true)     // true
    fmt.Printf("int: %d\n", 123)       // 123
    fmt.Printf("bin: %b\n", 14)        // 1110
    fmt.Printf("char: %c\n", 33)       // !
    fmt.Printf("hex: %x\n", 456)       // 1c8
    fmt.Printf("float1: %f\n", 78.9)   // 78.900000
    fmt.Printf("float2: %e\n", 123400000.0)  // 1.234000e+08
    fmt.Printf("float3: %E\n", 123400000.0)  // 1.234000E+08
    fmt.Printf("str1: %s\n", "\"string\"")   // "string"
    fmt.Printf("str2: %q\n", "\"string\"")   // "\"string\""
    fmt.Printf("str3: %x\n", "hex this")     // 6865782074686973
    fmt.Printf("pointer: %p\n", &p)          // 0x...

    fmt.Printf("width1: |%6d|%6d|\n", 12, 345)  // |    12|   345|
    fmt.Printf("width2: |%6.2f|%6.2f|\n", 1.2, 3.45)  // |  1.20|  3.45|
    fmt.Printf("width3: |%-6.2f|%-6.2f|\n", 1.2, 3.45)

    fmt.Printf("left: |%-6s|%-6s|\n", "foo", "b")
    fmt.Printf("right: |%6s|%6s|\n", "foo", "b")

    s := fmt.Sprintf("sprintf: a %s", "string")
    fmt.Println(s)

    fmt.Fprintf(os.Stderr, "io: an %s\n", "error")
}
$ go run string-formatting.go
struct1: {1 2}
struct2: {x:1 y:2}
struct3: main.point{x:1, y:2}
type: main.point
bool: true
int: 123
bin: 1110
char: !
hex: 1c8
float1: 78.900000
float2: 1.234000e+08
float3: 1.234000E+08
str1: "string"
str2: "\"string\""
str3: 6865782074686973
pointer: 0xc0000ba000
width1: |    12|   345|
width2: |  1.20|  3.45|
width3: |1.20  |3.45  |
left: |foo   |b     |
right: |   foo|     b|
sprintf: a string
io: an error

2.53 Text Templates(文本模板)

Go 的 text/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"})

    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#"})
}
$ go run text-templates.go
Value: some text
Value: 5
Value: [Go Rust C++ C#]
Name: Jane Doe
yes
no
Range: Go Rust C++ C#

2.54 Regular Expressions(正则表达式)

Go 的 regexp 包提供正则表达式支持。

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(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(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(r.ReplaceAllString("a peach", "<fruit>"))

    in := []byte("a peach")
    out := r.ReplaceAllFunc(in, bytes.ToUpper)
    fmt.Println(string(out))
}
$ go run regular-expressions.go
true
true
peach
[0 5]
[peach ch]
[0 5 2 4]
[peach punch pinch]
[[0 5 2 4] [6 11 8 10] [12 17 14 16]]
[peach punch]
true
a <fruit>
a PEACH

2.55 JSON(JSON 处理)

Go 内置支持 JSON 编码和解码。

package main

import (
    "encoding/json"
    "fmt"
    "os"
    "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))

    // slice 和 map 编码
    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))

    // 使用 tag 自定义 JSON 键名
    res2D := &response2{
        Page:   1,
        Fruits: []string{"apple", "peach", "pear"}}
    res2B, _ := json.Marshal(res2D)
    fmt.Println(string(res2B))

    // 解码到 map
    byt := []byte(`{"num":6.13,"strs":["a","b"]}`)
    var dat map[string]interface{}
    if err := json.Unmarshal(byt, &dat); err != nil {
        panic(err)
    }
    fmt.Println(dat)

    num := dat["num"].(float64)
    fmt.Println(num)

    strs := dat["strs"].([]interface{})
    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])

    // 流式编码
    enc := json.NewEncoder(os.Stdout)
    d := map[string]int{"apple": 5, "lettuce": 7}
    enc.Encode(d)

    // 流式解码
    dec := json.NewDecoder(strings.NewReader(str))
    res1 := response2{}
    dec.Decode(&res1)
    fmt.Println(res1)
}
$ go run json.go
true
1
2.34
"gopher"
["apple","peach","pear"]
{"apple":5,"lettuce":7}
{"Page":1,"Fruits":["apple","peach","pear"]}
{"page":1,"fruits":["apple","peach","pear"]}
map[num:6.13 strs:[a b]]
6.13
a
{1 [apple peach]}
apple
{"apple":5,"lettuce":7}
{1 [apple peach]}

2.56 XML(XML 处理)

encoding/xml 包提供 XML 支持。

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
    xml.Unmarshal(out, &p)
    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))
}
$ go run xml.go
 <plant id="27">
   <name>Coffee</name>
   <origin>Ethiopia</origin>
   <origin>Brazil</origin>
 </plant>
<?xml version="1.0" encoding="UTF-8"?>
 <plant id="27">
   <name>Coffee</name>
   <origin>Ethiopia</origin>
   <origin>Brazil</origin>
 </plant>
Plant id=27, name=Coffee, origin=[Ethiopia Brazil]
 <nesting>
   <parent>
     <child>
       <plant id="27">...</plant>
       <plant id="81">...</plant>
     </child>
   </parent>
 </nesting>

2.57 Time(时间)

Go 提供丰富的时间处理支持。

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))
}
$ go run time.go
2024-10-15 15:04:05.123456 +0000 UTC
2009-11-17 20:34:58.651387237 +0000 UTC
2009
November
17
20
34
58
651387237
UTC
Tuesday
true
false
false
...

2.58 Epoch(时间戳)

获取 Unix 纪元以来的秒数、毫秒数或纳秒数。

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()))
}
$ go run epoch.go
2024-10-15 15:04:05.292387 +0000 UTC
1351700038
1351700038292
1351700038292387000
2024-10-15 15:04:05 +0000 UTC
2024-10-15 15:04:05.292387 +0000 UTC

2.59 Time Formatting/Parsing(时间格式化与解析)

Go 使用基于模式的布局进行时间格式化与解析。

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)
}
$ go run time-formatting-parsing.go
2014-04-15T18:00:15-07:00
2012-11-01 22:08:41 +0000 +0000
6:00PM
Tue Apr 15 18:00:15 2014
2014-04-15T18:00:15.161182-07:00
0000-01-01 20:41:00 +0000 UTC
2014-04-15T18:00:15-00:00
parsing time "8:41PM" as ...

2.60 Random Numbers(随机数)

math/rand/v2 包提供伪随机数生成。

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()
}
$ go run random-numbers.go
68,56
0.8090228139659177
5.840125017402497,6.937056298890035
94,49

2.61 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)
}
$ go run number-parsing.go
1.234
123
456
789
135
strconv.ParseInt: parsing "wat": invalid syntax

2.62 URL Parsing(URL 解析)

解析和提取 URL 的各个组成部分。

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])
}
$ go run url-parsing.go
postgres
user:pass
user
pass
host.com:5432
host.com
5432
/path
f
k=v
map[k:[v]]
v

2.63 SHA256 Hashes(SHA256 哈希)

crypto/sha256 包计算 SHA256 哈希值。

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)
}
$ go run sha256-hashes.go
sha256 this string
1af1dfa857bf1d8814fe1af8983c18080019922e557f15a8a...

2.64 Base64 Encoding(Base64 编码)

encoding/base64 包提供 Base64 编码/解码。

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))
}
$ go run base64-encoding.go
YWJjMTIzIT8kKiYoKSctPUB+
abc123!?$*&()'-=@~
YWJjMTIzIT8kKiYoKSctPUB-
abc123!?$*&()'-=@~

2.65 Reading Files(读取文件)

Go 读取文件的多种方式。

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")
    defer os.Remove(path)

    // 读取整个文件到内存
    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: %s\n", n2, o2, string(b2[:n2]))

    // 使用 bufio 缓冲读取
    f.Seek(0, io.SeekStart)
    r4 := bufio.NewReader(f)
    b4, err := r4.Peek(5)
    check(err)
    fmt.Printf("5 bytes: %s\n", string(b4))

    f.Close()
}

准备文件并运行:

$ echo "hello" > /tmp/dat
$ echo "go" >>   /tmp/dat
$ go run reading-files.go
hello
go
5 bytes: hello
2 bytes @ 6: go
5 bytes: hello

2.66 Writing Files(写入文件)

Go 写入文件的多种方式。

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()
}
$ go run writing-files.go
wrote 5 bytes
wrote 7 bytes
wrote 9 bytes
$ cat /tmp/dat1
hello
go
$ cat /tmp/dat2
some
writes
buffered

2.67 Line Filters(行过滤器)

行过滤器是一种常见的程序类型,读取标准输入、处理并输出结果。

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)
    }
}
$ echo 'hello'   > /tmp/lines
$ echo 'filter' >> /tmp/lines
$ cat /tmp/lines | go run line-filters.go
HELLO
FILTER

2.68 File Paths(文件路径)

path/filepath 包提供可移植的文件路径操作。

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)
}
$ go run file-paths.go
p: dir1/dir2/filename
dir1/filename
dir1/filename
Dir(p): dir1/dir2
Base(p): filename
false
true
.json
config
t/file

2.69 Directories(目录操作)

Go 提供丰富的目录操作函数。

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())
    }

    // 使用 filepath.WalkDir 递归遍历
    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
}
$ go run directories.go
Listing subdir/parent
  child true
  file2 false
  file3 false
Visiting subdir
  subdir true
  subdir/file1 false
  subdir/parent true
  subdir/parent/child true
  subdir/parent/child/file4 false
  subdir/parent/file2 false
  subdir/parent/file3 false

2.70 Temporary Files and Directories(临时文件与目录)

os.CreateTempos.MkdirTemp 创建临时文件和目录。

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)
}
$ go run temporary-files-and-directories.go
Temp file name: /tmp/sample610887201
Temp dir name: /tmp/sampledir898854668

2.71 Embed Directive(嵌入指令)

//go:embed 编译器指令在构建时将文件嵌入到 Go 二进制文件中。

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))
}

准备文件并运行:

$ mkdir -p folder
$ echo "hello go" > folder/single_file.txt
$ echo "123" > folder/file1.hash
$ echo "456" > folder/file2.hash
$ go run embed-directive.go
hello go
hello go
123
456

2.72 Testing and Benchmarking(测试与基准测试)

使用 testing 包编写单元测试和基准测试。

package main

import (
    "fmt"
    "testing"
)

// IntMin 返回两个整数中较小的一个
func IntMin(a, b int) int {
    if a < b {
        return a
    }
    return b
}

// TestIntMinBasic 基本测试
func TestIntMinBasic(t *testing.T) {
    ans := IntMin(2, -2)
    if ans != -2 {
        t.Errorf("IntMin(2, -2) = %d; want -2", ans)
    }
}

// TestIntMinTableDriven 表驱动测试
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)
            }
        })
    }
}

// BenchmarkIntMin 基准测试
func BenchmarkIntMin(b *testing.B) {
    for b.Loop() {
        IntMin(1, 2)
    }
}
# 运行测试
$ go test -v
== RUN   TestIntMinBasic
--- PASS: TestIntMinBasic (0.00s)
=== RUN   TestIntMinTableDriven
=== RUN   TestIntMinTableDriven/0,1
=== RUN   TestIntMinTableDriven/1,0
=== RUN   TestIntMinTableDriven/2,-2
=== RUN   TestIntMinTableDriven/0,-1
=== RUN   TestIntMinTableDriven/-1,0
--- PASS: TestIntMinTableDriven (0.00s)
PASS

# 运行基准测试
$ go test -bench=.
BenchmarkIntMin-8 1000000000 0.3136 ns/op
PASS

2.73 Command-Line Arguments(命令行参数)

os.Args 提供程序启动时传递的原始命令行参数。

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)
}
$ go run command-line-arguments.go a b c d
[./command-line-arguments a b c d]
[a b c d]
c

2.74 Command-Line Flags(命令行标志)

flag 包解析命令行标志。

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())
}
$ go run command-line-flags.go -word=opt -numb=7 -fork=true -svar=flag
word: opt
numb: 7
fork: true
svar: flag
tail: []

$ go run command-line-flags.go -word=opt
word: opt
numb: 42
fork: false
svar: bar
tail: []

$ go run command-line-flags.go -h
Usage of ./command-line-flags:
  -fork
        a bool
  -numb int
        an int (default 42)
  -svar string
        a string var (default "bar")
  -word string
        a string (default "foo")

2.75 Command-Line Subcommands(命令行子命令)

使用 flag 包实现子命令模式(如 git commitgit push)。

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)
    }
}
$ go run command-line-subcommands.go foo -enable -name=joe a1 a2
subcommand 'foo'
  enable: true
  name: joe
  tail: [a1 a2]

$ go run command-line-subcommands.go bar -level 8
subcommand 'bar'
  level: 8
  tail: []

$ go run command-line-subcommands.go bar -enable
flag provided but not defined: -enable

2.76 Environment Variables(环境变量)

读取和设置环境变量使用 os.Getenvos.Setenvos.Environ

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])
    }
}
$ go run environment-variables.go
FOO: 1
BAR:

# 输出所有环境变量名(略)

2.77 Logging(日志)

Go 内置的 log 包提供日志功能。

package main

import (
    "bytes"
    "fmt"
    "log"
    "os"
)

func main() {
    log.Println("standard logger")

    log.SetFlags(log.LstdFlags | log.Lmicroseconds)
    log.Println("with micro seconds")

    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())
}
$ go run logging.go
2024/10/15 15:04:05 standard logger
2024/10/15 15:04:05.123456 with micro seconds
2024/10/15 15:04:05 logging.go:15: with file/line
2024/10/15 15:04:05 from mylog
2024/10/15 15:04:05 from mylog
from buflog:buf:2024/10/15 15:04:05 hello

2.78 HTTP Client(HTTP 客户端)

Go 的 net/http 包提供 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)
    }
}
$ go run http-client.go
Response status: 200 OK
<!DOCTYPE html>
<html>
  <head>
    <title>Go by Example</title>
...

2.79 HTTP Server(HTTP 服务端)

net/http 包也提供 HTTP 服务端功能。

package main

import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello, you've requested: %s\n", r.URL.Path)
    })

    http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello!\n")
    })

    http.ListenAndServe(":8090", nil)
}
# 在另一个终端运行
$ go run http-server.go &

$ curl localhost:8090/hello
Hello!

2.80 TCP Server(TCP 服务端)

使用 net 包编写 TCP 服务器。

package main

import (
    "fmt"
    "net"
    "strconv"
    "time"
)

func main() {
    listener, err := net.Listen("tcp", ":8080")
    if err != nil {
        panic(err)
    }
    defer listener.Close()

    for {
        conn, err := listener.Accept()
        if err != nil {
            panic(err)
        }

        go handleConnection(conn)
    }
}

func handleConnection(conn net.Conn) {
    defer conn.Close()

    conn.SetDeadline(time.Now().Add(10 * time.Second))

    buf := make([]byte, 1024)
    for {
        n, err := conn.Read(buf)
        if err != nil {
            fmt.Println("Connection closed")
            return
        }

        msg := string(buf[:n])
        fmt.Printf("Received: %s\n", msg)

        response := strconv.Itoa(len(msg)) + " bytes received\n"
        conn.Write([]byte(response))
    }
}
# 运行服务器
$ go run tcp-server.go &

# 使用 netcat 连接
$ nc localhost 8080
hello
5 bytes received

2.81 Context(上下文)

context 包用于控制超时、取消和传递请求范围的值。

package main

import (
    "fmt"
    "net/http"
    "time"
)

func main() {
    ctx := context.Background()
    ctx, cancel := context.WithTimeout(ctx, 1500*time.Millisecond)
    defer cancel()

    req, err := http.NewRequestWithContext(ctx, "GET", "https://httpbin.org/delay/2", nil)
    if err != nil {
        panic(err)
    }

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        fmt.Println("Request failed:", err)
        return
    }
    defer resp.Body.Close()
    fmt.Println("Response status:", resp.Status)
}
$ go run context.go
Request failed: Get "https://httpbin.org/delay/2": context deadline exceeded

2.82 Spawning Processes(启动进程)

Go 可以启动外部进程。

package main

import (
    "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))

    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))
}
$ go run spawning-processes.go
> date
Mon Oct 15 15:04:05 PDT 2024

> grep hello
hello grep

2.83 Exec’ing Processes(执行进程)

syscall.Exec 用外部进程替换当前 Go 进程。

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)
    }
}
$ go run execing-processes.go
total 44
-rw-r--r--  1 user  staff   1.1K Oct 15 15:04 ...
...

2.84 Signals(信号处理)

使用 os/signal 处理 Unix 信号。

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
)

func main() {
    sigs := make(chan os.Signal, 1)
    signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

    done := make(chan bool, 1)

    go func() {
        sig := <-sigs
        fmt.Println()
        fmt.Println(sig)
        done <- true
    }()

    fmt.Println("awaiting signal")
    <-done
    fmt.Println("exiting")
}
$ go run signals.go
awaiting signal
^C
interrupt
exiting

2.85 Exit(进程退出)

os.Exit 立即退出程序并返回状态码。

package main

import (
    "fmt"
    "os"
)

func main() {
    defer fmt.Println("!")  // 不会执行

    os.Exit(3)
}

// 注意:defer 中的 fmt.Println 不会执行
$ go run exit.go
exit status 3

# 检查退出码
$ go build exit.go
$ ./exit; echo $?
3

本文档由 Go 官方学习资源Go by Example 整合而成,涵盖 Go 语言的核心概念和常用标准库用法。

作者:Mark McGranaghan & Eli Bendersky(Go by Example 部分) | 许可证:CC BY 4.0