2023年6月Android开发转GO学习

76 阅读9分钟

毕业后长期在广州工作,从事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{

}