Go快速入门
一、环境安装
1、源码包下载
https://golang.org/dl/
https://golang.google.cn/dl/
https://studygolang.com/dl/
2、下载解压至/usr/local
tar -zxvf go1.14.4.linux-amd64.tar.gz -c /usr/local
3、cd /usr/local/go
src 源码
bin go指令 gofmt指令
4、配置bin到环境变量
vim ~/.bashrc
export GOROOT=/usr/local/go goRoot,表示go源码包所在位置
export GOPATH=$HOME/go goPath,写go代码的工作路径
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
source ~/.bashrc
5、验证
go version
二、语言特性
1、极简的部署
ldd 程序名 查看依赖关系
2、静态类型语言
编译期间能检测出隐藏的大多数问题,动态的比如shell
3、语言层面的并发
4、强大的标准库
runtime系统调度机制:用于垃圾回收,go调度的平均分配
gc垃圾回收:1.8后增加三色标记以及混合写屏障
丰富的标准库
三、语法新奇
1、main函数
package main //程序的包名,当前文件一定包含main函数 import ( "fmt" "time" ) func main(){ fmt.Println("hello") time.Sleep(1*time.Second) }
2、数据类型
//布尔型 false %t var isFlag bool fmt.Printf("%T,%t\n",isFlag,isFlag) // 数值型 整形 0 %d uint8 0~255 别名byte uint16 rune uint32 uint64 int8 -127~128 int16 int32 int64 int //数值型 浮点型 0.0 %f 默认6位小数,%.2f 保留两位小数 float32 IEEE-754 32位浮点数 float64 IEEE-754 64位浮点数 complex64 32位实数和虚数 complex128 64位实数和虚数 //字符串 string 空 %s 双引号 单引号会转为ASCⅡ编码表,unicode编码表,全世界文字,gbk全中国 转义字符 \
类型转换
a:=3 //int b:=5.0 //float64 //将int类型的a转换为float64类型 c:=float64(a)
整形不能转为布尔
3、运算符
算术运算符
+、-、*、/、%、++(自增)、--
关系运算符
==、>、<、>=、<=、!=
逻辑运算符
&& 、||、!
位运算符
& 位与,都是1,结果位1
| 位或,都是0,结果为0
^ 位异或,不同为1
&^ 位清空,a&^b,对于b上数值,如果为0,则取a对应位数值,为1,取0
<< 左移,高位丢弃,低位补0,乘以2的n次方
>> 右移,地位丢弃,高位补0,除以2的n次方
4、 变量
package main import{ "fmt" } //声明全局变量 方法一二三是可以的,方法四不行 var gA int =100 func main(){ //方法一 声明一个变量 默认是0 var a int //方法二 初始化 var b int =100 //方法三 var c=100 //方法四 d:=100 //打印类型%T fmt.Printf("%T",d) //声明多个变量 var xx,yy int =100,200 var kk,ll =100,"abc" var( vv int =10 jj bool =true ) }
package main import "fmt" func main(){ // := 自动推导 name := "zhangsan" var age int age=18 //%T表示打印类型 fmt.Println("%T",name) //%p 表示打印地址 fmt.Println("age:%d,内存地址:%p",age,&age) age=20 //地址不会变 }
变量交换
package main func main(){ var a int=100 var b int =200 //交换a,b的值 b,a=a,b }
匿名变量:_ 空白标识符,任何赋给这个标识符的值将被抛弃
package main import "fmt" func test()(int ,int){ return 100,200 } func main(){ a,_:=test() _,b:=test() fmt.Println(a,b) } //匿名变量不占用内存空间,不会分配内存
5、常量
package main import ( "fmt" ) //const来定义枚举类型 const ( //可以在const()添加一个关键字iota,每行iota都累加1,第一行0 //Beijing=0 //Shanghai=1 //SHENZHEN=2 BEIJING=iota //0 如果10*iota,分别是 0,10,20 SHANGHAI //1 SHENZHEN //2 ) const( a,b=iota+1,iota+2 //iota=0 a=1,b=2 c,d //iota=1 c:iota+1=2,d:ioata+2=3 e,f //iota=2,e=3,f=4 g,h=iota*2,iota*3 //iota=3,g=6,h=9 i,k //iota=4,i=8,h=12 ) func main(){ //常量 (只读) const length int =10 }
iota只有在const中使用
6、流程控制
var a int=90 switch a{ case 90: fmt.Println("A") case 80: fmt.Println("B") case 50,60,70: fmt.Println("C") default: fmt.Println("D") } switch默认匹配成功后不会执行其他case,如果需要执行其他case,可以使用fallthrough穿透case,break结束穿透
7、String
str:="hello" //获取长度 len(str) //获取指定字节 fmt.Pirntln(str[0])//104 fmt.Printf("%c",str[0])//h //字符串是一个个字符拼接而成 //for range循环 循环遍历数组、切片.... for i,v :=range str{ fmt.Print(i) //下标 fmt.Printf("%c",v)//值 }
8、函数
package main import ( "fmt" ) func fool1(a string,b int) (int,int){ c:=100 d:=200 return c } func fool2(a string b int)(r1 ,r2 int){ r1=100 r2=200 return } func main(){ }
func main(){ sum:=getSum(1,2,3,4) } func getSum(nums...int)(int){ sum:=0 for i:=0;i<len(nums);i++{ sum+=nums[i] } return sum } /* 注意: 可变参数要放在列表最后 一个函数参数列表中最多一个可变参数 */
func main(){ fmt.Printf("%T",f1) //func(int int) var f2 func(int,int) f2=f1 f2(1,2) } func f1(a,b int){}
9、值传递与引用传递
值传递:传递的是数据的副本,修改数据对原数据没影响
基础类型、array、struct...
引用传递:传递的是地址,修改数据对原数据有影响
切片:可以扩容的数据
map、chan...
10、import导包与init函数
init()函数调用早于main()函数
函数名大写表示其他包也可以调,小写其他包无法调用
11、import匿名以及别名导包方式
package main //直接导pak1不行,需要到gopath路径下 import ( _"Golang/demo/pak1" Golang/demo/pak2" mylib "Golang/demo/pak3" ."Golang/demo/pak4" ) //下画线表示给pak1起匿名别名,这样即使不使用pak1的方法,也不会报错 //mylib是显式别名,可以通过其调用 //加. 将当前pak4包中全部方法导入当前main包,方法可以直接使用,但容易发生歧义 func main(){ //pak1.Pak1funcTest() pak2.Pak2funcTest() mylib.Pak3funcTest() Pak4funcTest() }
12、指针
package main import "fmt" func swap (pa *int,pb *int){ var temp int temp=*pa *pa=*pb *pb=temp } func main(){ var a int=20 var p * int p=&a fmt.Println(&a) fmt.Println(p) var pp **int //二级指针 pp=&p fmt.Println(&p) fmt.Println(pp) }
13、 defer
package main import "fmt" func main(){ defer fmt.Println("main end1") defer fmt.Println("main end2")//压栈执行 fmt.Println("main hello") } //defer相当于c++析构,函数要结束后执行 //defer与return,return先执行
return先于defer执行,defer相当于析构
14、数组
package main func printArr(myArr [4]int){ //长度4和10是两个不同类型的数组,只能传长度为4的 } func main(){ var myArr1 [10]int myArr2:=[10]int{1,2,3,4} myArr3:=[4]int{1,2,3,4} for i:=0;i<len(myArr1);i++{ fmt.Println(myArr1[i]) } for idx,value:=range myArr2{ fmt.Println(idx,value) } //值拷贝 printArr(myArr3) }
package main import "fmt" func printArr(myArr []int){ } func main(){ myArr:=[]int{1,2,3,4}//动态数组,切片 //切片是引用类型,传递的是指针 printArr(myArr) }
15、slice(动态数组)
package main import "fmt" func main(){ //声明slice是一个切片,并且初始化,默认值是1,2,3,长度3 slice1:=[]int{1,2,3} //声明slice是一个切片,但是并没有分配空间,如果尝试访问元素1,会越界错误 var slice2 []int //开辟空间 slice2=make([]int,3) var slice3 []int=make([]int,3) slice4 := make([]int,3) //初始化切片slice5是数组arr的引用 slice5:=arr[:] //将arr左闭右开创建一个新的切片 slice5:=arr[startIndex:endIndex] slice6:=arr[startIndex:] slice7:=arr[:endIndex] //判断slice是否为空 if slice1==nil{ } }
package main func main(){ //长度为3,容量为5 var numbers=make([]int,3,5) fmt.Printf("len=%d,cap=%d,slice=%v",len(numbers),cap(numbers),numbers) //添加 numbers=append(numbers,1) //向容量已经满的切片继续新增,go底层会默认扩容为原来cap容量 //截取 这种截取s,s1,s2,s3底层共享内存空间 s:=[]int{1,2,3,4,5,6} //len=6,cap=6 //左闭右开 [0,2) s1:=s[0:2]//结果是1,2 s2:=s[:3]//结果是1,2,3 s3:=s[0:]//全部 //如果想分开拷贝,深拷贝(拷贝一个副本) s4:=make([]int,3) copy(s4,s) }
切片长度和容量不同,长度表示左指针至右指针的距离,容量表示左指针至底层数组末尾的距离
扩容会扩容为原来cap的两倍
16、map
package main func main(){ //map[k]v var myMap map[string]string //myMap==nil //开辟空间 myMap=make(map[string]string,10) myMap["one"]="java" myMap["two"]="c++" //满了会扩容 //可以写容量,也可以不写 myMap2:=make(map[int]string) myMap3:=map[string]string{ "one":"go", } }
package main func printMap(myMap map[string]string){ //myMap是引用传递 slice map channal } func main(){ myMap:=make(map[string]string) //添加 myMap["china"]="beijing" myMap["usa"]="huashengdun" //遍历 for k,v:=range myMap{ } //删除 delete(myMap,"china") //修改 myMap["usa"]="niu" }
17、面向对象--封装
package main //type声明一种新的数据类型myint,是int的一个别名 type myint int //定义一个结构体,把多种基本类型组合到一起 type Book struct{ title string auth string } func printBook1(book Book){ //值传递 传递一个副本 } func printBook2(book *Book){ //引用传递 book.auth="678" } func main(){ var a myint=10 var book1 Book book1.title="123" book1.auth="456" fmt.Printf("%v",book1) printBook2(&book1) }
package main type Hero struct{ Name string age int } func (this Hero) Show{ fmt.Println("name=",this.name) fmt.Println("age=",this.age) } func main(){ hero:=Hero{Name:"zhangsan",age:19} hero.show() }
package main //类名首字母大写,表示其他包也能访问 type Hero struct{ //类的属性首字母大写,表示该属性是对外能够访问的 Name string age int } //方法名大写,其他包也能访问 func (this *Hero) Show{ fmt.Println("name=",this.name) fmt.Println("age=",this.age) } func(this *Hero)GetName()string{ return this.Name } func(this *Hero)SetName(name string){ this.name=name } func main(){ hero:=Hero{Name:"zhangsan",age:19} hero.setName("lisi") hero.show() }
18、面向对象--继承
package main type Human struct{ name string sex string } func (this *Human)Eat(){ fmt.Println("Eat") } func (this *Human)Walk(){ fmt.Println("Walk") } //继承 type SuperMan struct{ Human //SuperMan类继承了Human类的方法 level int } //重写父类方法 func (this *SuperMan)Eat(){ fmt.Println("SuperMan:Eat") } //子类新方法 func (this *SuperMan)Fly(){ fmt.Println("Fly") } func main(){ h:=Human{"zhangsan","female"} h.Eat() //写法1 s:=SuperMan{ Human{ "lisi", "male" }, 88 } s.Eat() //写法2 var sm SuperMan sm.name="wangwu" sm.level=99 }
父类的类名写到子类的结构中
19、面向对象--多态
package main //interface本质是一个指针,指向当前interface所指向的具体类型 type Animal interface{ Sleep() GetColor() string //获取动物颜色 GetType() string //获取动物种类 } //具体的类 type Cat struct{ color string } func (this *Cat)Sleep(){ } func (this *Cat)GetColor() string{ return this.color } func (this *Cat)GetType() string{ return "Cat" } func main(){ var animal Animal//接口的数据类型,父类指针 animal=&Cat{"Green"} animal.Sleep() }
基本的要素:
有一个父类(接口)
有子类,实现了父类的全部接口方法
父类的指针指向子类
20、interface
interface 通用万能类型
package main func myFunc(arg interface{}){ fmt.Println(arg) //interface{} 如何区分此时引用的底层数据类型是什么 //给interface{} 提供"断言"的机制 //判断是否是string value,ok:=arg.(string) } type Book struct{ auth string } func main(){ book:=Book{"Go"} myFunc(book) }
21、断言
package main func main(){ var a string //a中pair <static type :string,value:"abc"> a="abc" //allTpe中pair <type:,value:> var allType interface{} //allTpe中pair <type:string,value:"abc"> allType=a //allTpye此时可以断言成string str,ok:=allTpye.(string) } func demo1() error{ //tty: pair<type:*os.File,value:"/dev/path"文件描述符> tty,err:=os.OpenFile("/dev/path",os.0_RDWR,0) if err!=nil{ return err } //r: pair<type:,value:> var r io.Reader //r: pair<type:*os.File,value:"/dev/path"文件描述符> r=tty //w: pair<type:,value:> var w io.Writer //w:pair<type:*os.File,value:"/dev/path"文件描述符> w=r.(io.Writer) } type Reader interface{ ReadBook() } type Writer interface{ WriteBook() } type Book struct{ } func (this *Book) ReadBook(){ } func (this *Book) WriteBook(){ } func demo2(){ // b:pair<type:Book,value:Book{}地址> b:=&Book{} var r Reader // r:pair<type:Book,value:Book{}地址> r=b r.ReadBook() var w Writer //r能断言为(Writer),因为w r具体的type是一致的,Book实现了这两个接口 w=r.(Writer) w.WriteBook() }
变量的内部构造有两部分(合起来称为pair):type与value。type(类型)分为static type(静态类型)与concrete type(具体类型)。static type 例如int,string等。concrete type指interface所指向的具体数据类型
22、反射
func ValueOf (i interface{}) Value{...} 0
func TypeOf(i interface{}) Type{...} nil
package main import( "reflect" ) func reflectNum(arg interface{}){ fmt.Println("type:",reflect.TypeOf(arg)) fmt.Println("value:",reflect.ValueOf(arg)) } func main(){ var num float64=31415 reflectNum(num) }
package main type User struct{ Id int Name string } func (this User) Call(){ } func main(){ user:=User{1,"abc"} } func Do(input interface{}){ //获取input的type inputType:=reflect.TypeOf(input) fmt.Println(inputType.Name()) //获取value inputValue:=reflect.ValueOf(input) fmt.Println(inputValue) //通过type获取里面的字段 /* 1、获取interface的reflect.Type ,通过Type得到NumField,进行遍历 2、得到每个field,数据类型 3、通过filed有一个Interface()方法得到对应value */ for i:=0;i<inputType.NumField();i++{ field:=inputType.Field(i) value:=inputType.Field(i).Interface() fmt.Printf("%s %v=%v\n",field.Name,field.Type,value) //Id int 1 } //通过type调用里面方法 for i:=0;i<inputType.NumMethod();i++{ m:=inputType.Method(i) //m.Name Call m.Type func(main.User) } }
23、结构体标签
package main type resume struct{ Name string `info:"name" doc:"myName"` Sex string `info:"sex"` } func findTag(str interface{}){ t:=reflect.TypeOf(str).Elem() for i:=0;i<t.NumField();i++{ tagString :=t.Field(i).Tag.Get("info") fmt.Println(tagString) } } func main(){ var re resume findTag(&re) }
package main import( "encoding/json" ) type Movie struct{ Title string `json:"title"` Year int `json:"year"` Actors []string `json:"actors"` } func main(){ movie:=Movie{"戏剧",2002,[]string{"李","刘"}} //编码的过程 结构体---->json jsonStr,err:=json.Marshal(movie) //解码 json---->结构体 myMovie:=Movie{} err = json.Unmarshal(jsonStr,&myMovie) }
四、goroutine与channel
1、goroutine
单进程操作系统的问题:
单一执行流程、计算机只能一个任务一个任务处理
进程阻塞所带来的cpu浪费时间
多进程带来的问题
多进程/线程解决了单进程/线程的阻塞问题,但是
进程/线程数量越多,切换成本越大
多进程伴随着同步竞争(锁、竞争资源冲突等)
调度器的设计策略
复用线程
work stealing机制 :如果存在空闲的本地队列,会偷取其他队列的G来执行
hand off机制:如果G1阻塞,会立即唤醒另一个线程,将G1所关联的P迁移到另一个线程来执行,G1阻塞结束,如果还要继续执行会加入其他队列中,不执行会睡眠或销毁M1
利用并行
可以利用GOMAXPROCS限定P的个数
比如p=cpu核数/2,利用一半cpu
抢占
G时间到了抢占执行
全局G队列
优先从其他队列偷,在从全局队列获取
2、创建goroutine
package main import( "fmt" "time" ) //子goroutine func newTask(){ i:=0 for { i++ fmt.Println("从goroutine:",i) time.Sleep(1*time.Second) } } //主goroutine func main(){ //创建一个go协程 去执行newTask() go newTask() //主协程退出,从协程也会退出 i:=0 for{ i++ fmt.Println("主goroutine:",i) time.Sleep(1*time.Second) } }
package main func main(){ //用go创建承载一个形参为空,返回值为空的一个匿名函数 go func(){ defer fmt.Println("匿名函数A defer") func(){ defer fmt.Println("匿名函数B defer") //退出当前协程 runtime.Goexit() fmt.Println("B") }() //加()表示调用此匿名函数 fmt.Println("A") }() //保证父协程不退出 for{ time.Sleep(1*time.Second) } } //正常执行:B--B defer--A--A defer //如果想退出当前子协程 runtime.Goexit() B defer--A defer //return会退出当前方法
3、channel
package main func main(){ //定义一个无缓冲的channel c:=make(chan int) go func(){ defer fmt.Println("goroutine结束") fmt.Println("goroutine运行") //将10写入channel c<-10 }() a:=<-c fmt.Println(a) }
make (chan Type)
make (chan Type,capacity)
channel <- value 发送value到channel
<-channel 接收并丢弃
x:=<-channel 从channel中读取数据,并赋值给x
x,ok:=<-channel
当主线程先执行到读取chan,发现还没有写入,会阻塞直到xieru
当从线程先执行到写入chan,由于没有缓冲,会阻塞直到被读取
4、channel有缓冲和无缓冲同步问题
package main func main(){ c:=make(chan int,3)//有缓冲的channel //len元素的数量,cap容量 fmt.Println(len(c),cap(c)) go func(){ for i:=0;i<4;i++{ c<-i fmt.Println(len(c),cap(c)) } }() for i:=0;i<3;i++{ num:=<-c } }
当channel已经满,再向里面写数据,就会阻塞
当channel为空,从里面取数据会阻塞
5、channel关闭
package main func main(){ c:=make(chan int) go func(){ for i:=0;i<5;i++{ c<-i } //close可以关闭一个channel close(c) }() for { //ok如果为true表示channel没有关闭,如果false表示channel已经关闭 if data,ok:=<-c;ok{ fmt.Println(data) }else{ break } //如果没有close,主协程一直读,会造成主协程死锁 } }
channel不需要经常去关闭,当确实没有数据可发关闭
关闭channel后,无法向channel继续发送数据(引发panic)
关闭channel后,可以继续从channel接收数据(有缓冲的)
对于nil channel(没有make),无论收发都会被阻塞
6、channel和range
package main func main(){ c:=make(chan int) go func(){ for i:=0;i<5;i++{ c<-i } //close可以关闭一个channel close(c) }() for data:=range c{ fmt.Println(data) } }
7、channel和select
package main func fibonacii(c,quit chan int){ x,y:=1,1 for{ select{ case c<-x: //如果c可写,该case执行 x=y y=x+y case <-quit: //如果quit可读,写入才可读 fmt.Println("quit") return } } } func main(){ c:=make(chan int) quit:=make(chan int) go func(){ for i:=0;i<5;i++{ //读取c,写了才能读 fmt.Println(<-c) } //读完,quit写入0 quit <- 0 }() fibonacii(c,quit) }
单流程下一个go只能监控一个channel的状态,select可以完成监控多个channel的状态
select{
case <- chan1:
//如果chan1成功读到数据,则进行该case处理语句,否则整个select会阻塞,随机触发
case chan2 <- 1:
//如果chan2可写
default:
//如果上面都没有成功,进入default
}
五、go modules模块管理
1、Gopath工作模式的弊端
1、没有版本控制概念,go get -u github.com/aceld/zinx 无法指定下载版本
2、无法同步一致第三方版本号
3、无法指定当前项目引用的版本号
2、GoModules模式基础环境说明
2.1 命令
go mod help 查看所有指令
go mod init 生成go.mod文件
go mod download 下载go.mod文件中指明的所有依赖
go mod tidy 整理现有依赖
go mod graph 查看现有依赖结构
go mod edit 编辑go.mod文件
go mod vendor 导出项目所有依赖到vendor目录
go mod verify 校验一个模块是否被篡改
go mod why 查看为什么需要依赖某模板
2.2 环境变量
GO111MODULE
作为Go modules的开关
auto :只要项目包含了go.mod文件,启用Go modules
on:启用
off:禁用,不推荐
go env -w GO111MODULE=on
GOPROXY
项目的第三方依赖库下载地址
默认值:https://proxy.golang.org,direct
direct表示如果代理地址中找不到需要的包,从源地址下
阿里云:https://mirrors.aliyun.com/goproxy/
七牛云:https://goproxy.cn,direct
GONOPROXY
哪些不需要代理
GOSUMDB
校验拉取的第三方库是否完整,保证拉取的代码没有被篡改
设置了goproxy就不用设置
GONOSUMDB
哪些不需要校验
GOPRIVATE
哪些库是私有的,设置后表示该库不需要通过镜像拉取,不需要校验完整性
go env -w GOPROVATE="git.example.com,github.com/zinx,*.example.com"
3、GoModules初始化项目
1、开启Go Modules模块
①go env -w GO111MODULES=on
②或者设置在环境变量中export GO111MODULE=on 设置在用户启动脚本中,需要重新打开终端或者执行source ~/.bashrc
2、初始化项目:
①创建空文件夹
②创建go.mod文件,起当前项目的模块名称 go mod init moduleName
③会生成一个go.mod文件,包含
module modlesName
go 1.20.1
④编写代码
执行go run 会自动下载依赖
手动down:go get github.com/zinx
⑤go mod会添加新代码
require github.com/zinx 版本号 //indirect
⑥会生成go.sum文件
罗列当前项目直接或间接依赖所有模块版本,保证项目依赖的版本不会被篡改
3、go mod:indirect 表示间接依赖,并没有直接依赖
4、go sum:保证库是完整的
库+h1+hash:库中所有文件hash,保证库完整性
库+go.mod+h1+hash:mod文件中的hash
4、改变模块依赖关系
改变依赖版本
go mod edit -replace=旧版本=新版本