go学习记录


实用技巧

重复克隆字符串

1
2
3
d := "a"
s := strings.Repeat(d, 10)
fmt.Println(s) // aaaaaaaaaa

通道超时

1
2
3
4
5
6
7
8
table := make(chan int)

select {
case d := <-table:
fmt.Println(d) // 能否从table通道中取得数据
case <-time.After(time.Second): // 1秒钟的超时时间
fmt.Println("time out")
}

定时器

1
2
3
4
tick := time.NewTicker(time.Second / 2) // 每秒两次执行循环内的动作
for t := range tick.C {
// todo
}

阻塞

1
2
3
for {} // 无限循环,占用cpu高
for { time.Sleep(time.Second) } // 占用cpu低
select {} // 阻塞到死,不占用cpu

容量为1的通道可以当作互斥锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
mutex := make(chan struct{}, 1) // 作为互斥锁,容量必须为1,当通道里有一个值就阻塞,如果大于1,可能会因为并发产生结果小于预期值
done := make(chan struct{})
counter := 0

num := 1000

for range num {
go func() {
for range 100 {
mutex <- struct{}{} // 加锁,由于通道容量为1,必须要解锁后才能进行下一步操作
counter++
<-mutex // 解锁
}

done <- struct{}{}
}()
}

for range num {
<-done
}
fmt.Println(counter) // 100000

查看时间差

1
2
startTime := time.Now()
fmt.Println(time.Since(startTime))

switch case

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
s := []interface{}{"a", 1, 2, []interface{}{}, []int{1, 2, 3}, nil, map[string]int{}, [...]string{"1", "a", "cc"}}

for _, v := range s {
switch d := v.(type) {
case int, int32:
fmt.Println("int", d, reflect.TypeOf(d))
case string:
fmt.Println("string", d, reflect.TypeOf(d))
case []int:
fmt.Println("[]int", d, reflect.TypeOf(d))
case []interface{}:
fmt.Println("[]intreface{}", d, reflect.TypeOf(d))
case []string:
fmt.Println("[]string", d, reflect.TypeOf(d))
case map[string]int:
fmt.Println("map", d, reflect.TypeOf(d))
case nil:
fmt.Println("nil", d, reflect.TypeOf(d))
default:
fmt.Println("default", d, reflect.TypeOf(d))
}
}

方法隐式声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
每一个值类型的方法都对应一个隐式声明的指针方法
每一个方法会隐式申明一个函数
所以一个值类型的方法会隐式声明一个指针方法,一个函数,一个指针函数
type Page struct {
limit int
}

func (p *Page) SetPage(limit int) *Page {
// 会隐式声明 (*Page).SetPage()函数
p.limit = limit

return p
}

func (p Page) Page() int {
// 会隐式声明 Page.Page() 函数
return p.limit
}

// Page方法隐式声明的方法
//func (p *Page) Page() int {
// return p.limit
//}

func main() {
c := Page{}
(*Page).SetPage(&c, 2) // 调用隐式申明的方法
fmt.Println(c, Page.Page(c)) // {2},2
c.SetPage(1) // 普通调用
fmt.Println(c, Page.Page(c)) // {1},1
}

金额计算

1
2
3
4
使用github.com/shopspring/decimal库
d1 := decimal.NewFromFloat(1.1)
d2 := decimal.NewFromFloat(1.111211212)
res := decimal.Sum(d1, d2)

遍历通道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
c:=make(chan int)
for v := range c {
fmt.Println(v)
}

等同于
for {
d, ok := <-c

if !ok {
break
}
fmt.Println(d)
}

变长参数

1
2
3
4
5
6
7
8
func test(a ...int) {
fmt.Println(a) // a是[]int类型
}

调用

test(1,2,3,4)
输出 []int{1,2,3,4}

拼接字符串

1
2
3
4
a := "aslfnl林肯afn"
b := "aaaa"

string(append([]byte(a), b...)) //语法糖,允许将字符串作为byte切片使用

byte,rune转换

1
2
[]byte(string(c)) rune->byte
[]rune(string(c)) byte->rune

