Golang入门笔记
第一个Go程序
package main		//包,表明代码所在的模块(包)
import "fmt"		//引入代码依赖
//功能实现
func main(){
	fmt.Println("Hello World");
}
编译执行
➜  go-example git:(master) ✗ cd src/example/main 
➜  main git:(master) ✗ ls
hello_world.go
➜  main git:(master) ✗ go run hello_world.go                                      
Hello World
➜  main git:(master) ✗ go build hello_world.go 
➜  main git:(master) ✗ ls
hello_world    hello_world.go
➜  main git:(master) ✗ ./hello_world 
Hello World
应用程序入口:
- 必须是main包:package main
- 必须是main方法:func main()
- 文件名不一定是 main.go
GO单元测试
在 Go 语言中,标准库中提供了一个非常好用的单元测试工具包 testing 。
单元测试文件必须命名为 *_test.go,测试函数必须以 Test 为开头,接着是函数名,函数的参数为 *testing.T 对象。
package unit
func Square(op int) int {
	return op*op
}
package unit_test
import (
	unit "sample-app/src/example/test/unit_test"
	"testing"
)
func TestSquare(t *testing.T) {
	inputs := [...]int{1, 2, 3}
	expected := [...]int{1, 4, 9}
	for i := 0; i < len(inputs); i++ {
		ret := unit.Square(inputs[i])
		if ret != expected[i] {
			t.Errorf("input is %d,the expected id %d,the actual %d", inputs[i], expected[i], ret)
		}
	}
}
数据类型
- bool
- string
- int int8 int16 int32 int64
- uint uint8 uint16 uint32 uint64 uintptr
- byte
- rune
- float32 float64
- Completx64 completx128
go 语言对类型转换十分严格
- 
go 语言不支持隐式类型转换 
- 
别名和原有类型也不能进行隐式类型转换 
运算符
Go 语言支持多种运算符,包括算术运算符、赋值运算符、比较运算符、逻辑运算符等
算术运算符
- + :加法运算
- - :减法运算
- * :乘法运算
- / :除法运算
- % :取模运算(求余数)
a := 6
b := 4
c := a + b
d := a - b
e := a * b
f := a / b
g := a % b
fmt.Println(c, d, e, f, g) // 输出 10 2 24 1 2
赋值运算符
= :将右侧的数值或表达式赋值给左侧的变量
a := 10
b := 5
a = a + b // a 的值变为 15
比较运算符
比较运算符通常用于控制程序的流程或进行条件判断
- == :判断两个值是否相等,相等则返回 true,否则返回 false
- != :判断两个值是否不相等,不相等则返回 true,否则返回 false
- < :判断左侧的值是否小于右侧的值,小于则返回 true,否则返回 false
- > :判断左侧的值是否大于右侧的值,大于则返回 true,否则返回 false
- <= :判断左侧的值是否小于等于右侧的值,小于等于则返回 true,否则返回 false
- >= :判断左侧的值是否大于等于右侧的值,大于等于则返回 true,否则返回 false
逻辑运算符
- && :逻辑与运算,左右两侧的表达式都为真,则返回 true,否则返回 false
- || :逻辑或运算,左右两侧的表达式有一个为真,则返回 true,否则返回 false
- ! :逻辑非运算,对右侧的表达式取反
条件和循环
在 Go 语言中,条件语句和循环语句都是基本的控制流语句。条件语句包括 if 语句、switch 语句,循环语句包括 for 语句。
循环
Go 语言仅支持循环关键字 for
package loop_test
import "testing"
// go 循环
func TestLoop(t *testing.T) {
  //条件循环
  n := 0
  for n < 5 {
    t.Log(n)
    n++
  }
  // for range 循环
  for index, value := range slice {
    // 这里是循环体
  }
  // while 循环
  for condition {
    // 这里是循环体
  }
  //无限循环 this loop will spin, using 100% CPU (SA5002)
  for {
    //...
  }
}
示例
for i := 0; i < 10; i++ {
    fmt.Print(i, " ")
}
// for range 循环
fruits := []string{"apple", "banana", "cherry"}
for i, fruit := range fruits {
    fmt.Printf("fruits[%d] = %s\n", i, fruit)
}
// while 循环
i := 0
for i < 5 {
    fmt.Print(i, " ")
    i++
}
if条件
//condition 表达式结果必须为布尔值
if condition {
  
}else if condition{
  
}else if condition{
  
}else{
  
}
var a int = 10
if a < 20 {
    fmt.Println("a 小于 20")
} else {
    fmt.Println("a 大于等于 20")
}
//支持变量赋值
if var declaration; condition{
  
}
switch 条件
package condition_test
import (
	"runtime"
	"testing"
)
func TestSwitchCase(t *testing.T) {
	switch os := runtime.GOOS; os {
	case "darwin":
		t.Log("OS X.")
	case "linux":
		t.Log("Linux.")
	default:
		t.Log("%S.", os)
	}
}
func TestSwitchMulitCase(t *testing.T) {
	for i := 0; i < 5; i++ {
		switch i {
		case 0, 2:
			t.Log("Even")
		case 1, 3:
			t.Log("Odd")
		default:
			t.Log("it is not 0-3")
		}
	}
}
func TestSwitchCaseCondition(t *testing.T) {
	for i := 0; i < 5; i++ {
		switch {
		case i%2 == 0:
			t.Log("Even")
		case i%2 == 1:
			t.Log("Odd")
		default:
			t.Log("unknow")
		}
	}
}
数组与切片
数组的声明
var a [3]int		//声明并初始化为默认零值
a[0] = 1	
b := [3]int{1,2,3}		//声明同时初始化
c := [2][2]int{{1,2},{3,4}}	//多维数组初始化
数组截取
a[开始索引(包含),结束索引(不包含)]
a := [...]int{1,2,3,4,5,6,7,8}
a[1:2] //2
a[1:3] //2,3
a[1:len(a)] //2,3,4,5,6,7,8
a[1:] //2,3,4,5,6,7,8
a[:3] //1,2,3
切片的初始化
切片只能和nil比较
// go 切片
func TestSliceInit(t *testing.T) {
	var s0 []int
	t.Log(s0, len(s0), cap(s0))
	s0 = append(s0, 1) //添加参数
	t.Log(s0, len(s0), cap(s0))
	s1 := []int{1, 2, 3, 4}
	t.Log(s1, len(s1), cap(s1))
	s2 := make([]int, 3, 5) //长度为3 容量为5
	t.Log(s2, len(s2), cap(s2))
	t.Log(s2[0], s2[1], s2[2])
	s2 = append(s2, 666)
	t.Log(s2[0], s2[1], s2[2], s2[3])
}
数组和切片的区别
- 长度不同:数组的长度是固定的,切片的长度可以随时改变
- 内存管理不同:数组的内存大小在声明时已经确定,切片内部使用了动态数组的功能,底层会自动进行内存分配和扩容
- 传递方式不同:数组在函数中传递会进行一次值拷贝,而切片则传递一个指向底层数组的指针
Map
Map的初始化
m := map[string]int{"one":1,"two":2,"three":3}
m1 := map[string]int{}
n1["one"] := 1
m2 := make(map[string]int,10 /**init capactiy **/)
//Map的value可以是一个方法
m := map[int]func(op int)int{}
m[1] = func(op int) int {return op}
Map遍历
m := map[string]int{"one": 1, "two": 2, "three": 3}
for key, value := range m {
  t.Log(key, value)
}
string
- string 是数据类型,不是引用类型和指针类型
- string 是只读的 byte slice
- string 的byte数组可以存放任何数据
Go语言中*和&的区别
*和&是 Go 语言中用于操作指针的运算符
- * 运算符被用于指针变量,可以访问指针地址中存储的值。
- & 运算符被用于获取变量的内存地址,生成指向该变量的指针。
函数
函数是一等公民
- 可以有多个返回值
- 所有参数都是值传递:slice,map,channel会有引用传递的错觉
- 函数可以作为变量的值
- 函数可以作为参数和返回值
//go多个返回值方法定义 
func retrunMulitValues() (int, int) {
	return rand.Int(), rand.Int()
}
func TestFn(t *testing.T) {
	a, b := retrunMulitValues()
	t.Log(a, b)
	c,_ := retrunMulitValues()
	t.Log(c)
}
可变参数
func TestVarParam(t *testing.T){
	t.Log(sum(1,1,1,1,1))
}
func sum(ops ...int) int {
	ret := 0
	for _,item := range ops{
		ret = ret + item
	}
	return ret;
}
defer 函数
Go 中的 defer 关键字用于在函数退出时执行一个函数或方法。defer 函数可以是一个普通函数、匿名函数或方法,它们的执行顺序与声明顺序相反,即后声明的 defer 函数先执行。可以用来释放资源、记录日志、错误处理等操作
defer function_name(params)
func Clear(){
	fmt.Println("clear resources!")
}
//defer 函数
func TestDefer(t *testing.T){
	defer Clear()
	t.Log("start")
	t.Log("execute")
}
Go行为的定义和实现
封装数据和行为
结构体定义
type Employee struct{
  Id string
  Name string
  Age int
}
实例创建及初始化
e := Employee{"1","zhangsan",20}
e1 := Employee{Name:"lis",Age:23}
e2 := new(Employee)
e2.Id = "2"
e2.Name = "wangwu"
e2.Age = 20
行为(方法)定义
//该方式在实例对应方法调用时,实例的成员会进行值复制
func (e Employee) String() string {
  fmt.Printf("e address is %x ", unsafe.Pointer(&e.Name))
  return fmt.Sprintf("ID:%s Name:%s Age:%d", e.Id, e.Name, e.Age)
}
//通常情况下为了避免内存拷贝使用该方式
func (e *Employee) String() string {
  fmt.Printf("e address is %x ", unsafe.Pointer(&e.Name))
  return fmt.Sprintf("ID:%s Name:%s Age:%d", e.Id, e.Name, e.Age)
}
Go接口
- 接口为非入侵性,实现不依赖于接口定义
- 接口的定义可以包含在接口使用者包内
接口定义
//接口定义
type Programmer interface{
  WriteHelloWorld() string
}
//接口实现
type GoProgrammer struct{
}
func (p *GoProgrammer) WriteHelloWorld() string{
  return "HelloWrold!"
}
接口变量
var prog string = &GoProgrammer{}
//类型
type GoProgrammer struct{}
//变量
&GoProgrammer{}
自定义类型
//自定义类型
type IntConv func (op int) int
//使用自定义类型
func timeSpent(inner func(op int) int) IntConv{
	return func(n int) int {
		start := time.Now()
		ret := inner(n)
		fmt.Println("time spent:",time.Since(start).Seconds())
		return ret
	}
}
扩展和复用
type Pet struct {
}
func (p *Pet) Speak() {
	fmt.Println("...")
}
func (p *Pet) SpeakTo(host string) {
	p.Speak()
	fmt.Println(" ", host)
}
type Dog struct {
	p *Pet
}
func (d *Dog) Speak() {
	fmt.Println("wang wang wang")
}
func (d *Dog) SpeakTo(host string) {
	d.Speak()
	fmt.Println(" ", host)
}
func TestDog(t *testing.T){
	d := new(Dog)
	d.SpeakTo("dog")
}
多态
//自定义类型
type Code string
//接口
type Programmer interface{
	WriteHelloWorld() Code
}
//实现
type GoProgrammer struct{}
type JavaProgrammer struct{}
//重写方法
func (p *GoProgrammer) WriteHelloWorld() Code{
	return "GoProgrammer write Go HelloWorld!";
}
func (p *JavaProgrammer) WriteHelloWorld() Code{
	return "JavaProgrammer write Java HelloWorld!"
}
func WriteHelloWorld(p Programmer){
	fmt.Println(p.WriteHelloWorld())
}
func TestPolymorphism(t *testing.T){
	gp := new(GoProgrammer)
	WriteHelloWorld(gp)
	jp := new(JavaProgrammer)
	WriteHelloWorld(jp)
}
空接口与断言
- 
空接口可以表示任何类型 
- 
通过断言来将空接口转换为指定类型 v,ok := p.(int) //ok=true时转换成功
func DoSomething(p interface{}) {
	/*
		ok := p.(int) : 断言,如果被断言为一个整型
		v : 转换后的值
	*/
	if v, ok := p.(int); ok {
		fmt.Println("Integer", v)
		return
	}
	if v, ok := p.(string); ok {
		fmt.Println("string", v)
		return
	}
	fmt.Println("unknow type")
}
//使用switch简化
func DoSomethingWithSwitch(p interface{}) {
	switch v := p.(type) {
	case int:
		fmt.Println("Integer", v)
	case string:
		fmt.Println("string", v)
	default:
		fmt.Println("unknow type")
	}
}
Go的错误机制
- 没有异常机制
- error 类型实现了error接口
- 可以通过 error.New() 来快速创建错误实例
//预先定义错误
var LessThanTwoError = errors.New("n should be in not less than 2")
var LargerThenHundredError = errors.New("n should be not larger than 100")
func print(n int) (int, error) {
	if n < 2 {
		return 0, LessThanTwoError
	}
	if n > 100 {
		return 0, LargerThenHundredError
	}
	return n + 1, nil
}
func Test(t *testing.T) {
	//错误检查
	if v, err := print(-1); err != nil {
		if err == LessThanTwoError {
			t.Log("error is LessThanTwoError:", err)
		} else if err == LargerThenHundredError {
			t.Log("error is LargerThenHundredError:", err)
		} else {
			t.Error(err)
		}
	} else {
		t.Log(v)
	}
}
panic
- panic 用于不可恢复的错误
- panic 推出钱会执行 defer 指定的内容
func TestPanic(t *testing.T){
	defer func()  {
		fmt.Println("finally!")
	}()
	panic(errors.New("errors new!"))
}
recover
- recover() 函数可以捕获 panic 抛出的错误,模拟 try-catch 的功能
- recover()函数只能在 defer 函数中使用
func TestRecover(t *testing.T) {
	defer func() {
		if err := recover(); err != nil { // 在defer 函数中使用recover()函数捕获错误
			fmt.Println("finally!")
			//恢复错误...
		}
	}()
	t.Log("start")
	panic(errors.New("errors new!"))
}
package
- 
基本复用模块单元 以首字母大写来表明可以被包外代码访问 
- 
代码的 package 可以和所在的目录不一致 
- 
同一目录中的Go代码的package要保持一致 
init 方法
- 在main函数被执行前,所有依赖的package的init方法都会被执行
- 不同包的init函数按照包导入的依赖关系决定执行顺序
- 每个包可以有多个init函数
- 包的每个源文件也可以有多个init函数
使用外部依赖
比如我们要使用该项目来使用线程安全的map。go get -u https://github.com/easierway/concurrent_map
- 
通过 go get来获取远程依赖go get -u强制从网络更新远程依赖
go get -u github.com/easierway/concurrent_map
import (
	"testing"
	//引入远程包,可以取一个cm的别名
	cm "github.com/easierway/concurrent_map"
)
func TestConcurrentMap(t *testing.T) {
	m := cm.CreateConcurrentMap(32)
	m.Set(cm.StrKey("one"), 1)
	t.Log(m.Get(cm.StrKey("one")))
	t.Log(m.Get(cm.StrKey("two")))
}
Go 并发编程
协程机制
goroutine(协程) 可以看作是一个并发执行的函数,与其他语言的线程(Thread)相比,它具备以下特点:
- 
很小的栈空间开销(2KB,可根据需要调整) 
- 
由 Go 自己管理调度,不依赖于底层系统线程 
- 
可以很方便地使用 go 关键字启动 
- 
通过 channel 进行通信(而不是共享内存),更容易控制和组合同步操作 
func TestGroutine(t *testing.T) {
	for i := 0; i < 10; i++ {
		go func(i int) { //使用 go 关键字将函数作为 goroutine 启动
			fmt.Println(i)
		}(i)
	}
	time.Sleep(time.Second * 1)
}
共享内存和并发机制
Mutex
sync.Mutex 是一个用于互斥访问共享资源的结构体。它具有两个主要方法:Lock 和 Unlock。
使用 Mutex 可以有效地控制并发访问,避免多个 goroutine 同时访问同一个共享资源,从而导致数据破坏或者不一致的情况发生。
func TestCounterThreadSafe(t *testing.T) {
	var mut sync.Mutex
	counter := 0
	for i := 0; i < 5000; i++ {
		go func() {
			defer func() {	//释放锁
				mut.Unlock()
			}()
			mut.Lock()
			counter++
		}()
	}
	time.Sleep(2 * time.Second)	//等待所有goroutine执行完毕
	t.Logf("counter = %d", counter)
}
WaitGroup
Go 中的 WaitGroup 是一种用于等待多个 goroutine 完成的机制,它可以避免在程序退出前未完成的 goroutine 的情况。
WaitGroup 包含两个方法:Add 和 Done,以及一个属性 Wait。
- Add 方法用于在 WaitGroup 中添加等待的 goroutine 的数量,一般在启动 goroutine 之前调用。
- Done 方法用于在 goroutine 完成时通知 WaitGroup,表示当前等待的 goroutine 数量减一。
- Wait 方法会使当前 goroutine 阻塞,直到所有等待的 goroutine 都执行完毕(即 Done 方法调用的次数等于 Add 方法调用的次数)。
func TestCounterWaitGroup(t *testing.T) {
	var mut sync.Mutex
	var wg sync.WaitGroup
	counter := 0
	for i := 0; i < 5000; i++ {
		wg.Add(1)
		go func() {
			defer func() {
				mut.Unlock()
			}()
			mut.Lock()
			counter++
			wg.Done()
		}()
	}
	wg.Wait()// time.Sleep(2 * time.Second)
	t.Logf("counter = %d", counter)
}
CSP 并发机制
CSP(Communicating Sequential Process)并发机制包含三个核心概念:goroutine、channel 和 select。
- Goroutine
Goroutine 是 Go 语言中轻量级线程的概念,在程序中可以创建众多的 goroutine 进行并发处理。与传统操作系统线程相比,goroutine 更加轻量、更高效,它们可以通过 go 关键字来启动,并由 Go 的运行时系统进行管理和调度。一个 goroutine 可以看作一个函数的执行体,每个 goroutine 都有自己的局部变量和栈,可以使用语言内置的关键字 go 以轻量级的方式启动。
- Channel
在 Go 语言中,goroutine 之间通信的主要方式是通过 channel,它是一种类似于队列的数据结构,可以用于两个或多个 goroutine 之间进行数据交换。通常,一个 goroutine 向 channel 中发送数据,另一个 goroutine 从 channel 中接收数据,通过 channel 实现了不同 goroutine 之间的同步和通信。创建一个 channel 只需使用内置函数 make 即可,如:ch := make(chan int)。
- Select
select 语句用于在多个 channel 上进行非阻塞的多路通信操作。select 语句会监听每个 channel 上的数据流动,当 channel 中有数据可用时,就会执行相应的分支。如果多个分支同时准备就绪,select 会随机选择其中一个分支执行,这种特性可以有效地实现复杂的并发模型。在 select 中可以使用 case 关键字处理不同的 channel 操作,select 后面可以跟一个默认分支 default,用于处理还没有准备就绪的 channel 操作。
多路选择和超时 select
多渠道的选择
select{
  case ret := <-retCh1:
  	t.Logf("result %s",ret)
  case ret := <-retCh2:
  	t.Logf("result %s",ret)
	default:
  	t.Error("No one retunted")
}
超时控制
select{
  case ret := <-retCh:
  	t.Logf("result %s",ret)
  case <- time.After(time.Second * 1)
  	t.Error("time out")
}
示例
func service() string {
	time.Sleep(time.Millisecond * 50)
	return "Done"
}
func AsyncService() chan string {
	retCh := make(chan string) //声明一个channel
	go func() {
		ret := service()
		fmt.Println("returned result.")
		retCh <- ret //往channel中放数据
		fmt.Println("service exited.")
	}()
	return retCh
}
func TestAsyncService(t *testing.T) {
	//多路选择实现超时
	select {
	case ret := <-AsyncService():
		t.Log(ret)
	// case <-time.After(time.Millisecond * 100):
	case <-time.After(time.Millisecond * 10):
		t.Error("time out")
	}
}
Chan的广播与关闭
- 向关闭的channel发送数据,会导致panic
- v,ok <- ch; ok为bool值,true 表示正常接受,false表示通道关闭
- 所有的channel接受者都会在channel关闭时,立即从阻塞等待中返回且上述ok值为false。这个广播机制常被利用,进行多个订阅者同时发送信号。如:退出信号。
func dataProducer(ch chan int, wg *sync.WaitGroup) {
	go func() {
		for i := 0; i < 10; i++ {
			ch <- i
		}
		close(ch) //关闭channel
		wg.Done()
		//ch <- 100 向关闭的通道继续发送数据会导致panic
	}()
}
func dataReceiver(ch chan int, wg *sync.WaitGroup) {
	go func() {
		for i := 0; i < 11; i++ {
			if data, ok := <-ch; ok { //接收者判断channel是否已经关闭
				fmt.Println(data)
			} else {
				fmt.Println("channel is close!")
				break
			}
		}
		wg.Done()
	}()
}
func TestCloseChannel(t *testing.T) {
	var wg sync.WaitGroup
	ch := make(chan int)
	wg.Add(1)
	dataProducer(ch, &wg)
	wg.Add(1)
	dataReceiver(ch, &wg)
	wg.Wait()
}
Chan的取消
在 Go 语言中,可以通过 context 和 close 函数来取消 chan
Close
在 Go 语言中,close 函数是用于关闭一个通道(channel)的。
通道关闭后,通道就不能再进行发送操作。当通道中的所有数据被接收完毕后,接收操作会立即返回一个零值和一个false值,表明通道已经关闭了。如果通道已经关闭,再次进行关闭操作会引起运行时panic。
/*
*
判断任务是否取消
*/
func isCancelled(cancelChan chan struct{}) bool {
	select {
	case <-cancelChan:
		return true
	default:
		return false
	}
}
//关闭chan
func cancel_1(cancelChan chan struct{}) {
	cancelChan <- struct{}{}
}
func cancel_2(cancelChan chan struct{}) {
	close(cancelChan)
}
/*
*
任务取消
*/
func TestCancel(t *testing.T) {
	cancelChan := make(chan struct{}, 0)
	for i := 0; i < 5; i++ {
		go func(i int, cancelCh chan struct{}) {
			for {
				if isCancelled(cancelCh) {
					break
				}
				time.Sleep(time.Millisecond * 5)
			}
			fmt.Println(i, "Canceled")
		}(i, cancelChan)
	}
	cancel_2(cancelChan)
	time.Sleep(time.Second * 1)
}
Context
context 是 Go 语言中的一个标准库。它提供了一种在 goroutine 之间传递的上下文信息的机制,可以用于处理超时、取消信号、截止时间等问题,防止程序把一些资源留下来占用,从而保证代码的健壮性和安全性。
- 使用 context 需要创建一个上下文对象,可以通过 context.Background() 或 context.TODO() 来创建
- 子context:context.WithCancel(parentContext)创建
- ctx,cancel := WithCancel(context.Background())
 
