Golang学习笔记
原作者视频地址:
https://www.bilibili.com/video/BV11Y4y1a7Jo?spm_id_from=333.1007.top_right_bar_window_custom_collection.content.click
本人为自学整理的文档
反射
基本概念是在程序运行时动态操作结构体
使用情况一:当变量存储结构体属性名称,想对结构体这个属性赋值或查看时,可以用反射
type People struct {
Id int
Name string
}
func main() {
peo:=People{20220509,"小王"}
//获取值到v,然后获取属性的值
v:=reflect.ValueOf(peo)
content:="Name"
fmt.Println(v.FieldByName(content))
//设置值,注意要用指针,而反射时获取peo1的地址,要用Elem()获取指针指向地址的封装,进而获取实际的值
peo1:=new(People)
y:=reflect.ValueOf(peo1).Elem()
//判断peo1的Id是否被设置
fmt.Println(y.FieldByName("Id").CanSet())
y.FieldByName("Id").SetInt(12345678)
fmt.Println(peo1)
}
使用情况二:判断变量类型
a:=509
//获取类型
fmt.Println(reflect.TypeOf(a))
//获取值
fmt.Println(reflect.ValueOf(a))
使用情况三:凡是结构体出现标签,并且想得到标签的值,都是通过反射获取的
t:=reflect.TypeOf(Teacher.People{})
fmt.Println(t.FieldByName("Name"))
name,_:=t.FieldByName("Name")
fmt.Println(name.Tag)
fmt.Println(name.Tag.Get("xml"))
Xml文件的读取
单条信息
1.先定义一个结构体用于存放读取的数据
type People struct {
XMLName xml.Name `xml:"people"`//整个元素节点
Id int `xml:"id,attr"`//一个是根节点一个是标签
Name string `xml:"name"`//结构体people的参数之一
Address string `xml:"address"`//结构体people的另一参数
}
2.然后调用相关库函数对xml文件进行读取,绑定数据并显示到控制台
peo:=new(Xml.People)
b,_:=ioutil.ReadFile("D://people.xml")
xml.Unmarshal(b,peo)
fmt.Println(peo)
多条信息
type Peoples struct {
XMLName xml.Name `xml:"peoples"`//整个元素节点
Version string `xml:"version,attr"`
Peos []People `xml:"people"`
}
peo:=new(Xml.Peoples)
b,_:=ioutil.ReadFile("D://people2.xml")
xml.Unmarshal(b,peo)
fmt.Println(peo)
Xml文件的生成
新建一个结构体并实例化,调用函数对信息生成xml文件
type People struct {
XMLName xml.Name `xml:"people"`//整个元素节点
Id int `xml:"id,attr"`//一个是根节点一个是标签
Name string `xml:"name"`//结构体people的参数之一
Address string `xml:"address"`//结构体people的另一参数
}
peo:=Xml.People{Id: 123,Name: "信息的结构体",Address: "D盘"}
//考虑xml文件的格式缩进
b,_:=xml.MarshalIndent(peo,""," ")
//追加头部信息
b = append([]byte(xml.Header),b...)
ioutil.WriteFile("D:/people3.xml",b,0777)
fmt.Println("执行结束")
日志
使用开发工具时,控制台打印的信息就是日志信息
项目最终发布后是没有开发工具的,而需要记录日志应该把信息输出到文件中,这个功能也是日志的功能
有三种级别日志输出
Print() 输出日志信息
Panic()打印日志信息,并触发panic,日志信息为Panic信息
Fatal()(打印日志信息后调用os.Exit(0)
所有日志信息打印时都带有时间,且颜色为红色
每种级别日志打印都提供了三个函数:Println(),Print(),Printf()
打印日志信息到文件/控制台
func LogPrint(path string) {
f,_:=os.OpenFile(path,os.O_APPEND|os.O_CREATE,0777)
logger:=log.New(f,"[Info]",log.Ltime)
logger.Println("打印日志信息")//打印到文件
log.Println("完成打印到文件")//打印到控制台
}
线程休眠是指main函数为主线程(协程),程序从上向下执行
可以用time包下的Sleep(n)来阻塞多少纳秒
延迟执行:
time.AfterFunc(3e9, func() {
fmt.Println("协程执行")
})
Goland从语言层面支持并发,在Goland中的goroutine(协程)类似其他语言的线程
几种主流并发模型
1.多线程,每个线程只处理一个请求,只有请求结束后对应的线程才会接收下一个请求。在高并发的情形下性能开销大。
2.基于回调的异步IO,在程序运行过程中可能产生大量的回调导致维护成本加大。
3.协程,不需要抢占式调用。
WaitGroup
计数器,只要计数器有内容就会阻塞
方法:
Add(delta int)表示向内部计数器添加增量detal
Done()表示减少WaitGroup计数器的值,应该在程序最后执行
Wait()表示阻塞直到WaitGroup计数器为0
var wg sync.WaitGroup
wg.Add(5)
//五个协程
for i:=0;i<5;i++{
go func() {
fmt.Println("第",i,"次执行")
wg.Done()
}()
}
//当上面的协程执行完后这里的主协程才能继续执行下去
wg.Wait()
fmt.Println("主协程完成")
补充说明:
互斥锁
var (
num = 100
wg sync.WaitGroup//计数器
m sync.Mutex//锁
)
func demo(){
m.Lock()//先上锁,再执行
for i:0;i<10;i++{
num = num - 1
}
m.Unlock()//解锁
wg.Done()//计数器减一
}
func main() {
wg.Add(10)
for i:=0;i<10;i++{
go demo()
}
wg.Wait()
fmt.Println("程序结束")
}
读写锁:锁的是可执行的代码范围
在go语言中,map不是线程安全的,多个goroutine同时操作会出现错误
读锁可以有多个,写锁只能有一个,且读写锁不能同时存在
var rwm sync.RWMutex
var wg sync.WaitGroup
wg.Add(10)
m:=make(map[int]int)
for i:=0;i<10;i++ {
go func(j int) {
rwm.Lock()
m[j] = j
fmt.Println(m)
rwm.Unlock()
wg.Done()
}(i)
}
wg.Wait()
fmt.Println("完成")
channel:协程间的同步和通信
1.简单无缓存通道代码示例
此代码中如果没有从channel中取值c,d=<-ch语句,会阻塞,程序结束时go func并没有执行
下面代码示例演示了同步操作,类似与WaitGroup功能,保证程序结束时goroutinei已经执行完成
向goroutine中添加内容的代码会阻塞goroutine执行,所以要把ch<-1放入到goroutine有效代码最后一行。
无论是向channel存数据还是取数据都会阻塞
close(channel)关闭channel,关闭后只读不可写
注:定义的channel应该是放一个数据,然后跳转到取一个数据
//示例一:执行子协程,ch放入数据,再执行取出数据,然后主协程结束,类型之前WaitGroup的计数器功能,不过还新增一个传值功能用于通信
ch:=make(chan int)
go func() {
fmt.Println("执行")
ch <- 996//注意这句语句应该放到子协程的最后,发挥同步功能,确保子协程执行完再执行主协程
}()
a:=<-ch
fmt.Println(a)
//示例二:ch1用于两个子协程通信,ch2用于两个子协程与主协程的同步
ch1 := make(chan string)
ch2 := make(chan int)
go func() {
ch1 <- "这里是子协程1,传输数据开始"
ch2 <- 1
}()
go func() {
content := <- ch1//取数据
fmt.Println("取出子协程1的通道数据,这里是子协程2:",content)
ch2 <- 2
}()
<-ch2
<-ch2//注意要取两次,因为是存入两次
ch1 := make(chan string)
ch2 := make(chan int)
go func() {
for i:=97;i<=97+26;i++{
ch1 <- fmt.Sprintf("%c",i)
}
ch2 <- 1
}()
go func() {
for n:= range ch1{
fmt.Println(n)
}
}()
<-ch2
fmt.Println("主协程完成")
上述这段代码的理解是:ch1是用于两个子协程间进行同步的,ch2是用于两个子协程与主协程间进行同步的;
整个代码的运行流程应该是—>
首先进入上面的协程,将’a’写入通道ch1中(此时这个通道就只有等到读取出’a’后才可以继续写入),这个进程后面的代码就不执行先;
然后进入两个进程的竞争,上面的进程还是先抢到执行权,然后发现通道ch1写不进了,所以阻塞上面的进程,得找通道输出
之后才轮到下面的进程,用for range对通道ch1进行读取,然后执行for range里面的程序,如果是按 <-ch1 的方式读取,它不会执行后面的程序,会直接跳到通道输入那部分的程序(即另一个协程已经可以执行了),后面的过程按这三步类推。
直到最后一个值从通道中读取出来,然后上面的进程发现已经不用写ch1了,接着执行ch2的写入,最后ch2写出并结束程序。
死锁:主协程阻塞才是死锁,子协程阻塞不算死锁
select:
select的用法是如果只有一个case条件满足,就执行case对应的内容,如果有多个case条件,就随机执行一个case情况,如果没有case条件满足,就执行default对应的内容,没有设置default就会产生死锁。
注意:select case只会从上往下执行一遍,如果想多次执行,需要配合for循环
//一直接收消息,上面发消息,下面用死循环接收,注意default就是避免死锁
ch1 := make(chan int)
for i:=0;i<10;i++{
go func(j int) {
ch1 <- j
}(i)
}
for {
select {
case n := <-ch1:fmt.Println(n)
default:
}
}
GC
GC英文全称garbage collector
一些常用GC筒法
1.引用计算法当对像被引用时计算器加一不被引用计数器减一
·PHP和object-C使用
·相互引用无法回收
·计数增加消耗
2.Mark And Sweep标记和清除算法.停止程序运行,递归遍历对象,进行标记标记完成后将所有没有引用的对象进行清除
由于标记需要停止程序(Stop the world),当对象特别多时标记和清除过程比较耗时.很难接受
3.三色标记法是Mark And Sweep的改进版.从逻辑上分为白色区(未搜刺,灰色区正搜索),黑色区(已搜索),灰色区内容是子引用没有进行搜索,黑色区表示子引用存在
4.分代收集一般情况都有三代。例如java中新生代,老年代,永久代。当新生代中带有阈值时会把对像放入到老年代,相同道理老年代内容达到值会放入到永久代,优先回收新生代
go语言采用Stop The World,用三色标记法
Go触发GC机制:情况一是当前申请的内存是上次申请的两倍;情况二是每两分钟触发一次GC