Golang 开发基础技术分享
Summary: Author: 张亚飞 | Read Time: 3 minute read | Published: 2021-07-17
Filed under
—
Categories:
Golang
—
Tags:
Note,
Golang 语言开发基础
Go
(又称 Golang
)是 Google
开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。Go 被誉为是未来的服务器端编程语言。
具有以下特点:
- 部署简单
- 并发性好
- 执行性能好
本次分享定位为Golang开发基础入门技能
Golang 常用命令及包管理工具
- 运行
go run main.go
- 测试
go test main_test.go
- 构建
go build main.go
包管理工具 go mod
go mod 是 Golang官方的包管理工具,其它的还有
glide
go.mod
module go_test
go 1.16
require (
github.com/cenkalti/backoff/v4 v4.1.0 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/fatih/color v1.10.0 // indirect
github.com/gin-contrib/gzip v0.0.3 // indirect
github.com/gin-gonic/gin v1.7.2
github.com/rdegges/go-ipify v0.0.0-20150526035502-2d94a6a86c40
github.com/zyfmix/go_tools v0.0.0-20210716055526-7e7023f46e52
go.uber.org/zap v1.18.1
gorm.io/gorm v1.20.12
)
replace gorm.io/gorm => ../../../Web/Work/gorm
一些指令的特殊含义
- require
- replace
- retract
- indirect
常用命令
go mod tidy
go get -u ./...
go get package@vn
go list ./...
go list -m all
Golang 特殊方法 init
不可以被调用 先于 main
函数执行
执行顺序为: 变量初始化
->init()
->main()
package main
import (
"fmt"
)
var T int64 = ic()
func init() {
fmt.Println("init in main.go")
}
func ic() int64 {
fmt.Println("calling a()")
return 2
}
func main() {
fmt.Println("calling main")
//undefined: init
//init()
}
见
main.go
Golang 基础数据类型
- int8
- float32
- array
- slice
- map
- channel
- func
- string
- array
- slice
- rune …
数组、切片和哈希占用的内存空间都是连续的,编译器会在编译阶段对代码进行优化
循环控制
for: 经典的三段式循环语法
package main
import "fmt"
func main() {
for i := 0; i < 5; i++ {
fmt.Printf("id:%d", i)
}
}
range: 是一个语法糖,最终都会在编译阶段优化为三段式循环,可以循环迭代 array
,slice
,map
,channel
等集合类型
使用 range
循环动态 slice
package main
import "fmt"
func main() {
arr := []int{1, 2, 3}
for _, v := range arr {
arr = append(arr, v)
}
fmt.Println(arr)
}
Golang 功能特性
不支持泛型,表达能力弱,一个方法不同参数要写几套,太过冗余
package tools
func ContainInt(intSlice []int, searchInt int) bool {
for _, value := range intSlice {
if value == searchInt {
return true
}
}
return false
}
package tools
func ContainInt64(intSlice []int64, searchInt int64) bool {
for _, value := range intSlice {
if value == searchInt {
return true
}
}
return false
}
单元测试用法
使用 *_test.go
结尾,单元测试写法
func TestXxx(*testing.T)
使用 Error
、Fail
或相关方法来发出失败信号
运行单元测试
go test -v fibonacci_test.go
go test -v -short fibonacci_test.go
goroutine协程
进程和线程都是由系统cpu进行调度.协程是用户态的轻量级线程,由用户自己调度控制,协程默认占用内存小(2kb),而线程(8mb),协程切换开销远比线程小. 使用 go 关键字就可以开一个协程
package main
import "fmt"
func main() {
go func() {
fmt.Println("----------")
}()
}
channel通道
协程消息通讯方式,阻塞式的
ch <- v // 发送值v到Channel ch中
v := <-ch // 从Channel ch中接收数据,并将数据赋值给v
示例:
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int, 0)
go func(ch chan int) {
for {
select {
case i := <-ch:
fmt.Println("receive ", i)
time.Sleep(time.Second)
case <-time.After(1000 * time.Millisecond):
fmt.Println("receive timeout", time.Now().Unix())
}
}
}(ch)
ch <- 1
time.Sleep(5 * time.Second)
}
使用 _,ok
判断 channel
是否关闭
可以增加一个返回值判断通道是否关闭或是否为空值
x, ok := <-ch
使用 range
从单个 channel
读取数据
package main
import "fmt"
func main() {
fc := make(chan int, 10)
fc <- 1
for i := range fc {
fmt.Println(i)
}
}
使用for-range读取channel,这样既安全又便利,当channel关闭时,for循环会自动退出
使用 select
从多个 channel
读取数据
package main
import (
"context"
"time"
"fmt"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
foo := make(chan int, 10)
go func() {
select {
case <-ctx.Done():
fmt.Printf("select -- ctx.done,at: %d", time.Now().Unix())
case fv, ok := <-foo:
fmt.Printf("select -- fv:%d,ok: %v,at: %d", fv, ok, time.Now().Unix())
case <-time.After(time.Second):
fmt.Printf("select -- timeout,at: %d\n", time.Now().Unix())
}
}()
time.Sleep(3 * time.Second)
cancel()
}
注意:使用
select
从多个channel
读取信息时,没有读取优先级,如果通道都有消息读取的顺序是随机的
实现一个非阻塞读的方法 并发控制或限流: 实现一个简易的令牌桶
见 main.go
reflect反射
通过反射,可以拿到运行时对象的信息,包括类型、值、方法等信息
package main
import (
"fmt"
"reflect"
)
func main() {
caches := make(map[string]interface{}, 0)
caches["data_ids"] = 1.3
cacheTypes := reflect.TypeOf(caches["data_ids"])
cacheVal := reflect.ValueOf(caches["data_ids"])
fmt.Printf("cacheTypes kind is %+v\n", cacheTypes.Kind())
fmt.Printf("cacheVal type is: %+v\n", cacheVal.Type())
fmt.Printf("cacheVal kind is: %v\n", cacheVal.Kind() == reflect.Float64)
fmt.Printf("cacheVal value is: %f\n", cacheVal.Float())
fmt.Printf("cacheVal value is: %s\n", cacheVal.String())
}
panic异常恢复
panic 可以中断程序的运行,但是可以通过 recover() 异常恢复,相当于 try,catch…
package main
import (
"fmt"
"strconv"
)
func main() {
cache, err := Parse("r3")
if err != nil {
fmt.Printf("err:%#v\n", err)
}
fmt.Println(fmt.Printf("cache:%v", cache))
}
type Cache struct {
id uint64
}
func Parse(input string) (s Cache, err error) {
defer func() {
if p := recover(); p != nil {
err = fmt.Errorf("internal error: %v", p)
}
}()
id, err := strconv.ParseUint(input, 10, 64)
if err != nil {
panic(err)
}
return Cache{
id: id,
}, nil
}
Json 库
Json
序列化非标准格式,如何在解析Json
时让int
字段类型兼容float
数据格式,而序列化后统一为标准int
格式
type MUIntType struct {
val uint
}
func (m *MUIntType) UnmarshalJSON(value []byte) error {
var raw interface{}
err := json.Unmarshal(value, &raw)
if err != nil {
fmt.Printf("[err]err:%s\n", err)
return err
}
switch raw.(type) {
case string:
fmt.Printf("[#][string]:%s\n", raw)
return nil
case float64:
fmt.Printf("[#][float64]:%s\n", raw)
return nil
default:
fmt.Printf("[#][UnknownType]:%T\n", raw)
break
}
return json.Unmarshal(value, &m.val)
}
func (m MUIntType) MarshalJSON() ([]byte, error) {
return json.Marshal(m.val)
}
见
tests/data/data_test.go
- 使用 Json 处理解析时间及空值处理
package main
import (
"time"
"fmt"
)
var TimeLayout = "2006-01-02 15:04:05"
type JsonTime time.Time
func (jt JsonTime) MarshalJSON() ([]byte, error) {
var stamp = fmt.Sprintf("\"%s\"", time.Time(jt).Format(TimeLayout))
return []byte(stamp), nil
}
见
tests/data/data_test.go
Golang 遇到的坑
for-range
循环指针重复问题
package main
import "fmt"
func main() {
slices := []int{1, 2, 3, 4, 5, 6}
pointers := make([]*int, 0)
fmt.Println()
for _, sv := range slices {
fmt.Print(sv, ",")
pointers = append(pointers, &sv)
}
fmt.Println()
for _, pv := range pointers {
fmt.Print(*pv, ",")
}
fmt.Println()
}
见
tests/range/range_test.go
解析Json
时使用 json.RawMessage
定义原始类型
循环引用问题
good => order; order => good import cycle not allowed
见
runs/cycle/main.go
模块化设计,让开发者考虑不同模块的功能划分与解耦 禁止循环引入会让编译变的更高效
- 解决方案
尽量不要划分很多独立相互依赖的包,扁平化
使用独立的 interface
声明
功能结构划分清晰:
route,controllers,handlers,middlewares,libs
比较好的学习资料
其它
runtime运行时
context上下文
内存泄漏问题
内存逃逸分析
Golang 其它服务
GRPC
CGO编程
interface接口
实现 fmt.Stringer 接口的类型都可以自动自动调用该方法
package fmt
type Stringer interface {
String() string
}
Comments