- 当前context被取消时,基于他的context都会被取消
- 接受取消通知:<- ctx.Done()
/*
*
通过context判断channel是否关闭
*/
func isCancelled(context context.Context) bool {
	select {
	case <-context.Done(): //接收消息通知
		return true
	default:
		return false
	}
}
/*
*
任务取消
*/
func TestCancel(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background()) //创建context
	for i := 0; i < 5; i++ {
		go func(i int, ctx context.Context) {
			for {
				if isCancelled(ctx) {
					break
				}
				time.Sleep(time.Millisecond * 5)
			}
			fmt.Println(i, "Canceled")
		}(i, ctx)
	}
	cancel() //取消context
	time.Sleep(time.Second * 1)
}
sync.Once(只执行一次的函数)
sync.Once 是 Go 标准库提供的一种用于实现单例模式的同步机制。
它可以确保某个操作仅在第一次被调用时执行一次,并且后续的调用直接返回第一次执行时的结果,非常适合做单例实现。
type Singleton struct {
}
var singletonInstance *Singleton
var once sync.Once
// 创建一个单例的对象
func GetSingletonObj() *Singleton {
	once.Do(func() {
		fmt.Println("create obj")
		singletonInstance = new(Singleton)
	})
	return singletonInstance
}
func TestSingleton(t *testing.T) {
	var wg sync.WaitGroup
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {
			obj := GetSingletonObj()
			fmt.Printf("%d\n", unsafe.Pointer(obj)) //输出单例对象的地址
			wg.Done()
		}()
	}
	wg.Wait()
}
使用buffered channel 实现对象池
package obj_pool
import (
	"errors"
	"time"
)
// Connection 连接信息
type Connection struct {
}
// ConnectionPool 连接对象池
type ConnectionPool struct {
	bufChan chan *Connection
}
// NewConnectionPool 创建连接池
// numOfObj channel 大小
func NewConnectionPool(numOfObj int) *ConnectionPool {
	connPool := ConnectionPool{}
	connPool.bufChan = make(chan *Connection, numOfObj)
	for i := 0; i < numOfObj; i++ {
		connPool.bufChan <- &Connection{}
	}
	return &connPool
}
// GetConnection 获取连接
func (p *ConnectionPool) GetConnection(timeout time.Duration) (*Connection, error) {
	select {
	case ret := <-p.bufChan:
		return ret, nil
	case <-time.After(timeout):
		return nil, errors.New("time out") //超时控制
	}
}
// ReleaseConnection 释放连接
func (p *ConnectionPool) ReleaseConnection(conn *Connection) error {
	select {
	case p.bufChan <- conn:
		return nil
	default:
		return errors.New("overflow")
	}
}
测试用例
// 测试对象池
func TestConnectionPool(t *testing.T) {
	pool := NewConnectionPool(10)
	for i := 0; i < 11; i++ {
		if v, err := pool.GetConnection(time.Second * 1); err != nil {
			t.Error(err)
		} else {
			fmt.Printf("%T\n", v)
			if err := pool.ReleaseConnection(v); err != nil {
				t.Error(err)
			}
		}
	}
	t.Log("Done")
}
sync.Pool 对象缓存
sync.Pool是 Go 语言标准库中提供的一种对象池实现方式。
对象池可以用于减少对象创建和垃圾回收的次数,提高程序性能。
sync.Pool 使用可重用的对象来构建一个对象池,以便它们可以在将来的操作中被再次使用,而不必再次分配和初始化。
sync.Pool的特点有:
- 对象池是并发安全的,可以被多个 Goroutine 并发访问。
- 对象池默认底层实现的大小是按需分配的,防止了需要时锁定大小的对象池出现的问题。
- 对象池中的对象在适当的时候可以被垃圾收集器处理掉,而不会一直累计占据内存。
syncPool的生命周期
- GC会清除 sync.Pool 中缓存的对象
- 对象缓存的有效期为下一次GC之前
使用 sync.Pool
- Get() 方法从池中获取一个对象。如果当前池为空,则返回一个新对象。
- Put(x interface{}) 方法将对象放回到池中。如果池已满,则不会加入该对象。
package obj_cache
import (
	"fmt"
	"sync"
	"testing"
)
// TestSyncPool 测试sync.Pool
func TestSyncPool(t *testing.T) {
	pool := &sync.Pool{
		New: func() interface{} {
			fmt.Println("create new Object")
			return 100
		},
	}
	v := pool.Get().(int)
	fmt.Println("v:", v)
	pool.Put(666)
	//runtime.GC() //手动GC 清除sync.Pool中缓存的对象
	v1 := pool.Get().(int)
	fmt.Println("v1:", v1)
	v2 := pool.Get().(int)
	fmt.Println("v2:", v2)
}
// TestSyncPoolInMultiGroutine 测试多协程情况下sync.Pool
func TestSyncPoolInMultiGroutine(t *testing.T) {
	pool := &sync.Pool{
		New: func() interface{} {
			fmt.Println("create new Object")
			return 100
		},
	}
	pool.Put(666)
	pool.Put(666)
	pool.Put(666)
	var wg sync.WaitGroup
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func(i int) {
			fmt.Println(pool.Get().(int))
			wg.Done()
		}(i)
	}
	wg.Wait()
}
Go中的反射编程
Go 语言通过反射(reflection)包实现了反射编程。
反射库提供了在运行时检查类型和变量、调用函数等相关的功能,让程序在运行时可以动态地获取类型信息、调用方法等。
- 可以使用 reflect.TypeOf() 方法来获取一个变量的类型信息
- 反射还可以动态地创建和修改对象。可以使用 reflect.New() 方法来创建一个值的指针
- 反射还可以动态地调用对象的方法。可以使用 reflect.ValueOf() 方法获取到一个值的 reflect.Value 类型对象,然后使用 reflect.Value 的 MethodByName() 方法来获取到某个方法,并调用这个方法。通过FieldByName()方法来获取到某个字段的信息。
- 通过kind来判断类型
判断类型--kind
// CheckType 通过kind来判断类型
func CheckType(v interface{}) {
	t := reflect.TypeOf(v)
	switch t.Kind() {
	case reflect.Float32, reflect.Float64:
		fmt.Println("Float")
	case reflect.Int, reflect.Int32, reflect.Int64:
		fmt.Println("Integer")
	default:
		fmt.Println("Unknown", t)
	}
}
func TestBasicType(t *testing.T) {
	var f float32 = 1
	CheckType(f)
}
TypeOf and ValueOf
type Employee struct {
	EmployeeId string
	Name       string `format:"normal"`
	Age        int
}
func (e *Employee) UpdateAge(newAge int) {
	e.Age = newAge
}
type Customer struct {
	CookieId string
	Name     string
	Age      int
}
func TestInvokeByName(t *testing.T) {
	e := &Employee{"1", "Tom", 30}
	//按名字获取成员
	t.Log(reflect.ValueOf(*e).FieldByName(`Name`))
	t.Logf("Name:value(%[1]v),Type((%[1]T)", reflect.ValueOf(*e).FieldByName(`Name`))
	if nameField, ok := reflect.TypeOf(*e).FieldByName(`Name`); !ok {
		t.Error("Failed to get Name field.")
	} else {
		t.Log("Tag:format:", nameField.Tag.Get("format"))
	}
	//动态调用method
	reflect.ValueOf(e).MethodByName("UpdateAge").
		Call([]reflect.Value{reflect.ValueOf(23)})
	t.Log("UpdateAge:", e)
}
struct Tag
在 Go 语言中,可以使用 Struct Tag 为结构体的各个字段添加元数据信息。
Struct Tag 是通过在字段定义后面添加一个用反引号 `` 包裹的字符串来定义的。
例如:
type Person struct {
  Name string `json:"name" xml:"name"`
  Age  int  `json:"age" xml:"age"`
  Phone string `json:"phone" xml:"phone"`
}	
访问struct tag
// TestStructTag 访问 struct tag
func TestStructTag(t *testing.T) {
	p := &Person{"Tom", 23, "6666666"}
	if field, ok := reflect.TypeOf(*p).FieldByName("Name"); ok {
		t.Log("field tag json:", field.Tag.Get("json"))
		t.Log("field tag xml:", field.Tag.Get("xml"))
	}
}
DeepEqual
在 Go 语言中,使用 reflect.DeepEqual() 函数可以比较两个任意类型的值是否相等。
func TestDeepEqual(t *testing.T) {
	a := map[int]string{1: "one", 2: "two", 3: "three"}
	b := map[int]string{1: "one", 2: "two", 3: "three"}
	//t.Log(a == b)
	t.Log("reflect.DeepEqual(a, b) result :", reflect.DeepEqual(a, b))
	s1 := []int{1, 2, 3}
	s2 := []int{1, 2, 3}
	s3 := []int{2, 3, 1}
	t.Log("s1 == s2?", reflect.DeepEqual(s1, s2))
	t.Log("s1 == s3?", reflect.DeepEqual(s1, s3))
}
Go内置JSON解析
在 Go 中,内置有对 JSON 的支持,在 encoding/json 包中提供了一些方法和工具来处理 JSON 格式的数据。
JSON 解析
使用 json.Unmarshal 函数将 JSON 数据的字符串表示转换为 Go 语言的数据结构
type Person struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}
func TestJsonParsing(t *testing.T) {
	var jsonStr = `{"name":"张三","age":18}`
	var person Person
	//第一个参数必须是字节切片类型,所以需要通过 []byte 函数将字符串转换为字节切片
	//第二个参数是解析后的结果所要赋值的变量的指针
	err := json.Unmarshal([]byte(jsonStr), &person)
	if err != nil {
		t.Error("json parsing error :", err)
		return
	}
	t.Log("person :", person)
}
JSON 解析为Map
有时,我们并不知道 JSON 数据的具体结构,这时候我们可以将 JSON 数据整体解析成 map[string]interface{} 类型的数据
func TestJson2Map(t *testing.T) {
	var jsonStr = `{"name":"张三","age":18}`
	var data map[string]interface{}
	err := json.Unmarshal([]byte(jsonStr), &data)
	if err != nil {
		t.Error("json parsing error :", err)
		return
	}
	t.Log("data :", data)
	t.Log("data Name:", data["name"])
	t.Log("data Age:", data["age"])
}
JSON解析为切片
JSON 数据可能是一个数组,我们可以将其解析为相应的切片类型
func TestJson2Slice(t *testing.T) {
	var jsonStr = `[{"name":"张三","age":18},{"name":"李四","age":20}]`
	var persons []Person
	err := json.Unmarshal([]byte(jsonStr), &persons)
	if err != nil {
		t.Error("json parsing error :", err)
		return
	}
	t.Log("data :", persons)
}
easyjson
easyjson 提供了一种快速简便的方法来将 Go 结构编组/解组到 JSON 或从 JSON 中解组,而无需使用反射。在性能测试中,easyjson 比标准
encoding/json包高 4-5 倍,比其他 JSON 编码包高 2-3 倍。
安装
# for Go < 1.17
go get -u github.com/mailru/easyjson/...
# for Go >= 1.17
go get github.com/mailru/easyjson && go install github.com/mailru/easyjson/...@latest
使用
首先,我们需要定义要编码和解码的 Go 结构体类型,并为该类型实现 EasyJSON 的 easyjson.Marshaler 和 easyjson.Unmarshaler 接口。可以通过执行以下命令生成 easyjson 文件:
easyjson -all <file>.go
例如我定一个struct_def.go文件,里面有一个Person结构体
type Person struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}
指定easyjson -all struce_def.go后,生成的struct_def_easyjson.go) 中会有以下几个方法
func (p *Person) MarshalJSON() ([]byte, error)
func (p *Person) UnmarshalJSON([]byte) error
func (p *Person) MarshalEasyJSON(w *jwriter.Writer)
func (p *Person) UnmarshalEasyJSON(l *jlexer.Lexer)
测试
import (
	"encoding/json"
	"github.com/mailru/easyjson"
	"testing"
)
// 编码
func TestPerson_MarshalEasyJSON(t *testing.T) {
	person := Person{Name: "Tom", Age: 23}
	jsonData, err := json.Marshal(person)
	if err != nil {
		t.Error("json parsing error ...")
		return
	}
	t.Log("json str :", string(jsonData))
}
// 解码
func TestPerson_UnmarshalEasyJSON(t *testing.T) {
	jsonData := []byte(`{"name":"Tom","age":25}`)
	var person Person
	err := easyjson.Unmarshal(jsonData, &person)
	if err != nil {
		t.Error("json parsing error ...")
		return
	}
	t.Log("person :", person)
}
Go Http服务
package main
import (
	"fmt"
	"io"
	"net/http"
)
func main() {
	http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
		body, err := io.ReadAll(request.Body)
		if err != nil {
			// 处理错误
		}
		fmt.Println("Request Body:", string(body))
		// 对请求体做更多处理
		// 将处理结果写回响应
		_, err = writer.Write([]byte("Response Body"))
		if err != nil {
			return
		}
	})
	err := http.ListenAndServe(":9091", nil)
	if err != nil {
		return
	}
}

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号