Go 学习完整指南
Go 学习完整指南 #
本文档整合了 Go 官方学习资源 (go.dev/learn/) 和 Go by Example (gobyexample.com) 的全部内容,提供完整、可用、可重复学习的 Go 语言参考资料。
第一部分:Go 官方学习资源 #
1.1 Go 入门教程 (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) #
本教程通过构建两个模块(一个可导入的库模块和一个调用者应用)介绍 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) #
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 - 保留前缀:
test、example
最佳实践 #
- 始终将
go.mod和go.sum提交到版本控制 - 依赖变更后运行
go mod tidy - 生产环境使用显式版本而非分支名
- 定期更新依赖获取安全修复:
go get -u
1.4 开发 Web 服务 (RESTful API with 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 #
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 要点 #
Effective Go 是编写清晰、高效、惯用 Go 代码的官方指南。以下为最核心的惯例摘要:
格式化 #
- 缩进使用制表符(tab),始终运行
gofmt - 大括号与控制语句在同一行
- imports 按字母排序,标准库与第三方库之间用空行分隔
命名规范 #
| 元素 | 规范 |
|---|---|
| 包名 | 简短、小写、单单词、无下划线 |
| 导出名 | MixedCaps(首字母大写) |
| 非导出名 | mixedCaps(首字母小写) |
| Getter | 不加 Get 前缀,字段 owner 的 getter 为 Owner() |
| 单方法接口 | 方法名 + -er:Reader、Writer、Stringer |
控制结构 #
// 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.Is和errors.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 文档总览 #
核心文档资源 #
| 资源 | 链接 | 说明 |
|---|---|---|
| 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 — 模糊测试
模块参考 #
- Managing dependencies — 依赖管理
- Developing and publishing modules — 模块开发与发布
- Module release and versioning workflow — 版本发布工作流
1.8 Go 学习路径推荐 #
新手入门路径 #
- Tour of Go — 交互式学习 Go 基础(https://go.dev/tour/)
- Getting Started 教程 — 安装并编写第一个程序
- Create a Module 教程 — 理解模块和包
- Go by Example — 通过示例学习具体语法
- 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 的 if 和 else 条件分支。
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(非阻塞通道操作) #
使用 select 的 default 分支实现非阻塞的通道操作。
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.CreateTemp 和 os.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 commit、git 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.Getenv、os.Setenv 和 os.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