获取首个中文字符索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
s := "AL兰SKFfAF阿是富兰克林快疯了"
// 一个中文占3个字节,使用字符索引,不能使用下标,需要先转成rune切片
b := []rune(s)

for i, v := range b {
// 判断是否为中文
if unicode.Is(unicode.Han, v) {
fmt.Println(i, string(v))
break
}
}

// 如果要使用字节索引,获取的字节是字符开始的下标,直接使用会是乱码
for i, v := range s {
// 判断是否为中文
if unicode.Is(unicode.Han, v) {
fmt.Println(i, string(v))
break
}
}
//使用s[2:5]显示完整的中文

删除切片中的一段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
小切片可用
append(s[:from], s[to:]...)

大切片,避免使用append可能产生内存分配
var s = []int{1, 2, 3, 4, 5, 6}
to := 3
from := 1
copy(s[from:], s[to:])
s[:len(s)-(to-from)]

优化大切片
var s = []int{1, 2, 3, 4, 5, 6}
to := 3
from := 1

if n := to - from; len(s)-to > n {
//如果切片尾部的数据足够填充from到to之间的数据,就把切片尾部的数据移动到填充的地方
copy(s[from:to], s[len(s)-n:])
} else {
//如果切片数据不足,直接copy
copy(s[from:to], s[to:])
}

// 清理尾部已删除的槽位,避免内存泄露
for i := len(s) - (to-from); i < len(s); i++ {
s[i] = 0 // 零值赋值,释放原来的引用
}

s = s[:len(s)-(to-from)]


修改切片长度容量

1
2
3
4
5
var a = make([]int, 1, 4)

elem := reflect.ValueOf(&a).Elem()
elem.SetLen(2) //修改长度,长度只能修改为小于等于容量的值
elem.SetCap(2) //修改容量,容量只能修改为小于等于原始容量的值

clear函数

1
清空map,或者把切片中的字段转成0,不能对数组使用

查看内存消耗

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var memStats runtime.MemStats
runtime.ReadMemStats(&memStats)
fmt.Printf("Alloc = %v MiB\n", memStats.Alloc/1024/1024)
fmt.Printf("TotalAlloc = %v MiB\n", memStats.TotalAlloc/1024/1024)
fmt.Printf("Sys = %v MiB\n", memStats.Sys/1024/1024)
fmt.Printf("NumGC = %v\n", memStats.NumGC)

或者
var stat syscall.Rusage
syscall.Getrusage(syscall.RUSAGE_SELF, &stat)
fmt.Printf("Max RSS (KB): %v\n", stat.Maxrss)

或者
GODEBUG=gctrace=1 go run main.go
GODEBUG=gctrace=1 ./main

切片转数组

1
2
a := []int{1, 2, 3, 4}
d := [3]int(a[1:]) //d是新的数组,与a不共享内存数据,新数组大小不能大于切片大小

子切片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
a:=[]int{1,2,3,4,5,6}
a[l1:l2]//技巧,从0开始从前往后数,留头去尾
cap计算 = 长度 6 - l1 = 3
len计算 = l2 - l1

//例子
a := [...]int{0, 1, 2, 3, 4, 5, 6}
s0 := a[:] // 7,7 | 0, 1, 2, 3, 4, 5, 6
s1 := s0[:] // 7,7 | 0, 1, 2, 3, 4, 5, 6
s2 := s1[1:3] // 2,6 | 1, 2
s3 := s1[3:] // 4,4 | 3, 4, 5, 6
s4 := s0[3:5] // 2,4 | 3, 4
s5 := s4[:2:2] // 2,2 | 3, 4
s6 := append(s4, 77) //3,4 | 3,4,77 同时s6容量充足,依然使用s0作为底层,append s6会影响到 s0 s0 = 0,1,2,3,4,77,6
s7 := append(s5, 88) //3,4 | 3,4,88 cap=4是因为s5的容量达到上线,append扩容,原容量<1024,容量*2,扩容之后分配新的数组,所以不会对s0产生影响
s8 := append(s7, 66) // 4,4 | 3,4,88,66
s3[1] = 99 // 4,4 | 3,99,77,6, s0 = 0,1,2,3,99,77,6

