golang核心笔记
一、Go的命令行
- GOROOT: go当前安装目录
- GOPATH: 工作区的集合,多个用:分隔.工作区是放置 Go 源码文件的目录.三个目录:**src 目录,pkg 目录,bin 目录**。
- GO111MODULE: mod包管理开启
- GOPROXY: go代理,配合GO111MODULE使用
- go env -w GO111MODULE=on
- eport GO111MODULE=auto
- go mod init [project_name] # 初始化一个mod管理项目
- go env # 查看更多命令
- go build xx.go -o xx -i [build flags]
- go run xx.go
- go clean # 删除目标文件和缓存文件
- go test -v # 测试项目目标的xxx_test.go文件
- go test -test.bench=".*" # 测试总个目录的
- go test xxx_test.go -test.bench=".*" # 测试单个文件
- go test xxx_test.go -benchmem -test.bench=".*" # 显示内存
- gofmt -w xx.go # 格式化某个文件或目录
二、标识符、变量与类据类型
25个关键字
if for func case struct import
go type chan defer default package
map const else break select interface
var goto range return switch continue fallthrough
保留字
内建常量:true false iota nil
内建类型:int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
float32 float64 complex128 complex64
bool: byte rune string error
内建函数: make delete complex panic append copy
close len cap real imag new recover
变量与常量的定义
- 常量定义: const [常量名] [类型]
- 变量定义: var [变量名] [类型]
//**************************常量定义************************************************
const name = 'ok' //隐式类型定义
const name string = "ok" //显式类型定义
//**************************变量定义************************************************
var dp = [3][5]int{[5]int{1, 2, 3, 4, 5}, [5]int{4, 5, 6}, [5]int{7, 8, 9}} //多维数组的初始化,不足补0
s := []string{"abc", "ABC"} //切片初始化
slice1 := s[1:2] //[开始:结束] 开始和结束不能大于数组实际长度
var slice2 []type = make([]type, len)
s1 := []int{1, 2} //切片增加元素
s1 = append(s1, 3)
s2 := []int{4, 5}
s3 := append(s1, s2...) //合并两个切片
//**************************************************************************
res := struct { //匿名结构体
Name string
Age int
}{Name:"lily", Age:18}
jsons, err := json.Marshall(res) //json序列化(注:jsons的类型是[]type)
errs = json.Unmarshal(jsons, &res2) //反序列化
//**************************************************************************
person := map[int]string{ //map集合是无序的 key-value 数据结构
1 : "Tom",
2 : "Aaron",
3 : "John",
}
delete(person, 2) //删除
person[2] = "Jack" //增加
person[3] = "Kevin" //修改
数据类型
- 值类型: 整型int、浮点型(float32、flaoat64)、复数complex128、bool、string、struct
- 引用类型:*、slice、map、func、chan、interface
- 字符型可以使用byte来保存单个字母:byte的实际类型是uint8, rune的实际类型是int32
- 整数类型:int和uint
- int8、int16、int32和int64四种有符号整数类型,uint8、uint16、uint32和uint64对应四种无符号整数类型。
strings包提供了字符串的一些常见操作函数
Index(str, s string) int // 查找s在字符串str中的索引
Contains(str, s string) bool // 判断str是否包含s
Join(s []string, str string) string // 通过字符串str连接切片 s
//替换字符串str中old字符串为new字符串,n表示替换的次数,小于0全部替换
Replace(str,old,new string,n int) string
Splite(str,s string)[]string // 字符串str按照s分割,返回切片
Trim(s string, cutset string) string // 去除头部、尾部指定的字符串
Fields(s string) []string // 去除空格,返回切片
strconv包的字符串转换函数: Append、Format、Parse
package main
import (
"fmt"
"strconv"
)
func main() {
// Append 系列函数将整数等转换为字符串后,添加到现有的字节数组中
str1 := make([]byte, 0, 100)
str1 = strconv.AppendInt(str1, 4567, 10)
str1 = strconv.AppendBool(str1, false)
str1 = strconv.AppendQuote(str1, "abcdefg")
str1 = strconv.AppendQuoteRune(str1, '单')
fmt.Println(string(str1)) // 4567false"abcdefg"'单'
// Format 系列函数把其他类型的转换为字符串
a := strconv.FormatBool(false)
b := strconv.FormatFloat(123.23, 'g', 12, 64)
c := strconv.FormatInt(1234, 10)
d := strconv.FormatUint(12345, 10)
e := strconv.Itoa(1023)
fmt.Println(a, b, c, d, e) // false 123.23 1234 12345 1023
// Parse 系列函数把字符串转换为其他类型
f, _ := strconv.ParseBool("false")
g, _ := strconv.ParseFloat("123.23", 64)
h, _ := strconv.ParseInt("1234", 10, 64)
i, _ := strconv.ParseUint("12345", 10, 64)
j, _ := strconv.Atoi("1023")
fmt.Println(f, g, h, j, i, j) // false 123.23 1234 1023 12345 1023
}
map与sync.Map
- map是一个无序键值对集合, 底层采用hash数据结构。类似C++中的 unorder_map, 而C++中的map底层是红黑树,天然有序
- sync.Map是线程安全map
package main
import (
"fmt"
"sync"
)
func main() {
var sM sync.Map // 特点1: 无段初始化,直接声明即可
sM.Store("id", 1) // 特点2: Store表示存储,Load表示获取,Delete表示删除。
sM.Store("name", "leilei")
fmt.Println(sM.Load("name"))
sM.Range(func(k,v interface{}) bool { //特点3: 使用Range配合一个回调函数进行遍历操作,通过回调函数返回内部遍历出来的值,
fmt.Println(k, v) // 需要继续迭代时,返回true,终止迭代返回false。
return true
})
}
浮点数判断相等
//使用 == 号判断浮点数,是不可行的,替代方案如下:
func isEqual(f1,f2,p float64) bool {
// p为用户自定义精度,如:0.00001
return math.Abs(f1-f2) < p
}
三、流程控制
-
fallthrough
代表不跳出switch,后面的语句无条件执行 -
break
用于函数内跳出当前for
、switch
、select
语句的执行 -
continue
用于跳出for
循环的本次迭代。 -
goto
可以退出多层循环, 可以用来统一错误处理
if i == '3' { // 初始化与判断写在一起: if a := 10; a == 10
}
if err := Connect(); err != nil { // 这里的 err!=nil 才是真正的if判断表达式
}
switch num {
case 1: // case 中可以是表达式
fmt.Println("111")
case 2:
fmt.Println("222")
default:
fmt.Println("000")
}
// 传统的for循环
for init;condition;post{
}
// for循环简化
var i int
for ; ; i++ {
if(i > 10){
break;
}
}
// 类似while循环
for condition {}
// 死循环
for{
}
// for range:一般用于遍历数组、切片、字符串、map、管道
for k, v := range []int{1,2,3} {
}
四、运算符
算术运算符: + - * / % ++ --
关系运算符: == != <= >= < >
逻辑运算符: ! && ||
位运算: &(按位与) |(按位或) ^(按位取反) <<(左移) >>(右移)
赋值运算符: = += -= *= /= %= <<= >>= &= ^= |=
其他运算符: &(取地址) *(取指针值) <-(Go Channel相关运算符)
& 按位与,参与运算的两个数二进制位相与:同时为1,结果为1,否则为0
| 按位或,参与运算的两个数二进制位相或:有一个为1,结果为1,否则为0
^ 按位异或:二进位不同,结果为1,否则为0
<< 按位左移:二进位左移若干位,高位丢弃,低位补0,左移n位其实就是乘以2的n次方
>> 按位右移:二进位右移若干位,右移n位其实就是除以2的n次方
五、函数与闭包
- 支持有名称的返回值;
- 不支持默认值参数;
- 不支持重载;
- 不支持命名函数嵌套,匿名函数可以嵌套;
- Go函数从实参到形参的传递永远是值拷贝,有时函数调用后实参指向的值发生了变化,是因为参数传递的是指针的拷贝,实参是一个指针变量,传递给形参的是这个指针变量的副本,实质上仍然是值拷贝;
- Go函数支持不定参数;
闭包实现累加器
func Accumulate(value int) func() int {
return func() int { // 返回一个闭包
value++
return value
}
}
func main() {
accAdd := Accumulate(1)
fmt.Println(accAdd()) // 2
fmt.Println(accAdd()) // 3
}
六、面向对象
- 封装:用struct实现,通过大小写来控制权限
- 继承:通过嵌套匿名结构体实现继承特性(嵌套有名结构休 实现组合关系)
- 多态:通过interface实现
子类和父类构造函数的实现方法
type Person struct {
Name string
Age int
}
type Student struct {
Person
ClassName string //嵌套匿名结构体
}
//构造父类
func NewPerson(name string, age int) *Person {
return &Person{
Name: name,
Age: age,
}
}
//构造子类
func NewStudent(classname string) *Student {
p := &Student{}
p.ClassName = classname
return p
}
func main() {
s := NewStudent("一班")
fmt.Println(s) // &{{ 0} 一班}
}
七、interface{}与多态
//interface{} 定义方法
type 接口类型名 interface {
方法名1(参数列表) 返回值列表
方法名2(参数列表) 返回值列表
...
}
- 接口实现:接口的方法与实现接口的类型方法格式一致(方法名、参数类型、返回值类型一致)。所有方法都要被实现。
- 接口赋值:接口本质上是一个指针类型。实现了该接口的struct和子接口,可以赋值给接口。
- 接口类型做为参数:如果一个函数有个接口作为参数。那么实现了该接口的struct都可以做为此参数。
- 空接口: Go也能像其它动态语言一样,在数据结构中存储任意类型的数据。
- 接口嵌套:内部属性属于外部属性
类型断言
- 如果不清楚当前struct是什么类型,可以采用类型断言,运行时判断。
package main
func main() {
switch t := areaIntf.(type) {
case *Rectangle:
// do something
case *Triangle:
// do something
default:
// do something
}
}
多态
- 1、多个类型(结构体)可以实现同一个接口。
- 2、一个类型(结构体)可以实现多个接口。
- 3、实现接口的类(结构体)可以赋值给接口。
package main
import "fmt"
type Shaper interface {
Area() float64
}
type Rectangle struct {
length float64
width float64
}
func (r *Rectangle) Area() float64 {
return r.length * r.width // 实现 Shaper 接口中的方法
}
func (r *Rectangle) Set(l float64, w float64) {
r.length = l //Set 是属于 Rectangle 自己的方法
r.width = w
}
type Triangle struct { // ==== Triangle ====
bottom float64
hight float64
}
func (t *Triangle) Area() float64 {
return t.bottom * t.hight / 2
}
func (t *Triangle) Set(b float64, h float64) {
t.bottom = b
t.hight = h
}
func main() {
rect := new(Rectangle)
rect.Set(2, 3)
areaIntf := Shaper(rect) //这种方法只能将指针类型的类示例赋值给接口
fmt.Printf("The rect has area: %f\n", areaIntf.Area())
triangle := new(Triangle)
triangle.Set(2, 3)
areaIntf = Shaper(triangle) //这种方法只能将指针类型的类示例赋值给接口
fmt.Printf("The triangle has area: %f\n", areaIntf.Area())
}
八、文件读取
- 内置flag包获取配置
- config.json文件、ini文件、yaml文件、toml文件
- 万能的viper
1 配置文件读取
package main
//json文件
import "encoding/json"
import "ioutil"
buf, _ := ioutil.ReadFile(path)
myConfig := &MyConfig{} //对应json中的k-v项
_ = json.Unmarshal(buf, myConfig)
// ini文件
import "github.com/go-ini/ini"
myConfig := &MyConfig{} //对应ini中的k-v项
err := ini.MapTo(myConfig, "config.ini")
// 另一种常用来读ini文件:
import "gopkg.in/ini.v1"
cfg,_ := ini.Load(path)
cfg.Section("").Key("app_mode").String() //read
cfg.Section("section_name").Key("port").SetValue("8086") //write
cfg.SaveTo(path)
// yaml文件
import "gopkg.in/yaml.v2"
myConfig := &MyConfig{} //对应ini中的k-v项
file, err := ioutil.ReadFile("config.yaml")
err = yaml.Unmarshal(file, myConfig)
// toml文件
import "github.com/BurntSushi/toml"
myConfig := &MyConfig{} //对应yaml中的k-v项
toml.DecodeFile("config.toml", myConfig)
GO并发
- 并发主要由切换时间片来实现<同时>运行。
- 并行是直接利用多核实现多线程的运行。
- OS线程一般都有固定的栈内存(一般2MB)
- goroutine(可增长的栈)的栈不是固定的,他可以按需增大和缩小(典型大小2KB, 限制可达1GB)
- goroutine 奉行通过
通信来共享内存
(CSP并发模型),而不是共享内存来通信
Goroutine启动一个协程:(如果main协程退出,case协程自动退出)
package main
func case() {
fmt.Println("case Goroutine!")
}
func main() {
go case() // 启动另外一个goroutine去执行case函数
fmt.Println("main协程 done!")
}
使用sync.WaitGroup启动多个协程
package main
import "sync"
import "fmt"
var wg sync.WaitGroup
func hello(i int) {
defer wg.Done() // goroutine结束就登记-1
fmt.Println("Hello Goroutine!", i)
}
func main() {
for i := 0; i < 10; i++ {
wg.Add(1) // 启动一个goroutine就登记+1
go hello(i)
}
wg.Wait() // 等待所有登记的goroutine都结束
}
Channel
- CSP并发模型,提倡通过通信共享内存而不是通过共享内存而实现通信。
- 带缓存的channel和不带缓存的channel<阻塞通道>
- 创建不带缓存的 ch := make(chan interface{})
- 创建带缓存的 ch := make(chan interface{}, num int)
- 只读的 ch := make(<-chan interface{})
- 只写的 ch := make(chan<- interface{})
- 发送 ch <- 10
- 接收 x := <- ch // 从ch中接收值并赋值给变量x
<- ch // 从ch中接收值,忽略结果 - 关闭 close(ch) // 管道不存取时一定要关闭 【close以后还可以读取数据】
- 长度 len(ch) // 求缓冲通道中元素的数量
- 容量 cap(ch) // 求缓冲通道的容量
两者的区别
不带缓冲的通道,发送和接收操作都会阻塞当前协程
带缓冲的通道,进一次长度 +1,出一次长度 -1,如果长度等于缓冲长度时,再进就会阻塞
管道会出现panic地场景
- close以后的管道,再写入。
- 重复close
- 只读的chan不能close, 编译会报错。
- for-range管道,遍历完后,如果chan是关闭的,遍历完数据,正常退出。
- for-range管道,遍历完后,如果chan不是关闭的,遍历完数据,程序会行等待,直到出现死锁。
eg1:采用无缓冲通道进行通信
package main
import "fmt"
func recv(c chan int) {
ret := <-c
fmt.Println("接收成功", ret)
}
func main() {
ch := make(chan int)
go recv(ch) // 启用goroutine从通道接收值
ch <- 10
fmt.Println("发送成功")
}
无缓冲通道上的收发操作都会阻塞,直到另一个goroutine在该通道上执行收发操作,这时值才能发送成功,两个goroutine将继续执行。
相反,如果接收操作先执行,接收方的goroutine将阻塞,直到另一个goroutine在该通道上发送一个值。
使用无缓冲通道进行通信将导致发送和接收的goroutine同步化。因此,无缓冲通道也被称为同步通道。
eg2:按顺序实现输入输出
package main
import "fmt"
func main() {
ch := make(chan int)
go func() {
for i := 0; i < 5; i++ {
ch <- i
fmt.Println("write num is :", i)
}
close(ch) //这里必须,否则实现交替输出后,main程无法退出
}()
for { //主协程只负责读取chan中的数据
if data, ok := <- ch; ok {
fmt.Println("read num is :", data)
} else {
fmt.Println("no data")
break
}
}
}
eg3:交替打印数字和字母
package main
func PrintNums(f chan int, wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 5; i++ {
fmt.Println("num", i)
f <- 1
<- f
}
}
func PrintChars(f chan int, wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 5; i++ {
fmt.Println("char", string('a' + i))
<- f
f <- 1
}
}
func main() {
runtime.GOMAXPROCS(8)
flag := make(chan int)
var wg sync.WaitGroup
wg.Add(2)
go PrintNums(flag, &wg)
go PrintChars(flag, &wg)
wg.Wait()
}
eg4: Time包的定时器功能
package main
import (
"fmt"
"time"
)
func main() {
// 1.获取ticker对象
ticker := time.NewTicker(1 * time.Second)
i := 0
// 子协程
go func() {
for {
//<-ticker.C
i++
fmt.Println(<-ticker.C)
if i == 5 {
//停止
ticker.Stop()
}
}
}()
time.Sleep(6 * time.Second)
}
runtime
- runtime.Gosched() 让出CPU时间片,重新等待调度。
- runtime.Goexit() 退出当前协程
- runtime.GOMAXPROCS(int) (最大256)
调度器使用GOMAXPROCS参数来确定需要使用多少个OS线程来同时执行Go代码。
默认值是机器上的CPU核心数。
例如在一个8核心的机器上,调度器会把Go代码同时调度到8个OS线程上(GOMAXPROCS是m:n调度中的n)。 - goroutine与os线程是M:N的关系