毕业后长期在广州工作,从事Android/Flutter应用工作,对当前应用开发市场的不确定性,同事部分项目涉及GO开发,带着兴趣,先自学这门语言,预备一门手艺,别饿着了。
我觉得语言这东西,就和英语26个字母一样,从简单的入门语法开发,先学会用轮子,再造轮子。
以下内容适合初学者,记录自己学习的过程,都是八股文干货,不带一句废话。准备面试的可以花一分钟快速阅读,拾忆。如果大佬们有推荐的一些资料可以私聊我,也希望能通过我们这代人将技术传播给其他需要的人。
常用指令
go run main.go go mod init 生成go.mod文件 go build main.go 当前目录生成可执行文件main,./main执行 go install main.go安装到$GOBIN目录,终端输入main执行
编辑器:Visual studio code +GO扩展插件或GOland var 变量名 类型 = 表达式 fmt.println("")
var ( j int =1 k int =2 ) 或 i:=10变量声明
const name = ""常量
依次递增的变量1,2,3 const( one=iota+1 two three )
强类型语言:不同类型的变量无法相互使用和计算 int->string i2s:=strconv.Itoa(i) s2i,err:=strconv.Atoi(i2s) 数字类型之间的转换使用强制转换 i2f:=float64(i) f2i:=int(f64)
string类型s 的前缀是否是H (strings.Hasprefix(s,"H")) s中查找字符串o(strings.Index(s,"o")) s转大写 (strings.ToUpper(s))
if i>10 { fmt.println("i>10") }else{
}
if i:=6; i>10 { fmt.println("i>10") }else{
}
switch i:=6;{ case i>10: fmt.println("i>10") case i>5: fallthrough default: fmt.priintln("") }
for i:=1;i<=100;i++ { sum+=i }
for支持continue、break 等价while sum:=0 i:=1 for i<100{ sum+=i i++ }
array存放固定长度、相同类型、连续存放的数据 array:=[5]string{"a","b","c","d","e"} array:=[...]string{"a","b","c","d","e"} array:=[5]string{1:"b",3:"d"} 初始化索引1的值为b,3的值为d
循环遍历 for i,v:=range array{ fmt.println("索引%d,值%s\n",i,v) }
动态数组slice
slice:=array[2:5]索引2-5之间的元素,不包括5 array[:]等价于array[0:5]
注意改变slice的值,array的值也会同步改变 slice1:=make([]string,4) slice1:=make([]string,4,8)容量8,使用append追加元素超出后,会自动扩容 slice1:=[]string{"a","b","c","d","e"}
Map是无序的,K-V键值对集合,K的类型相同、V的类型相同 nameMap:=make(map[string]int) nameMap["haha"] = 123 等价 nameMap:=map[string]int{"haha":123}
age,ok:=nameMap["haha"] if ok { fmt.Println(age) }
delete(nameMap,"haha")
遍历Map for k,v:=range nameMap{ fmt.Println("Key is",k,"Value is",v) }
len(nameMap)
string和[]byte 注意:一个汉字对应3个字节 s:="haha" bs:=[]byte(s)字节切片 fmt.Println(s[0],s[1],s[2])
二维数组
aa:=[3][3]int{}
函数多值返回 func sum(a,b int) (int,error){ if a<0 ||b<0{ return 0,errors.New("") } return a+b,nil }
返回值命名 func sun(a,b int) (sum int,err error){ sum = a+b err=nil return }
可变参数(参数数量可变) func sum1(params ...int) int{ sum :=0 for _,i := rang params { sum+=i } return sum }
函数名字首字母小写代表私有函数,只有在同一个包中才能被调用 函数名字首字母大写代表公有函数,在不同包也能被调用
匿名函数 func main(){ sum2 := func(a,b int) it{ return a+b }
//调用匿名函数 fmt.Println(sum2(1,2)) }
闭包(在函数内再定义函数,此函数可以使用外部函数的变量) func main(){ cl:=colsure() fmt.Println(cl()) fmt.Println(cl()) }
//闭包然会一个匿名函数,该匿名函数拥有外部函数colsure的变量i func colsure() func() int { i:=0 //匿名函数 return func() int{ i++ return i } }
值类型接收者 type重命名 type Age uint表示定义一个新类型等价于uint func (age Age) string(){ fmt.Println("") }
//这样Age和String就绑定一起,String是Age的方法 func main(){ age:=Age(25) age.String() }
指针接收者 func (age *Age) Modify(){ *age = Age(30) } //bad age:=Age(25) age.String() //good age.modify() age.String() ///good (&age).Modify()
结构体struct type persion struct{ name String age uint }
var p persion p:=persion{"haha",20} fmt.Println(p.name,p.age)
面向接口的编程
接口 type Stringer interafce{ String() string } //接口实现 func (p persion) String() string{
}
工厂函数,用于创建自定义的结构体 func NewPerson(name string) *person{ return &person{name:name} } //使用工厂函数 p:=NewPerson("haha")
工厂函数创建接口,好处是可以隐藏内部具体的实现,让调用者只需要关注接口的使用即可 以error.New为例 errors/errors.go //工厂函数,返回一个error接口,具体实现是*errorString func New(text string) error{ return &errorString{text} } //结构体,内部有一个字段s,存储错误信息 type errorString struct{ s string } //用于实现error接口 func (e *errorString) Error() string{ return e.s }
类型的组合
1、结构体的组合 type Reader interface{ Read(p []byte) (n int,err error) } type Writer interface{ Write(p []byte)(n int,err error) } typr ReadWriter interface{ Reader Writer }
类型断言 a:=s.(person)如果s是person类型,正常返回a,反之程序中止 //类型断言的多值返回 a,ok:=s.(*person) if ok{ }else{ }
type error interface{ Error() string } 自定义error type commonError struct{ errorCode int errorMsg string }
func (ce *commonError) Error() string{ return ce.errorMsg }
使用 return 0,&commonError{ errorCode:1 errorMsg:"" }
error断言 sum,err := add(-1,2) if cm,ok:=err.(*commonError); ok{ fmt.Println(cm.errorCode) }else{
}
defer用于修饰一个函数或方法,使得该函数或者方法在返回前执行,也就是被延迟,但又保证一定会执行 用于成对的操作,文件的打开关闭,加锁和释放锁,连接的建立和断开 func ReadFile(fileName string) ([]byte,error){ f,err := os.Open(fileName) if err != nil{ return nil,err } defer f.Close()
return readAll(f,n) } 先执行readAll(f,n),在整个ReadFile返回前执行f.close
go是静态的强类型语言,很多问题都能在编译时捕获,其他需要在运行时检查,比如数组越界访问,不相同的类型转换等,导致panic异常 主动抛出panic异常,例如 连接MySql数据库, func connectMySQL(ip,userName,password string){ if ip=="" { panic("ip 不能为空") } } ip为空直接抛出panic异常
interface{}是空接口的意思,在go语言代表任意类型
recover在程序崩溃前做一些资源的释放,通过defer+匿名函数+recover func main(){ defer func(){ if p:=recover();p!=nil{ fmt.Println(p) } }
//panic connectMySQL("","root","123456") }
多个defer语句按照后进先出原则
启动goroutine
go function() 管道:协程之间通信的方式 ch:=make(chan string)只能发送string的无缓冲管道,同步管道 ch:=make(chan string,5)只能发送string的有缓冲管道,容量为5 onlySend:=make(chan<-int)单向管道 onlyReceive:=make(<-chan int)
关闭管道close(ch)
select多路复用,在多个channel中,任意一个channel有数据产生,select都可以监听到,然后执行相应分支 select{ case i1=<-c1: //todo case c2<-i2: //todo default: //todo }
互斥锁:同一时间内只能又一个goroutine(协程)执行,其他协程要等其完成后才能执行 var( sum int mutex sync.Mutex }
func add(i int){ mutex.Lock() defer mutex.UnLock() sum+=i }
读写锁(可以设置允许同时读数据,或者同时写数据) var mutex sync.RWMutex func readSum() int{ //只获取读锁 mutex .RLock() defer mutex.RUnlock() b:= sum return b }
time.sleep(2 *time.Second)
sync.waitGroup(可以更好额跟踪协程,在协程完成后,整个方法才能执行完毕) func run(){ var wg sync.WaitGroup //因为要监控110个协程,设置计数器110 wg.add(110) for i:=0;ii<100;i++{ go func(){ defer wg.Done()//计数器减一 add(10) }() }
for i:=0;i<10;i++{ go func(){ defer wg.Done() fmt.Println("") }() }
wg.wait() }
sync.Once(代码只执行一次) func doOnce(){ var once sync.Once onceBody:=func(){ fmt.Println("only once") }
done:=make(chan bool) for i:=0;i<10;i++{ go func(){ //把要执行的函数或方法 once.Do(onceBody) done <-true }() }
for i:=0;i<10;i++{ <-done } }
sync.Cond 阻塞和唤醒协程 cond:=sync.NewCond(&sync.Mutex) cond.L.Lock() cond.wait() cond.L.UnLock()
cond.Broadcast()//发号枪响,阻塞的所有协程一起启动 cond.Signal()//唤醒等待时间最长的一个协程
context使用 func main(){ var wg sync.WaitGroup wg.add(1)
ctx,stop:=context.WithCancel(context.Background)
go func(){ defer wg.Dones() watchDog(ctx,"haha") }()
time.Sleep(5*time.Second) stop() wg.Wait() }
func watchDog(ctx context.Context,name string){ //开启for select循环,一直在后台监控 for{ select { case<-ctx.Done(): fmt.Println(name,"停止") return }
default: fmt.Println(name,"正在监控") }
time.Sleep(1*time.Second) }
context可以控制取消多个协程,是一个接口,他具备手动,定时,超时发出取消信号、传值等功能,主要是哦内阁制多个协程之间的合作,尤其是取消操作 type Context interface{ Deadline()(deadline time.Time,ok bool)//返回设置的截至时间,ok:是否设置乐截至时间 Done()<-chan struct{} //返回一个只读的channel ,收到Done()方法后,可以做清理操作、然后退出协程,释放资源 Err() error //返回取消的错误原因 Value(key interface{}) interface{}//获取Context绑定的值,是一个键值对,所以要同通过一个key才能获取对应值 }
创建Context 树 WithCancel(parent Context) WithDeadline(parent Context,d time.Time)//定时关闭 WithTimeOut(parent Context,timeout time.Duration) WithValue(parent Context,key ,val interface{})//生成一个可以携带key-value键值对的Context
Context 传值 func main(){ wg.add(4) valCtx:=context.WithValue(ctx,"userId",2) go func(){ defer wg.Done() getuser(valXtx) }()
}
func getUser(ctx context.Context){ for { select{ case<-ctx.Done(): return default: userId:=ctx.Value("userId") time.Sleep(1*time.Second) } } }
for-select多路复用,一般和channel完成组合模式 1、监听狗无限循环模式,只有收到终止指令才会退出 2、for range select有限循环模式,用于把迭代的内容发到channel上
select-timeOut模式,设置网络请求超时时间 result:=make(chan string) go func(){ //模拟网络请求 time.Sleep(8*time.Second) result <-"服务端无结果" }()
select { case v:=<-result: fmt.Println(v) case <-time.After(5*time.Second) fmt.Println("网络访问超时") }
流水线模式Pipline
指针 var intP *int intp:=&name
var intp:=new(int)//返回*int
new 只用于分配内存,并且把内存清零,也就是返回一个指向对应类型零值的指针,new 一般用于需要显式的返回指针 make只用于slice、chan、map
运行时反射
reflect.valueOf reflect.TypeOf
单元测试
创建cha20/main.go //单元测试 go test -v ./ch20 //单元测试覆盖率 go test -v --coverprofile=ch20.cover ./ch20 //查看覆盖率报告 go tool cover -html=ch20.cover -o=ch20.html
静态代码检查 golangci-liint的安装,代码分析工具
逃逸分析
go build -gcflags="-m -1" ./ch20/main.go
-m表示打印出逃逸分析信息,-1表示禁止内联,可以更好的观察逃逸
优化技巧 1、避免逃逸以,栈内存效率更高,还不用GC,小对象的传参,array比slice效果好 2、如果无法避免逃逸,还是在堆上分配了内存,对于频繁的内存申请操作,要学会重用内存。比如用sync.Pool 3、选用合适的算法,达到高性能的目的,比如用空间换时间 避免使用锁,并发加锁的范围尽可能小,使用StringBuilder做string和[]byte之间的转换,defer嵌套不要太多
go自带的性能剖析工具 pprof
go代理 go env -w GO111MODULE=on go env -w GOPROXY=goproxy.io,direct
泛型
func min[V int|float32|float64](a ,b V) V{ if a<b { return a }else{ return b } }
使用 min(2,3) min(1.2,2.3) minInt(2,3) minFloat(1.2,2.3) minint minfloat64
自定义约束类型 type Number interface{ int|float32|float64 }
func min[V Number](a,b V) V{
}