goto

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
func main() {
i := 1
Lbam:
fmt.Println(i)

k := i * i //正确
fmt.Println(k)

if i > 5 {
goto Cc
}

k := i * i //错误,k的声明有可能被跳过
fmt.Println(k)
{
k := i * i //正确,减少了k的作用域
fmt.Println(k)
}

i++

goto Lbam
Cc:
fmt.Println(i)
}

switch-case

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
switch case,不需要break就能跳出分支,需要进入下一个分支可使用fallthrough
最后一个分支不能使用fallthrough,最后一个分支不一定是default分支,default分支有可能在中间
n := rand.IntN(10000)
switch n % 9 {
case 0, 1:
fmt.Println(1)
fallthrough //进入下一个分支
case 2, 3, 4, 5:
fmt.Println(2345)

if true {
fallthrough //错误,fallthrough必须是最后一条语句
}
default:
fmt.Println("default")
case 6, 7, 8:
fmt.Println(346)
fallthrough //错误,最后一个分支不能使用fallthrough
}

句点引入包

1
2
3
4
5
6
7
8
9
10
11
import (
. "fmt"
. "math/rand/v2"
)

func main() {
Print(IntN(9000)+1000, " ")
}

fmt.Print省略成Print
rand.IntN省略成IntN

匿名引入

1
2
3
4
import (
_ "math/rand"
)
为了执行引入包中的init一遍,可以不使用这个包

随机数

1
2
获取1000到9999的随机数,1.22及以上的版本,使用math/rand/v2
rand.IntN(9000) + 1000 // rand.IntN(9000)获取0到8999的随机数,+1000就是1000到9999的随机数

初始化

1
2
包级变量的初始化顺序取决于依赖顺序,不一定是顺序执行的,不能循环依赖
局部变量初始化顺序是顺序执行的

获取数据类型

1
2
3
4
5
6
7
8
9
var y int
reflect.TypeOf(y) //int
fmt.Sprintf("%T", y) //int

%v:将被替换为对应实参字符串表示形式
%T:将替换为对应实参的类型的字符串表示形式。
%x:将替换为对应实参的十六进制表示。实参的类型可以为字符串、整数、整数数组(array)或者整数切片(slice)等。 (数组和切片将在以后的文章中讲解。)
%s:将被替换为对应实参的字符串表示形式。实参的类型必须为字符串或者字节切片(byte slice)类型。
%%:将被替换为一个百分号

int最大值

1
2
3
4
5
6
7
8
获取uint最大值
在64位操作系统下可用,在32位操作系统下非法
const MaxUint uint = (1 << 64) - 1
通用方法
^uint(0)

获取int最大值
int(^uint(0) >> 1)

rune

1
2
3
4
5
6
7
8
9
10
rune中的特殊字符
\a (rune值:0x07) 铃声字符
\b (rune值:0x08) 退格字符(backspace)
\f (rune值:0x0C) 换页符(form feed)
\n (rune值:0x0A) 换行符(line feed or newline)
\r (rune值:0x0D) 回车符(carriage return)
\t (rune值:0x09) 水平制表符(horizontal tab)
\v (rune值:0x0b) 竖直制表符(vertical tab)
\\ (rune值:0x5c) 一个反斜杠(backslash)
\' (rune值:0x27) 一个单引号(single quote)

获取操作系统的位数

1
2
3
4
5
6
7
8
9
获取操作系统位数 32 << (^uint(0) >> 63) 32或64
^uint(0) >> 63 64位操作系统向右移动63位还剩一个1,32位操作系统为0
32<<1|0,相当于100000向左移动一位或者不移动,移动一位=1000000,相当于*2=64

是不是64位的操作系统
^uint(0)>>63 > 0

是不是32位的操作系统
^uint(0)>>32 == 0

常量补全

1
2
3
4
5
6
const (
x float64 = 1.1
y
)

y自动补全为1.1

iota

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const (
x = iota
y
)

x=0,y=1

const (
x = iota + 1.5
y
z = iota
)

x=1.5,y=2.5,z=2

const (
x = 1 << iota
y
z
)

x=1,y=2,z=4

const (
x = iota - 1
y
z
)

x=-1,y=0,z=1