文章介绍:笔者对Go语言知识进行体系化总结,有一定全面性与深度。
从 go.dev 上下载 Go 安装包,解压至 /usr/local 目录下。增加/usr/local/go/bin路径到PATH环境变量。
Go 的环境变量:
名称 | 描述 |
---|---|
GOROOT | 安装目录,包含编译器、命令行工具、标准库等。 |
GOPATH | 工作目录,包含自动下载的第三方依赖源码和可执行文件。 |
GO111MODULE | 模块管理模式,默认值是 auto 表示根据当前目录来判断。 |
GOPROXY | 模块代理地址,用于下载公开的第三方依赖,多个用逗号隔开,遇到 direct 时表示直接访问源地址。 |
GOSUMDB | 使用 GOPROXY 时,校验和数据库的地址。 |
GONOPROXY | 不通过代理,直接下载的第三方依赖,如公司私有仓库。 |
GONOSUMDB | 不通过代理,直接下载的第三方依赖校验和数据库。 |
GOPRIVATE | 指示哪些仓库下的模块是私有的,等于同时设置 GONOPROXY 和 GONOSUMDB。 |
安装过程:
#用 root 身份
yum install -y wget #安装 wget
#下载 go,版本号可以更新
wget https://go.dev/dl/go1.20.6.linux-amd64.tar.gz
#解压至/usr/local
tar -C /usr/local -zxvf go1.20.6.linux-amd64.tar.gz
#删除安装包
rm -f go1.20.6.linux-amd64.tar.gz
vi /etc/profile #用 vi 编辑 profile 文件
#按 i 在最后添加 export PATH=$PATH:/usr/local/go/bin
#按 ESC,输入符号:,再输入 wq 回车保存退出
source /etc/profile #加载配置
echo $PATH #确认已添加 PATH
su dev #切换开发者用户,dev 为远程开发用户
#设置 goproxy 为国内镜像
go env -w GOPROXY=https://goproxy.cn,direct
Go 命令行工具位于 /usr/local/go/bin/go。
常用命令:
命令 | 描述 |
---|---|
go version | 查看 Go 版本。 |
go env | 查看 Go 环境变量。 |
go get | 下载依赖源码包,下载后位于 GOPATH/src 下。 |
go install | 编译并安装依赖包,安装后位于 GOPATH/bin 下。 |
go build | 编译源码,生成可执行文件。 |
go run | 运行源码文件,一般用来运行单个文件。 |
go fmt | 格式化源码。 |
go vet | 检查代码错误。 |
go test | 运行单元测试。 |
go generate | 运行代码生成工具。 |
go mod | 模块管理工具,用于下载、更新、删除依赖包。 |
常用 vs code,安装 Go 相关插件,可以使用 remote ssh 插件进行远程开发。
Go 语言的代码风格类 C 语言,但更简洁。
主要区别点:
主要相似点:
值类型与引用类型: 只有 slice、map、channel 、interface是引用类型,其他都是值类型。值类型如果要避免拷贝需要用指针。
Go 是静态强类型语言,变量类型在编译期确定,且会进行类型检查。 变量声明有多种方式,首选是忽略类型的极简方式。
//方式一:显示指定类型不赋值,会初始化为默认值
var a int
a = 10
//方式二:显示指定类型并赋值
var b int = 10
//方式三:直接赋值省略类型,编译器会自动推断类型
var b = 10
//方式四:直接赋值省略类型,编译器会自动推断类型,使用符号 := 替代 var 关键字
//注意:已声明过的变量再使用符号 := 会编译出错,只能声明局部变量
c := 10
多变量声明与赋值:
//显示指定类型并赋值
var a, b int = 1, 2
//省略类型,可同时声明多种类型
x, s := 123, "str"
//批量声明
var(
data1 int
data2 string
)
//直接交换a和b的值
a, b = b, a
//假设 myfunc 返回两个值,第一个值不需要,用下划线忽略
_, ok := myfunc()
如果变量只声明未初始化,那么其值为默认值。各类型默认值如下:
按优先级从高到低,作用域分为:
在同一个作用域下变量只能声明一次,但在不同作用域下变量可以同名,按所在作用域的优先级决定使用哪个。
var a float64 = 511.678
var b int32 = int32(a) //浮点数转整型丢失小数
//var c int = b //int 与 int32 属于不同类型,编译报错
var c int = int(b) //强制转换
var d uint8 = uint8(b) //大整型转小整型超出范围不会报错,但会截断
fmt.Println(a, b, c, d) //511.678 511 511 255
arrByte := []byte("abc")
fmt.Println(arrByte) //[97 98 99]
arrByte[0] = 'A'
str := string(arrByte)
fmt.Println(str) //Abc
s1 := strconv.Itoa(c) //int 转 string
x, err := strconv.Atoi("123") //string 转 int,若成功err为nil
fmt.Println(s1, x, err) //511 123 <nil>
s2 := strconv.FormatFloat(a, 'f', 2, 64) //float 转 string,2位小数四舍五入
f, err := strconv.ParseFloat(s2, 64) //string 转 float
fmt.Println(s2, f, err) //511.68 511.68 <nil>
//方式一:显示指定类型
const MAX, MIN int = 100, 1
//方式二:省略类型
const WIDTH, TEXT = 100, "abc"
//方式三:批量声明
const(
DATA = "abc"
DATA2 //使用前面常量的表达式,值也为"abc"
LEN = len(TEXT) //允许对前面的常量进行运算
)
const(
Known = 0
Male = 1
Female =2
)
const (
a = iota //0
b //1
c //2
d = "ha" //独立值,iota += 1
e //"ha" iota += 1
f = 100 //iota +=1
g //100 iota +=1
h = iota //7,恢复计数
i //8
)
数字常量支持多进制表达:
进制 | 表达方式 | 示例 |
---|---|---|
十进制 | 常规写法 | 123 |
八进制 | 0开头 | 0123 |
十六进制 | 0x开头 | 0x123 |
二进制 | 0b开头 | 0b10101 |
类型 | 运算符 |
---|---|
算术 | +加 -减 *乘 /除 %取余 ++自增 --自减 |
关系 | ==等于 !=不等于 >大于 <小于 >=大于等于 <=小于等于 |
逻辑 | &&与 ||或 !非 |
位 | &位与 |位或 ^位异或 <<左移 >>右移 |
赋值 | =赋值 +=相加赋值 -=相减赋值 *=等 |
字符串 | +拼接 +=拼接赋值 ==等于 !=不等于 |
其他 | &指针取地址 *指针取值 |
注意:
if a > 2 {
//TODO
} else if a > 3 {
//TODO
} else {
//TODO
}
//在表达式前使用赋值语句,ok 仅在 if 块内有效
if ok := isOk(); ok {
//TODO
}
switch num:=myfunc(); num {
case 1: //TODO
case 2: //TODO
case 3, 4, 5 : //TODO
default: //TODO
}
switch {
case score == 100, score == 99: //TODO
case score >= 90: //TODO
case score >= 60: //TODO
default: //TODO
}
switch x.(type) { //假设 x 是 interface{} 类型
case nil:
fmt.Printf("x 是 nil")
case int:
fmt.Printf("x 是 int")
case float64:
fmt.Printf("x 是 float64")
}
select {
case v := <-ch1: //如果ch1有数据 则执行
fmt.Print(v, " ")
case v := <-ch2: //如果ch2有数据 则执行
fmt.Print(v, " ")
default://如果 ch1 和 ch2 都没数据则执行
}
Go 语言的循环控制都是用 for 关键字,有以下多种形式:
另外,循环中可以使用 break/continue/goto+label语句退出循环,用法类似其他语言。
for i := 0; i < 10; i++ {
//TODO
}
for i < 100 {
i++
}
for {
//TODO
}
for k, v := range map {
//TODO
}
Go 语言的函数定义语法如下:
func functionName(param1 type1, param2 type2, ...) (result1 type1, result2 type2, ...) {
//TODO
return result1, result2, ...
}
函数名特点:
参数特点:
func func1(){} //无参数
func func2(s string, x, y int){} //多个参数,x与y都为int类型,简写
func func3(args ...int){} //可变参数,调用时用 func3(1,2,3) 或 func3(slice...)
func func4(a *int){} //用指针实现引用传递
func func5(a func(int)){} //函数本身作为参数
返回值特点:
func func1() int { return 0 } //单返回值
func func2() (int, string) { //多返回值
return 0, "" //必须带返回值
}
func func3() (x, y int, s string) {//多命名返回值,简写
x, y, s = 0, 1, ""
return //等同于 return x, y, s
}
func buildFunc() func(string) { //函数本身作为返回值
return func(s string) {
fmt.Println(s)
}
}
//调用方法如:buildFunc()("hello")
init()
main()
匿名函数是没有函数名的函数,应用场景:
func main(){
//函数变量
myfunc := func (s string){
fmt.Println(s)
}
myfunc("hello")
//并发执行
go func (s string){
fmt.Println(s)
}("hello")
}
func outer() func() int {
counter := 0
return func() int {
counter++
return counter
}
}
func main() {
//闭包
closure := outer()
fmt.Println(closure()) //1
fmt.Println(closure()) //2
}
//该例子中,outer()函数执行完后,counter局部变量作为闭包的一部分保留下来,仍然可以被读写。
Go 支持递归,递归函数是指函数在内部直接或间接调用自身。 递归函数的特性:
defer 语句用于延迟调用指定的函数。 defer 的特点:
defer 的使用场景:
//文件释放
func openFile() {
file, err := os.Open("txt")
if err != nil {
return
}
defer file.Close() //合理位置
}
//锁释放
func lockScene() {
var mutex sync.Mutex
mutex.Lock()
defer mutex.Unlock()
//业务代码...
}
//捕获 panic
func demo() {
defer func() {
if err := recover(); err !=nil{
fmt.Println(string(Stack()))
}
}()
panic("unknown")
}
struct 是自定义结构体,用于聚合多种类型的数据。
定义:
使用:
type Point struct{ X, Y int } //X 与 Y 简写在一行
type Staff struct {
Id int `json:"Identity"` //加 tag 控制 json 序列化字段名
Name string
Address struct { //嵌套匿名结构体
Street string
City string
}
}
func main() {
p1 := Point{1, 2} //按字段顺序直接赋值
p2 := Point{X: 3, Y: 4} //按字段名赋值
fmt.Println(p1, p2) //{1 2} {3 4}
s := &Staff{ //获取指针,经逃逸分析会分配到堆
Name: "wills",
Address: struct {
Street string
City string
}{
Street: "123 St.",
City: "SHENZHEN",
},
}
s.Id = 1 //通过指针访问字段方式一样
data, _ := json.Marshal(s)
fmt.Println(string(data))
//{"Identity":1,"Name":"wills","Address":{"Street":"123 St.","City":"SHENZHEN"}}
}
Go 支持为 struct 定义方法,再通过 x.方法名() 的方式调用。 方法定义方式如下:
func (x T) 方法名(参数) (返回值) { //对类型 T 定义方法
}
func (x *T) 方法名(参数) (返回值) {//对类型 T 的指针定义方法
}
注意:
type Point struct{ X, Y int }
func (p *Point) Add1() { p.X++; p.Y++ }
func (p Point) Add2() { p.X++; p.Y++ } //因为值拷贝修改无效
func main() {
p := Point{10, 20} //按字段顺序直接赋值
p.Add1() //p 的数据发生变更
fmt.Println(p) //{11 21}
p.Add2() //p 的数据不会发生变更
fmt.Println(p) //{11 21}
}
struct嵌入其他命名struct可以实现组合模式,嵌入其他匿名struct可以实现类似继承模式。 如果A嵌入了匿名的B和C,则可以通过A直接访问B和C的字段或方法,Go 会由浅至深地查找,找到则停止查找。
type B struct{ x, y int }
type C struct{ m, n int }
func (b *B) Add() { b.x++; b.y++ }
type A struct {//A嵌入匿名的 B 和 C
B
C
z int
}
func main() {
a := A{B{10, 11}, C{20, 21}, 30}
a.Add() //通过 A 直接访问 B 的方法
a.m = 25 //通过 A 直接访问 C 的字段
fmt.Println(a) //{{11 12} {25 21} 30}
}
指针是用来保存变量内存地址的变量。有以下特点:
Go指针的应用场景:
逃逸分析:
//以下例子目的在展示指针用法,不代表该场景下需用指针。
//小对象建议使用值传递和值返回,避免在堆上分配内存,因为堆分配开销较大,还需要通过 GC 回收内存。
type Point struct{ x, y int }
// 使用指针作为参数,实现引用传递。
// 使用指针实现方法,实现对成员变量的修改。
func (its *Point) Add(p *Point) {
its.x, its.y = its.x+p.x, its.y+p.y
}
// 使用指针作为返回值,会引发逃逸分析,返回值在堆上分配。
func buildPoint(x, y int) *Point {
return &Point{x, y}
}
func main() {
p := buildPoint(1, 2)
p.Add(&Point{3, 4})
fmt.Println(p) //&{4 6}
}
数组是定长且有序的相同类型元素的集合。有以下特点:
//方式一,先声明再赋值
var a [3]int
a = [3]int{1,2,3}
//方式二,用 var 声明且赋值
var b = [3]int{1, 2, 3}
//方式三,用 := 符号声明且赋值
c := [3]int{1, 2, 3}
//方式四,借助编译器推断长度
d := [...]int{1, 2, 3}
s := [...]string{0: "a", 3: "b"} //通过索引赋值,1 和 2 是默认值""
for index, val := range s { //index是索引,val 是元素值
if val == "" {
s[index] = "-" //注意 val 值复制修改无效,要通过索引修改数组
}
}
fmt.Println(len(s), s) //4 [a - - b]
//多维数组
arr := [2][3]int{{11, 12, 13}, {21, 22, 23}}
fmt.Println(arr) //[[11 12 13] [21 22 23]]
fmt.Println(len(arr)) //2
切片是动态数组,是变长且有序的相同类型元素的集合。
切片的使用:
s1 := []int{0, 1, 2} //声明且初始化
s1 = append(s1, 3) //追加元素,append返回值必须赋值回切片
s2 := s1 //仅复制引用
s2[0] = 9 //s2和s1是同个切片的引用,修改s2也会修改到s1
fmt.Println("s1:", len(s1), cap(s1), s1) //s1: 4 6 [9 1 2 3]
var s3 []int //仅声明不初始化
fmt.Println("s3:", len(s3), cap(s3), s3 == nil) //s3: 0 0 true
s3 = []int{} //初始化空切片,空切片不等于 nil
fmt.Println("s3:", len(s3), cap(s3), s3 == nil) //s3: 0 0 false
s3 = make([]int, len(s1), 100) //初始化容量为 100
copy(s3, s1) //复制数据
s4 := append(s3, 4) //追加到新的切片,没有扩容 s4 和 s3 底层数组为同一个,但长度不同表示的数据仍然不同
s4[0] = 99 //会同时修改到s3和s4的第一个元素
fmt.Println("s3:", len(s3), cap(s3), s3) //s3: 4 100 [99 1 2 3]
fmt.Println("s4:", len(s4), cap(s4), s4) //s4: 5 100 [99 1 2 3 4]
s5 := s4[1:3] //截取索引1到2,不包括 3
fmt.Println("s5:", len(s5), cap(s5), s5) //s5: 2 99 [1 2]
s6 := s4[:3] //截取索引0到2,不包括 3
fmt.Println("s6:", len(s6), cap(s6), s6) //s6: 3 100 [99 1 2]
s7 := s4[3:] //截取索引3到尾部
fmt.Println("s7:", len(s7), cap(s7), s7) //s7: 2 97 [3 4]
s7[0] = 1000 //截取的子切片数据仍然在母切片上,故修改元素会修改到母切片
fmt.Println("s4:", len(s4), cap(s4), s4) //s4: 5 100 [99 1 2 1000 4]
map 是一种无序的键值对的集合,键是唯一的。
map 的使用:
var m1 map[string]int //只声明不初始化无法使用
// m1["a"] = 1 //panic
fmt.Println("m1,", len(m1), m1 == nil) //m1, 0 true
m1 = map[string]int{} //初始化为空则可以使用
fmt.Println("m1,", len(m1), m1 == nil) //m1, 0 false
m2 := make(map[string]int, 100) //用make初始化,设置初始大小 100
fmt.Println("m2,", len(m2), m2) //m2, 0 map[]
m3 := map[string]int{"a": 1, "b": 2} //直接赋值初始化
fmt.Println("m3,", len(m3), m3) //m3, 2 map[a:1 b:2]
m4 := m3 //仅复制引用
m4["c"] = 3 //m4和m3是同个map的引用,修改m4也会修改到m3
fmt.Println("m3,", len(m3), m3) //m3, 3 map[a:1 b:2 c:3]
for k := range m4 { //遍历key
if k == "b" {
delete(m4, k) //遍历中可以删除 key
}
}
_, ok := m4["b"] //ok表示 key 是否存在
fmt.Println("m4, b ok", ok) //m4, b ok false
mm := map[string]map[string]int{ //嵌套 map
"一": {"a": 10, "b": 11},
"二": {"m": 20, "n": 21},
}
fmt.Println("mm,", len(mm), mm) //mm, 2 map[一:map[a:10 b:11] 二:map[m:20 n:21]]
interfae 用于定义一组方法,只要结构体实现了这些方法,就实现了该接口。接口的作用在于解耦和实现多态。 接口可以嵌套多个其他接口,等于拥有了这些接口的特征。 空接口 interface{}(内建别名 any) 可以赋值为任意类型变量,结合类型判断或反射可以实现处理任意类型数据。 接口类型转换可以用类型断言,语法如 x,ok := value.(Type)。 接口类型的变量可以用符号==进行比较,只有都为 nil 或类型相同且值相等时才为 true。
type Phone interface {
Call(num string)
}
type Camera interface {
TakePhoto()
}
type SmartPhone interface { //嵌套了 Phone 和 Camera
Phone
Camera
}
type IPhone struct{}
func (iphone IPhone) Call(num string) {
fmt.Println("iphone call", num)
}
func (iphone IPhone) TakePhoto() {
fmt.Println("iphone take photo")
}
type Android struct{}
func (android Android) Call(num string) {
fmt.Println("android call", num)
}
func (android Android) TakePhoto() {
fmt.Println("android take photo")
}
type SmartPhoneFactory struct{}
func (factory SmartPhoneFactory) CreatePhone(phoneType string) SmartPhone {
switch phoneType {
case "iphone":
return IPhone{}
case "android":
return Android{}
default:
return nil
}
}
func main() {
sp := SmartPhoneFactory{}.CreatePhone("iphone")
sp.Call("123456") //iphone call 123456
sp.TakePhoto() //iphone take photo
var x interface{} = sp
switch x.(type) {
case IPhone:
fmt.Println("x is iphone")
case Android:
fmt.Println("x is android")
} //x is iphone
iphone, ok := x.(IPhone) //类型断言
fmt.Println(iphone, ok) //{} true
var value interface{} = 123
i, ok := value.(int)
fmt.Println(i, ok) //123 true
i32, ok := value.(int32) //常量 123 是 int 类型,类型断言失败
fmt.Println(i32, ok) //0 false
}
goroutine 是一种语言级的协程,是 go 并发调度的单元。特点如下:
func doJob(name string) string {
fmt.Println("doJob:", name)
return name + "_result"
}
func main() {
go doJob("job1") //用 go 调用函数启动协程
x := "nothing"
go func() {
x = doJob("job2") //通过闭包修改外部环境变量 x
}()
fmt.Println("x:", x) //x: nothing,协程创建了但未运行
time.Sleep(3 * time.Second) //简单等待协程运行,更科学的做法是用线程同步机制
fmt.Println("x:", x) //x: job2_result,协程已运行
}
channel 是用于协程间传递指定类型数据的通道,是一种队列,可以实现协程并发同步。使用要点:
func doJob(name string, ch chan string) { //通道作为参数引用传递
fmt.Println("doJob:", name)
ch <- name + "_result" //将处理结果发送到通道
close(ch) //关闭通道
}
func main() {
var ch chan string //仅声明不初始化
// fmt.Println(<-ch) //不初始化也可以接收,但会阻塞等待,这里会导致死锁异常。
fmt.Println(len(ch), cap(ch), ch == nil) //0 0 true
ch = make(chan string) //初始化,没有缓冲区
fmt.Println(len(ch), cap(ch), ch == nil) //0 0 false
go doJob("job1", ch) //启动协程
fmt.Println(len(ch), cap(ch)) //0 0,对于没有缓冲区的通道,长度任何时候都是 0
data, ok := <-ch //接收到数据则 ok 为 true
fmt.Println(data, ok) //job1_result true
data, ok = <-ch //再接收会阻塞,直到通道关闭
fmt.Println(data == "", ok) //true false
ch2 := make(chan string, 2) //声明且初始化,缓冲区容量
go func() {
for _, a := range "abcd" {
ch2 <- string(a)
fmt.Printf("w(%c,%d) ", a, len(ch2)) //写入完成后输出
}
time.Sleep(3 * time.Second) //等待3秒
close(ch2) //关闭通道
}()
for x := range ch2 { //遍历通道,没有数据会阻塞,等通道关闭后退出遍历
fmt.Printf("r(%s,%d) ", x, len(ch2))
}
// w(a,0) w(b,1) w(c,2) r(a,2) r(b,2) r(c,1) r(d,0) w(d,0)
}
func ReadOnly(ch <-chan int) {
for x := range ch {
print(x, " ")
}
}
func WriteOnly(ch chan<- int) {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch)
}
func main() {
ch := make(chan int)
go WriteOnly(ch)
ReadOnly(ch)
}
//内建error接口
type error interface {
Error() string
}
//断言底层结构类型,并获取更多错误信息。
func main() {
f, err := os.Open("/test.txt")
if err, ok := err.(*os.PathError); ok {
fmt.Println("File at path", err.Path, "failed to open")
return
}
fmt.Println(f.Name(), "opened successfully")
}
package main
import (
"fmt"
"github.com/pkg/errors"
)
// 自定义error
type BizError struct {
Code int32 //错误编码
Message string //错误信息
Cause error //内部错误
}
// 实现error接口
func (err *BizError) Error() string {
return fmt.Sprintf("%s", err)
}
// 实现fmt.Formatter接口
func (err *BizError) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
fmt.Fprintf(s, "code:%d, message:%s", err.Code, err.Message)
if err.Cause != nil {
fmt.Fprintf(s, ", cause:%+v", err.Cause)
}
return
}
fallthrough
case 's':
fmt.Fprint(s, err.Message)
}
}
func WrapBizError(code int32, cause error) error {
return &BizError{
Code: code,
Message: mapBizErrorMessage[code],
Cause: cause,
}
}
const ERROR_LOGIN_FAIL = 1000
const ERROR_LOGIN_FAIL_MSG = "login fail"
var mapBizErrorMessage = map[int32]string{
ERROR_LOGIN_FAIL: ERROR_LOGIN_FAIL_MSG,
}
func login(user string, pwd string) error {
//github.com/pkg/errors包的Errorf()创建的错误包含堆栈信息
return WrapBizError(ERROR_LOGIN_FAIL, errors.Errorf("user '%s' not found", user))
}
func main() {
err := login("wills", "123456")
if err != nil {
fmt.Printf("%+v", err)
}
}
func divide(a, b int) int {
if b == 0 {
panic("除数不能为零")
}
return a / b
}
func main() {
defer func() {
if err := recover(); err != nil {
fmt.Println("panic:", err)
debug.PrintStack() //堆栈信息
}
}()
result := divide(10, 0)
fmt.Println(result)
}
Go 通过包(package)和模块(module)进行代码的模块化组织和管理。
/* 示例为未启用GO111MODULE的情况,项目文件结构如下:
.
|-- main.go
`-- mypkg
`-- mypkg.go
*/
//mypkg/mypkg.go
package mypkg
import "fmt"
func init() {
fmt.Print("init ")
}
func MyFunc() {
fmt.Println("MyFunc ")
}
//main.go
package main
import p "./mypkg"
func main() {
p.MyFunc()
}
//运行输出:init MyFunc
//示例 go.mod 文件,仅包含一个依赖
module code.oa.com/mymod //模块名,需包含代码库地址
go 1.20 //指明本模块代码所需的最低 go 版本,仅起标识作用
require github.com/pkg/errors v0.9.1 //依赖的模块名及版本
//示例 go.sum 文件,仅包含一个依赖
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
type slice struct {
array unsafe.Pointer // 指向底层数组
len int // 长度
cap int // 容量
}
map 的数据结构,核心是哈希表+桶链,具体如下:
map 的算法
协程的调度抽象看是一个生产者与消费者问题。程序生成协程G,线程M消费协程。
GM模型(早期版本(v1.1)的实现):
GMP模型(后来版本的实现):
通道的操作流程图(有缓冲区情况):
提供格式化输入与输出操作。
打印到控制台:
生成字符串:
输出到 io:
生成 error:
x := struct {
name string
age int
}{"john", 10}
fmt.Printf("%v\n", x) //{john 10}
fmt.Printf("%+v\n", x) //{name:john age:10}
fmt.Printf("%#v\n", x) //struct { name string; age int }{name:"john", age:10}
fmt.Printf("%9.2f\n", 123.456) // 123.46,前面有3个空格整体宽度9字符
控制台输入:
字符串输入:
io 输入:
提供字符串相关操作,包括大小写转换、修剪、查找替换、拆分拼接等。
var sb strings.Builder
sb.WriteString("Hello")
sb.WriteString("World")
fmt.Println(sb.String()) //HelloWorld
提供字符串转换相关操作。
s := strconv.Itoa(123) //int 转字符串,十进制
fmt.Println(s)
i, err := strconv.Atoi("123") //字符串转 int,十进制
fmt.Println(i, err)
i64, err := strconv.ParseInt("666", 8, 64) //字符串转 int,8 进制
strBit := strconv.FormatInt(i64, 2) //int 转字符串,2 进制
fmt.Println(strBit, err) //八进制 666 对应二进制 110110110
b, err := strconv.ParseBool("TRUE") //1 t T TRUE true True都为真,0 f F FALSE false False为假
fmt.Println(b, err) //true
f, err := strconv.ParseFloat("123.4567", 64) //字符串转 float64
fmt.Println(f, err)
提供时间相关操作,包括取系统时间与时区、格式化转换、比较、定时器、协程睡眠等。
//获取当前时间
now := time.Now()
fmt.Println("1: ", now)
//转字符串 24小时制
strNow := now.Format("2006-01-02 15:04:05")
fmt.Println("2: ", strNow)
fmt.Println("3: ", now.Format("2006-01-02 03:04:05 PM")) //12小时制
//字符串转日期,使用 0 时区
utcTime, err := time.Parse("2006-01-02 15:04:05", "2023-01-02 14:00:00")
fmt.Println("4: ", utcTime, err)
//字符串转日期,使用系统的本地时区
localTime, err := time.ParseInLocation("2006-01-02 15:04:05", "2023-01-02 14:00:00", time.Local)
fmt.Println("5: ", localTime, err)
//获取年月日时分秒
fmt.Println("6: ", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())
//获取unix时间戳(UTC1970起秒数)
fmt.Println("7: ", now.Unix())
//比较是否相等,会考虑时区影响
fmt.Println("8: ", now.Equal(now.UTC()))
//日期加减
fmt.Println("9: ", now.AddDate(-1, 2, 3)) //年-1,月+2,日+3
//时间加减
fmt.Println("10:", now.Add(time.Hour*2-time.Minute*2)) //时+2,分-2
//比较时间大小
fmt.Println("11:", now.Before(now.AddDate(0, 0, 1)))
fmt.Println("12:", now.After(now.AddDate(0, 0, -1)))
//时间差,获取时间间隔
duration := now.Sub(now.AddDate(0, 0, -1))
fmt.Println("13:", duration, duration.Hours(), duration.Minutes(), duration.Seconds())
//time.Since() 等于 time.Now().Sub()
fmt.Println("14:", time.Since(now.AddDate(0, 0, -2)))
//定时器,执行一次
time.AfterFunc(2*time.Second, func() {
fmt.Println("15: ", time.Since(now))
})
//定时器,间隔触发
ticker := time.NewTicker(3 * time.Second)
go func() {
for x := range ticker.C {
fmt.Println("16: ", x.Sub(now))
}
}()
//协程睡眠
time.Sleep(time.Second * 10)
ticker.Stop()
/*输出结果
1: 2023-09-14 13:20:17.650942692 +0800 CST m=+0.000047084
2: 2023-09-14 13:20:17
3: 2023-09-14 01:20:17 PM
4: 2023-01-02 14:00:00 +0000 UTC <nil>
5: 2023-01-02 14:00:00 +0800 CST <nil>
6: 2023 September 14 13 20 17
7: 1694668817
8: true
9: 2022-11-17 13:20:17.650942692 +0800 CST
10: 2023-09-14 15:18:17.650942692 +0800 CST m=+7080.000047084
11: true
12: true
13: 24h0m0s 24 1440 86400
14: 48h0m1.640158075s
15: 3.640260837s
16: 4.640267816s
16: 7.642234665s
16: 10.642233452s
*/
提供数学相关操作,包括各种数字类型的最大值常量、取整、取随机数、数学函数等。
提供排序相关操作,支持基本类型或自定义类型的排序。 内部实现了插入排序、归并排序、堆排序和快速排序,会根据数据量和是否稳定排序自动选择算法,确保效率。
标准库定义了以下类型,并实现了排序接口:
//int切片正序
arrInt := []int{5, 3, 7, 1, 9}
sort.Ints(arrInt)
fmt.Println(arrInt)//[1 3 5 7 9]
//int切片逆序
sort.Sort(sort.Reverse(sort.IntSlice(arrInt)))
fmt.Println(arrInt)//[9 7 5 3 1]
//float切片正序
arrFloat := []float64{2.0, 9.3, 2.3, 1.1, 6.3}
sort.Float64s(arrFloat)
fmt.Println(arrFloat)//[1.1 2 2.3 6.3 9.3]
//float切片逆序
sort.Sort(sort.Reverse(sort.Float64Slice(arrFloat)))
fmt.Println(arrFloat)//9.3 6.3 2.3 2 1.1]
//string切片正序
arrStr := []string{"x3", "x1", "v", "z", "b", "a"}
sort.Strings(arrStr)
fmt.Println(arrStr)//[a b v x1 x3 z]
//string切片逆序
sort.Sort(sort.Reverse(sort.StringSlice(arrStr)))
fmt.Println(arrStr)//[z x3 x1 v b a]
type Person struct {
Name string
Age int
}
type PersonSlice []Person
func (s PersonSlice) Len() int { return len(s) }
func (s PersonSlice) Less(i, j int) bool { return s[i].Age < s[j].Age }
func (s PersonSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func main() {
persons := PersonSlice{{"a", 20}, {"b", 5}, {"c", 10}, {"d", 5}}
//对实现了 sort.Interface 接口的类型排序
sort.Sort(persons)
//稳定排序
sort.Stable(persons)
fmt.Println(persons) //[{b 5} {d 5} {c 10} {a 20}]
//二分查找,返回最小符合条件的元素索引,注意后面元素必须全部符合条件
x := sort.Search(len(persons), func(i int) bool {
return persons[i].Age >= 10
})
fmt.Println(persons[x:]) //[{c 10} {a 20}]
//直接传入 Less 函数排序,无需实现 sort.Interface 接口
sort.Slice(persons, func(i, j int) bool {
return persons[i].Name < persons[j].Name
})
fmt.Println(persons) //[{a 20} {b 5} {c 10} {d 5}]
}
提供操作系统相关操作,包括文件、目录、进程、环境变量等操作。
//写入文件,0666为Unix权限代码,八进制0666等于二进制110110110,表示三种身份都是读写
if err := os.WriteFile("a.txt", []byte("hello world"), 0b110110110); err != nil {
fmt.Println(err)
}
//读取文件
buf, err := os.ReadFile("a.txt")
if err != nil {
fmt.Println(err)
}
fmt.Println(string(buf))
//删除文件
if err := os.Remove("a.txt"); err != nil {
fmt.Println(err)
}
//创建多级目录
if err := os.MkdirAll("b/c", 0666); err != nil {
fmt.Println(err)
}
//删除目录
if err := os.RemoveAll("b"); err != nil {
fmt.Println(err)
}
//获取临时目录
tmpDir := os.TempDir()
fmt.Println(tmpDir)
//获取工作目录
dir, err := os.Getwd()
if err != nil {
fmt.Println(err)
} else {
fmt.Println(dir)
}
//获取环境变量
x := os.Getenv("GOPATH")
fmt.Println(x)
//进程退出,0 为正常,非 0 为出错
os.Exit(0)
提供处理同步的工具,包括互斥锁、读写锁等。
组等待,用于实现主协程等待指定数量的子协程执行完成后再继续执行。
func main() {
wg := sync.WaitGroup{}
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
fmt.Print(i, " ")
}(i)
}
wg.Wait()
}
互斥锁(sync.Mutex)用于保证在任意时刻,只有一个协程访问某个数据。
type Counter struct { //支持加锁的计数器
sync.Mutex//嵌套,使Counter支持Mutex的方法
Data int
}
func main() {
counter := Counter{}
wg := sync.WaitGroup{} //用于辅助主协程等待
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counter.Lock() //加锁确保不会并发修改
defer counter.Unlock() //延迟解锁
counter.Data++
}()
}
wg.Wait()
fmt.Println(counter.Data)
//如果没有发生并发修改,将输出1000,否则小于1000
}
读写锁(sync.RWMutex)可以被同时多个读取者持有或唯一个写入者持有。
func main() {
m := sync.RWMutex{}
wg := sync.WaitGroup{}
start := time.Now()
for i := 0; i < 3; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
m.Lock()
fmt.Println(int(time.Since(start).Seconds()), "write begin ", i)
defer m.Unlock()
time.Sleep(time.Second * 5)
fmt.Println(int(time.Since(start).Seconds()), "write end ", i)
}(i)
}
time.Sleep(time.Second * 1)
for i := 0; i < 3; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
m.RLock()
fmt.Println(int(time.Since(start).Seconds()), "read begin ", i)
defer m.RUnlock()
time.Sleep(time.Second * 1)
fmt.Println(int(time.Since(start).Seconds()), "read end ", i)
}(i)
}
wg.Wait()
}
/*输出结果可见:写独占读并发
0 write begin 2
5 write end 2
5 read begin 2
5 read begin 0
5 read begin 1
6 read end 1
6 read end 2
6 read end 0
6 write begin 0
11 write end 0
11 write begin 1
16 write end 1
*/
Go 的内建类型map支持并发读,但不支持并发写。有两种方法来实现 map 并发读写:
m := sync.Map{}
for i, c := range "abcdefg" {
m.Store(string(c), i) //写入
}
v1, ok := m.Load("c") //读取
fmt.Println(v1, ok) //2 true
m.Delete("c") //删除
v2, ok := m.Load("c") //再次读取,不存在
fmt.Println(v2, ok) //<nil> false
//遍历
m.Range(func(key, value interface{}) bool {
fmt.Println(key, value)
return true //返回 false 终止循环
})
net包及其子包 net/http、net/url 包等提供了HTTP、TCP、UDP 等网络协议和相关辅助功能的实现。
http 服务端的实现主要包括以下类型:
func httpServer() {
mux := http.NewServeMux()
mux.HandleFunc("/get", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("resp of get " + r.URL.Query().Get("param")))
})
mux.HandleFunc("/postform", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("resp of postform " + r.PostFormValue("param")))
})
mux.HandleFunc("/postjson", func(w http.ResponseWriter, r *http.Request) {
reqBody, err := io.ReadAll(r.Body)
if err != nil {
fmt.Println(err)
return
}
w.Write([]byte("resp of postjson " + string(reqBody)))
})
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
}
err := server.ListenAndServe()
if err != nil {
fmt.Println(err)
}
}
func main() {
go httpServer()
time.Sleep(time.Second * 1) //简单等待服务端启动
processResult := func(resp *http.Response, err error) {
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()
buf, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(buf))
}
//http get
processResult(http.Get("http://127.0.0.1:8080/get?param=123"))
//http post form
processResult(http.PostForm("http://127.0.0.1:8080/postform", url.Values{"param": {"456"}}))
//http post json
processResult(http.Post("http://127.0.0.1:8080/postjson", "application/json", bytes.NewReader([]byte(`{"param":"789"}`))))
}
func processError(err error) { //用于简单处理错误
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func tcpServer() {
listener, err := net.Listen("tcp", "127.0.0.1:8080") //监听
processError(err)
for {
conn, err := listener.Accept() //建立连接
processError(err)
go func() {
defer conn.Close() //延迟关闭连接
//先读取数据,约定换行作为边界符
data, err := bufio.NewReader(conn).ReadString('\n')
processError(err)
fmt.Print("server read:", data)
//再写入数据,data已包含换行
_, err = conn.Write([]byte("resp " + data))
processError(err)
}()
}
}
func tcpClient() {
conn, err := net.Dial("tcp", "127.0.0.1:8080") //建立连接
processError(err)
defer conn.Close() //延迟关闭连接
//先写入数据,约定换行作为边界符
_, err = conn.Write([]byte("hello world\n"))
processError(err)
//再读取数据,约定换行作为边界符
data, err := bufio.NewReader(conn).ReadString('\n')
processError(err)
fmt.Print("client read:", data)
}
func main() {
go tcpServer()
time.Sleep(time.Second) //简单等待服务端启动
tcpClient()
}
func processError(err error) { //用于简单处理错误
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func udpServer() {
udp, err := net.ListenUDP("udp", &net.UDPAddr{ //开始监听
IP: net.IPv4(0, 0, 0, 0),
Port: 8080,
})
processError(err)
defer udp.Close() //延迟关闭监听
for {
//读取数据
buf := [512]byte{}
n, addr, err := udp.ReadFromUDP(buf[:])
processError(err)
data := string(buf[0:n])
fmt.Println("server read:", data, "from:", addr.String())
//再写入数据
_, err = udp.WriteToUDP([]byte("resp "+data), addr)
processError(err)
}
}
func udpClient() {
udp, err := net.DialUDP("udp", nil, &net.UDPAddr{
IP: net.IPv4(0, 0, 0, 0),
Port: 8080,
})
processError(err)
defer udp.Close() //延迟关闭
//先写入数据
_, err = udp.Write([]byte("hello world"))
processError(err)
//再读取数据
buf := [512]byte{}
n, addr, err := udp.ReadFromUDP(buf[:])
processError(err)
data := string(buf[0:n])
fmt.Println("client read:", data, "from:", addr.String())
}
func main() {
go udpServer()
time.Sleep(time.Second) //简单等待服务端启动
udpClient()
}
context包:提供上下文相关功能,通常只适用于后端接口实现请求上下文的应用场景。
上下文:表示执行某个任务时所处的环境和背景状态。
区分请求参数和请求上下文信息:
使用方法:
使用建议:
底层原理:
type userNameKey struct{} //定义空struct做key的类型
func process(ctx context.Context) {
now := time.Now()
for { //每秒检测一次取消信号
select {
case <-ctx.Done(): //上游取消或到达截止时间时,返回已关闭通道
fmt.Println("process done, err:", ctx.Err())
return
default: //有 default 分支的 select 不会阻塞
time.Sleep(time.Second * 1)
userName := ctx.Value(userNameKey{}) //获取上下文信息
fmt.Println(int(time.Since(now).Seconds()), userName)
}
}
}
func main() {
//创建一个超时时间为10秒的可取消上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
//创建一个带键值对的子上下文
ctx = context.WithValue(ctx, userNameKey{}, "wills")
go process(ctx) //将上下文传递给下游
time.Sleep(time.Second * 5) //等待执行 5 秒后取消
cancel() //WithTimeout()返回的闭包函数,上游取消时调用
time.Sleep(time.Second * 5) //简单等待子线程退出后再退出
}
reflect 包提供反射相关功能,反射是指程序在运行期对自己进行检测、修改和扩展的能力。 通过反射,可以获取类型、字段、方法等反射信息,可以修改字段值,支持数组、切片、map、指针、接口等复杂类型。使程序在运行期获得极为灵活的能力。 常用方法或接口:
type A struct {
Astr1 string `ignore:"true"`
Astr2 string
AsliceB []B
innerStr1 string //内部字段
}
type B struct {
Bstr1 string
Bstr2 string `ignore:"true"`
}
// 传入任何类型数据,清空其类型为 string 的公开字段值,如果字段标记 ignore 则忽略。
// 支持嵌套struct和切片类型。
func ClearAllStringFields(obj any) error {
objType, objValue := reflect.TypeOf(obj), reflect.ValueOf(obj)
if objType.Kind() == reflect.Slice { //slice需要循环递归处理
lstLen := objValue.Len()
for j := 0; j < lstLen; j++ {
objItem := objValue.Index(j)
if objItem.Kind() == reflect.Ptr {
ClearAllStringFields(objItem.Interface())
continue
}
if objItem.CanAddr() {
ClearAllStringFields(objItem.Addr().Interface())
}
}
return nil
}
if objType.Kind() == reflect.Ptr { //指针需要取值
objType, objValue = objType.Elem(), objValue.Elem()
}
if objType.Kind() != reflect.Struct {
return nil
}
fieldNum := objType.NumField()
for i := 0; i < fieldNum; i++ { //遍历结构体的字段
curField := objType.Field(i)
curValue := objValue.Field(i)
if !curValue.CanSet() { //过滤掉不可修改的字段,首字母小写的字段不可修改
continue
}
if curField.Type.Kind() == reflect.Struct ||
curField.Type.Kind() == reflect.Slice {
ClearAllStringFields(curValue.Interface())
continue
}
ignore := curField.Tag.Get("ignore")
if ignore == "true" {
continue
}
curValue.Set(reflect.Zero(curValue.Type()))
}
return nil
}
func main() {
s := []A{
{"no", "yes", []B{{Bstr1: "yes", Bstr2: "no"}, {Bstr1: "yes", Bstr2: "no"}}, "no"},
{"no", "yes", []B{{Bstr1: "yes", Bstr2: "no"}, {Bstr1: "yes", Bstr2: "no"}}, "no"},
}
fmt.Printf("before clear:\n%+v\n", s)
ClearAllStringFields(s)
fmt.Printf("after clear:\n%+v\n", s)
}
Gin 是轻量级的 Web 框架,用于快速搭建 RESTful 风格的 Web 服务。 有以下特点:
func MyMiddleware1() gin.HandlerFunc { //中间件 1
return func(context *gin.Context) {
fmt.Println("MyMiddleware1 begin")
context.Next() //执行下一个中间件后再继续
fmt.Println("MyMiddleware1 end")
}
}
func MyMiddleware2() gin.HandlerFunc { //中间件 2
return func(context *gin.Context) {
fmt.Println("MyMiddleware2")
}
}
func main() {
r := gin.Default() //默认引擎注册了日志中间件和panic处理中间件
r.Use(MyMiddleware1(), MyMiddleware2()) //注册两个自定义中间件
r.GET("/hello", func(c *gin.Context) { //注册GET路由
c.JSON(http.StatusOK, gin.H{
"message": "Hello world",
})
})
r.GET("/hello/:id", func(c *gin.Context) { //注册GET路由,支持URL参数
id := c.Param("id")
c.JSON(http.StatusOK, gin.H{
"message": "Hello " + id,
})
})
r.Run(":8080")
}
/* test.http
###
GET http://127.0.0.1:8080/hello HTTP/1.1
###
GET http://127.0.0.1:8080/hello/123 HTTP/1.1
*/
Beego是一个全功能的Web开发框架,采用MVC架构,相对缺点是性能弱,代码结构复杂。 iris功能比gin丰富,支持MVC,基础功能与gin比较接近,比Beego新,比Beego性能好。
orm 框架有 gorm 和 xorm 等,其中社区活跃度是 gorm。
net/rpc 是标准库自带rpc框架。使用标准库 encoding/gob 进行编解码,无法跨语言调用。
gRPC 是谷歌开发的rpc框架,有以下特点:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。