查看goroutine数量
runtime.NumGoroutine()
设置执行Goroutine的最多cpu数目
runtime.GOMAXPROCS(2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
func GOMAXPROCS(n int) int {
if GOARCH == "wasm" && n > 1 {
n = 1 // WebAssembly has no threads yet, so only one CPU is possible.
}
lock(&sched.lock)
ret := int(gomaxprocs)
unlock(&sched.lock)
if n <= 0 || n == ret { //设为0或负数时默认为机器最大cpu数目
return ret
}
stopTheWorld("GOMAXPROCS")
// newprocs will be processed by startTheWorld
newprocs = int32(n)
startTheWorld()
return ret
}
|
包管理工具godep常用命令
godep save 初始化或,生成Godeps和vendor目录
godep restore 按照Godeps.json文件go get
Go Cross Compile
1
2
3
4
|
GOOS=linux GOARCH=amd64 go build hello.go
# beego交叉编译打包
bee pack -be GOOS=linux -be GOARCH=amd64
|
Go数据类型
- bool
- uint/int
- byte
- Intx : x代表8,16,32,64其中一个
- floatx: x为32和64其中一个
- complex64/complex128: 复数类型
- uintptr
- arrary/struct/string
- 引用类型slice, map, channel
- 接口: interface
- 函数:func
浮点数计算精度有点坑,
1
2
3
4
5
6
|
a := 8.89 //默认计算机位数
fmt.Println(a + 0.3)
fmt.Println(8.899 + 0.3)
9.190000000000001
9.19
|
位运算
- 位移:要位移的值是有符号值时,Go自动应用算术位移
- 取反:^a, 在 Go 中 x = 1 ^ x 可以翻转该位
- 异或:a^b.
1
|
32 << (^uint(0) >> 63) // 可能位32或64,取决于平台位数
|
生成随机数
在package rand里,如rand.Int(),rand.Intn(n int)
终端输入
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
|
input := bufio.NewScanner(os.Stdin)
for input.Scan() {
fmt.Println(foo(foo(input.Text())))
}
nums := [9][9]int{}
for i := 0; i < 9; i++ {
for j := 0; j < 9; j++ {
fmt.Scanf("%d", &nums[i][j])
}
}
//输入整数数字,回车一次处理一次
input := bufio.NewScanner(os.Stdin)
nums := []int{}
for input.Scan() {
numStrs := strings.Split(input.Text(), ",")
for _, str := range numStrs {
num, _ := strconv.Atoi(str)
nums = append(nums, num)
}
fmt.Println(nums)
nums = []int{}
}
|
实现可以在任意目录下建立go项。当然GOPATH变量还是要存在的。$GOPATH/pkg目录下会有一个mod目录
1
2
3
4
5
6
7
8
|
go mod download 下载依赖的module到本地cache(默认为$GOPATH/pkg/mod目录)
go mod edit 编辑go.mod文件
go mod graph 打印模块依赖图
go mod init 初始化当前文件夹, 创建go.mod文件
go mod tidy 增加缺少的module,删除无用的module
go mod vendor 将依赖复制到vendor下
go mod verify 校验依赖
go mod why 解释为什么需要依赖
|
go mod经常会因download失败,最好加少代理,比较优秀的代理网站如 https://goproxy.io/zh/、https://goproxy.cn/ 等,若失败多试试几个代理网站,因为有的代理网站有可能没有你要的模版也会download失败。
字符串拼接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
join1 := s1 + s2
join2 := fmt.Sprintf("%s%s", s1, s2)
ss := []string{s1, s2}
join3 := strings.Join(ss, "")
var builder strings.Builder
builder.WriteString(s1)
builder.WriteString(s2)
fmt.Println(builder.String())
var buf bytes.Buffer
buf.WriteString(s1)
buf.WriteString(s2)
fmt.Println(buf.String())
|
效率方面这里有介绍»
但我用我下面方法计算耗时,好像跟资料不符,不知道是哪出了问题
1
2
3
4
5
6
7
8
9
|
func computeTime(desc string, function func()) {
start := time.Now()
function()
duration := time.Since(start)
fmt.Printf("%s %s\n\n", desc, duration.String())
}
|
设计模式
https://legacy.gitbook.com/book/hxangel/go-patterns/details
最值
1
2
3
|
int(^uint(0)>>1) 最大值
^(int(^uint(0)>>1))最小值
|
初始化二维数组
没有简易的方法给数组初始化特定值
1
2
3
4
5
6
|
f := make([][]int, n)
for i := 0; i < len(matrix); i++ {
f[i] = make([]int, n)
}
f := [n][n]int //n必须为常量
|
位运算
1
2
3
|
res >>= 2 //右移
res <<= (32 - bitN) //左移
|
interface排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package sort
// A type, typically a collection, that satisfies sort.Interface can be
// sorted by the routines in this package. The methods require that the
// elements of the collection be enumerated by an integer index.
type Interface interface {
// Len is the number of elements in the collection.
Len() int
// Less reports whether the element with
// index i should sort before the element with index j.
Less(i, j int) bool
// Swap swaps the elements with indexes i and j.
Swap(i, j int)
}
|
比如对二维数组排序,以第一个元素排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
type Nums2VList [][]int
func (s Nums2VList) Len() int {
return len(s)
}
func (s Nums2VList) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s Nums2VList) Less(i, j int) bool {
return s[i][0] < s[j][0]
}
list := Nums2VList([][]int{a1, a2, a3,...})
sort.Sort(list)
|
程序在零点触发执行
1
2
3
4
5
6
7
8
9
|
for {
doSomething()
now := time.Now()
next := now.Add(time.Hour*24)
next = time.Date(next.Year(),next.Month(),next.Day(),0,0,0,0,next.Location())
t := time.NewTicker(next.Sub(now))
<- t.C
}
|
初始化
1
2
|
var arr []int //arr长度为0,但为nil
arr := make([]int, 0) //长度为0,但不为nil map亦如此
|
不过奇怪的是arr可以使用append 操作,而map不能
1
2
3
4
5
6
7
8
9
10
11
|
func main() {
var data []int
fmt.Println(data == nil) //true
data = append(data, 1)
fmt.Print(data)
var p map[int]bool //panic: assignment to entry in nil map
fmt.Println(p == nil) //true
p[1] = true
}
|
对于slice,当json解析时 a nil slice encodes to null, while []string{} encodes to the JSON array []
强制转换
1
2
3
4
5
6
|
a := -1
_ = uint(a) // why no panic?
var b uint
b = uint(a)
fmt.Println(b)
_ = uint(-1) // panics: main.go:7: constant -1 overflows uint
|
the reason»
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
type Foo string
func f(a Foo) {}
func main() {
f("sarkozy")
const t = "julie gayet"
f(t)
s := "hollande"
//compile error
f(s)
f(Foo(s)) // ok
}
|
the reason»
map无法取址
map无法取得对象地址,故无法对其进行修改
1
2
3
4
5
6
7
8
9
|
var m = map[string]struct{x, y int} {
"foo": {2, 3},
}
func main() {
fmt.Println(m["foo"].x)
m["foo"].x = 4 // cannot assign to m["foo"].x
fmt.Println(m["foo"].x)
}
|
需要改为
1
2
3
|
var m = map[string]*struct{x, y int} {
"foo": {2, 3},
}
|
»
atomic原子操作
原子操作由底层硬件支持,而锁则由操作系统提供的API实现。显然原子操作效率高,但作用域小,且功能少。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// PutMessage writes a Message to the queue
func (t *Topic) PutMessage(m *Message) error {
t.RLock()
defer t.RUnlock()
if atomic.LoadInt32(&t.exitFlag) == 1 {
return errors.New("exiting")
}
err := t.put(m)
if err != nil {
return err
}
atomic.AddUint64(&t.messageCount, 1)
atomic.AddUint64(&t.messageBytes, uint64(len(m.Body)))
return nil
}
|
golamg atomic uint64原子操作没有减法,减法操作如下
1
2
3
4
|
var x uint64 = 90
var c uint64 = 2
atomic.AddUint64(&x, ^uint64(c-1)) //90-2
fmt.Println(x)
|
»
var不需要指定类型
1
|
var dp = [1001][1001]int{} //自动判别类型
|
select chan
select加break和不加效果一样,若加了break,break只跳出select,并不能跳出select的外循环;若没有default,程序会阻塞到chan非空为止
若想要跳出for循环,可以用goto+point或break+指定循环
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
|
// break+指定循环
EXIT_FOR:
for {
fmt.Println("select")
select {
case <-ch2:
case <-ch1:
break EXIT_FOR
// default:
}
}
w.Done()
// goto
for {
fmt.Println("select")
select {
case <-ch2:
case <-ch1:
goto EXIT
// default:
}
}
EXIT:
w.Done()
|
defer
defer在return后执行,在函数结束前执行。执行return res时,将res的值赋给函数返回变量。
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
32
33
34
35
36
37
38
39
40
41
42
43
|
package main
import "fmt"
func main() {
fmt.Println(DeferFunc1(1))
fmt.Println(DeferFunc2(1))
fmt.Println(DeferFunc3(1))
DeferFunc4()
}
func DeferFunc1(i int) (t int) { //返回变量位t
t = i
defer func() {
t += 3
}()
return t //这一步t是多余的,相当于t=t
}
func DeferFunc2(i int) int {
t := i
defer func() {
t += 3
}()
return t //将t的值给函数返回变量,故defer中再对t修改时,是没有
}
func DeferFunc3(i int) (t int) {
defer func() {
t += i
}()
return 2
}
func DeferFunc4() (t int) {
defer func(i int) {
fmt.Println(i)
fmt.Println(t)
}(t)
t = 1
return 2
}
|
详解»
defer函数嵌套函数执行顺序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
func calc(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}
func main() {
a := 1
b := 2
// The value of calc("10", a, b) needs to be resolved first to be registered with defer.
defer calc("1", a, calc("10", a, b))
a = 0
defer calc("2", a, calc("20", a, b))
b = 1
}
|
详解6»
new和make初始化
new会自动用zero value初始化值,比如0,nil,““等,返回指针(指针不是nil,是指向存储地址)。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
list := new([]int)
*list = append(*list, 1)
fmt.Println(*list)
stu := new(map[string]int)
*stu = nil
// fmt.Println(*stu)
(*stu)["明晶"] = 12
// listm := make([]int, 0)
// fmt.Println(listm)
// stum := make(map[string]int)
// stum["明晶"] = 12
|
详解»
struct比较
下面程序编译会通过吗?为什么?
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
|
func main() {
sn1 := struct {
name string
age int
}{age: 11, name: "qq"}
sn2 := struct {
age int
name string
}{age: 11, name: "qq"}
if sn1 == sn2 { //变量顺序不同
fmt.Println("sn1 == sn2")
}
sm1 := struct {
age int
m map[string]string
}{age: 11, m: map[string]string{"a": "1"}}
sm2 := struct {
age int
m map[string]string
}{age: 11, m: map[string]string{"a": "1"}}
if sm1 == sm2 {
fmt.Println("sm1 == sm2")
}
}
|
struct containing map[string]string cannot be compared
可以通过reflect.DeepEqual比较
组合重写方法
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
|
func main() {
stu := Student{}
stu.ShowA()
}
type People struct {
}
func (this *People) ShowA() {
fmt.Println("show A")
this.ShowB()
}
func (this *People) ShowB() {
fmt.Println("show B")
}
type Student struct {
People
}
func (this *Student) ShowB() {
fmt.Println("student show B")
}
// 改进
// func (this *Student) ShowA() {
// this.People.ShowA()
// this.ShowB()
// }
|
输出:
golang没有继承,只有组合,Student组合了People的功能,但People下的方法无法知道Student的方法ShowB。这点跟java的继承方法不同. golang的组合有点像俄罗斯套娃,大套娃可以call小套娃call它的方法,但小套娃里面的功能大套娃看不见;而java的继承,和生物继承有些类似,子类继承了父类的所有方法成为自己身体的一部分。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public class People {
public void showA(){
System.out.println("show A");
showB();
}
public void showB(){
System.out.println("show B");
}
}
public void showB(){
// super.showB();
System.out.println("student show B");
}
public class Main {
public static void main(String[] args) {
Student stu = new Student();
stu.showA();
}
}
|
上面java代码输出:
1
2
|
show A
student show B
|
switch 和 select
select的case操作只能是io语句
- 没有default语句时,select会一直等待知道某个io可以操作
- 如果有多个case同时可以运行,会随机选择一个执行
switch的case,只有表达式、类型(boolean-expression or integral type)»
interface方法集
https://golang.google.cn/ref/spec#Method_sets
找找下面代码段问题
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
|
package main
import (
"fmt"
)
type People interface {
Speak(string) string
}
type Student struct{}
func (stu *Student) Speak(think string) (talk string) {
if think == "bitch" {
talk = "You are a good boy"
} else {
talk = "hi"
}
return
}
func main() {
var peo People = Student{}
think := "bitch"
fmt.Println(peo.Speak(think))
}
|
如上例有一种修改方法是将Speak方法改为
1
2
3
4
5
6
7
8
|
func (stu Stduent) Speak(think string) (talk string) {
if think == "bitch" {
talk = "You are a good boy"
} else {
talk = "hi"
}
return
}
|
此时声明语句可以接收(t T) and (t *T)两种方法
1
2
|
var peo People = &Stduent{}
var peo People = Stduent{}
|
从接收者的角度来看规则
Values |
Methods Receivers |
T |
(t T) |
*T |
(t T) and (t *T) |
对于类型T,它的方法集只包含接收T类型的方法;对于类 *T,它的方法集包含接收T类型的方法和 接收 *T 类型的方法。故上面的Student{} 的方法集没有Speak方法,所以不可以用People接口接收;若改为&Student{},它包含了Speak方法集,则可以。(仅对interface有效)
1
2
3
4
5
6
7
|
func tell(peo People, sentence string) {
peo.Speak(sentence)
}
func main() {
var stu Student = Student{}
tell(&stu, "bitch") //或 tell(stu, "bitch") T/*T都可以适配T
}
|
若开头的问题声明,换成下面代码,会有问题吗?没问题,但不是说Student{}的方法集不包含Speak方法吗?没错,但是你这样写编译器会帮你把 peo.Speak(think)
改成 (&peo).Speak(think)
1
2
3
|
peo := Student{}
think := "bitch"
fmt.Println(peo.Speak(think))
|
开头问题答案:
1
|
cannot use (Student literal) (value of type Student) as People value in variable declaration: missing method Speak
|
interface内部结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
type People interface {
Show()
}
type Student struct{}
func (stu *Student) Show() {
}
func live() People {
var stu *Student
return stu
}
func main() {
if live() == nil {
fmt.Println("AAAAAAA")
} else {
fmt.Println("BBBBBBB")
}
}
|
data指向了nil并不代表interface指向了nil,interface{}才是真的nil »
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
|
type eface struct { //空接口
_type *_type //类型信息
data unsafe.Pointer //指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于c语言中的void*)
}
type iface struct { //带有方法的接口
tab *itab //存储type信息还有结构实现方法的集合
data unsafe.Pointer //指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于c语言中的void*)
}
type _type struct {
size uintptr //类型大小
ptrdata uintptr //前缀持有所有指针的内存大小
hash uint32 //数据hash值
tflag tflag
align uint8 //对齐
fieldalign uint8 //嵌入结构体时的对齐
kind uint8 //kind 有些枚举值kind等于0是无效的
alg *typeAlg //函数指针数组,类型实现的所有方法
gcdata *byte
str nameOff
ptrToThis typeOff
}
type itab struct {
inter *interfacetype //接口类型
_type *_type //结构类型
link *itab
bad int32
inhash int32
fun [1]uintptr //可变大小 方法集合
}
|
Type Alias(类型别名)
1
2
3
4
5
6
7
8
|
func main() {
type MyInt1 int
type MyInt2 = int
var i int =9
var i1 MyInt1 = i
var i2 MyInt2 = i
fmt.Println(i1,i2)
}
|
MyInt1是基于int定义的新类型,MyInt2是int的别名。
闭包
闭包引用相同变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
func main() {
a, b := test(10)
a()
b()
}
func test(x int) (func(), func()) {
return func() {
fmt.Println(x)
x += 10
}, func() {
fmt.Println(x)
}
}
|
闭包变量延迟
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
func main() {
funcs := test(10)
for _, f := range funcs {
f()
}
}
func test(x int) []func() {
var funcs []func()
for i := 0; i < 2; i++ {
funcs = append(funcs, func() {
fmt.Println(i)
})
}
return funcs
}
|
Rune处理utf-8字符串个数神器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
func Utf8Index(str, substr string) int {
asciiPos := strings.Index(str, substr)
if asciiPos == -1 || asciiPos == 0 {
return asciiPos
}
totalSize := 0
pos := 0
reader := strings.NewReader(str)
for _, size, err := reader.ReadRune(); err == nil; _, size, err = reader.ReadRune() {
totalSize += size
pos++
if totalSize == asciiPos {
return pos
}
}
return pos
}
|
nil值
对于 nil 的 map、channel,我们可以简单的把它看成只读对象,写入会 panic。
nil的slice,可读可写
1
2
3
4
5
6
7
8
9
|
var m map[string]string //nil
m["name"] = "zzy" // nil error
e, _ := m["hello"]
fmt.Println(len(e)) // ""
var s []int //nil
fmt.Println(len(s)) //0
s = append(s, 1)
|
close chan
Receive on a closed channel will return the zero value for the channel’s type without blocking:
1
2
3
4
5
6
7
|
package main
import "fmt"
func main() {
ch := make(chan int)
close(ch)
fmt.Println(<-ch)
}
|
The executing result is like below:
详情»
nsq的topic exitChan利用了这一特性来忽略操作
Recover
发生panic,若不处理,整个Go程序会退出。为了让程序继续执行,Go有一个类似 Java 的 Throw…catch 机制来处理错误 —— Recover。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
func foo(a int) {
defer func() {
if r := recover(); r != nil {
fmt.Printf("捕捉到错误: %s\n", r)
}
}()
b := 0
a = a / b
}
func main() {
go foo(1)
time.Sleep(time.Second)
fmt.Println("正常退出")
}
|
输出:
1
2
3
|
$ go run main.go
捕捉到错误: runtime error: integer divide by zero
正常退出
|
但 recover 只对当前函数的错误有用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
func foo(a int) {
defer func() {
if r := recover(); r != nil {
fmt.Printf("捕捉到错误: %s\n", r)
}
b := 0
a = a / b
}()
}
func main() {
go foo(1)
time.Sleep(time.Second)
fmt.Println("正常退出")
}
|
输出:
1
2
3
4
5
6
7
8
9
10
11
|
$ go run main.go
panic: runtime error: integer divide by zero
goroutine 6 [running]:
main.foo.func1(0xc00004c7d8)
/Users/cmj/workspace/Go/src/hello/main.go:76 +0xa2
main.foo(0x1)
/Users/cmj/workspace/Go/src/hello/main.go:78 +0x53
created by main.main
/Users/cmj/workspace/Go/src/hello/main.go:80 +0x42
exit status 2
|
查看内存逃逸
内存逃逸:栈上的对象逃逸到了堆上
1
|
go build -gcflags '-m -m' main.go
|
逃逸情况
-
占用的内存太大,比如 make([]int, 10000000)
1
2
3
4
|
// Large objects (> 32 kB) are allocated straight from the heap.
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
...
}
|
-
函数返回指针,无法推断对象的生命周期
-
因不确定长度或动态扩展,需要在运行时重新分配地址,可预算空间
-
运行时不确定的类型也会逃逸,比如interface
分配在堆上的缺点:堆上的对象使用完后需要 GC
https://studygolang.com/articles/22875
性能分析工具 pprof
追踪GC
可以和pprof配合使用
1
|
GODEBUG=gctrace=1 go run app.go
|
slice遍历可以不取值
1
2
3
|
nums := []int{1, 2, 3}
for range nums {
}
|
参考
https://learnku.com/go/t/23460/bit-operation-of-go
https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function