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