commit e6ee860fd975e57f6fbece83c090fbe6273d5f3a Author: frank <3224536684@qq.com> Date: Wed Mar 1 11:23:26 2023 +0800 :sparkles: init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7689b76 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +### Go template +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work + +.idea +# .vscode +app.env + +.DS_Store +*/.DS_Store \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e3fb8e4 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "go.autocompleteUnimportedPackages": true, + "go.testFlags": [ + "-v", + "-cover" + ] +} \ No newline at end of file diff --git a/README.MD b/README.MD new file mode 100644 index 0000000..93b7a9c --- /dev/null +++ b/README.MD @@ -0,0 +1,73 @@ +# Go学习 + +### 编写hello world程序 +- package 必须是 main +- 方法名必须是 main() +- 文件名不一定是 main.go + +### 和其他语言差异 +- Go中 main 函数不支持任何返回值 +- 通过os.Exit来返回状态 +- main 函数不支持传入参数 +- 通过 os.Args 获取命令行参数 + +### 编写测试程序 +1. 源码文件以 `_test` 结尾: xxx_test.go +2. 测试方法名以 Test 开头: func TestXXX(t *testing.T) {...} + + +### 变量赋值 +- 赋值可以自动进行类型推断 +- 在一个赋值语句中可以对多个变量进行同时赋值 +```go +// 单独赋值 +var a int = 1 +var b int = 1 + +// 同时赋值 +var ( + a int = 1 + b int = 1 +) + +// 自动推断 +a := 1 +b := 1 +``` + +### 基本数据类型 + +```go +bool +string +int int8 int16 int32 int64 +uint uint8 uint16 uint32 uint64 uintptr +byte // alias for uint8 +rune // alias for int32, represents a Unicode code point +float32 float64 +complex64 complex128 +``` + +### 类型转化 +- 与其他编程语言的差异 + 1. Go不允许隐式类型转换 + 2. 别名和原有类型也不能进行隐式类型转换 + +- 类型的预定义值 + 1. math.MaxInt64 + 2. math.MaxFloat64 + 3. math.MaxUint32 + + +### 算术运算符 +- Go语言没有前置的 ++, --, + + +### 线程和协程 Thread vs. Groutine + +1. 创建时默认的 stack (栈) 的大小 + - JDK5 以后 Java Thread stack 默认为 1M + - Groutine 的 stack 初始化大小为 2k +2. 和 KSE (Kernel Speace Entity) 的对应关系 + - Java Thread 是 1:1 + - Groutine 是 M:N \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..92af0fc --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module go_study + +go 1.20 + +require github.com/go-playground/assert/v2 v2.2.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..1998fd7 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= diff --git a/src/c10_func/func_test.go b/src/c10_func/func_test.go new file mode 100644 index 0000000..1a35e76 --- /dev/null +++ b/src/c10_func/func_test.go @@ -0,0 +1,60 @@ +package func_test + +import ( + "fmt" + "math/rand" + "testing" + "time" +) + +// 多返回值函数 +func returnMultiValues() (int, int) { + return rand.Intn(10), rand.Intn(20) +} + +// 函数用法之一: 统计执行时间 +func timeSpent(inner func(op int) int) func(op int) int { + return func(n int) int { + start := time.Now() + ret := inner(n) + fmt.Println("time spent: ", time.Since(start).Seconds()) + return ret + } +} + +func slowFun(op int) int { + time.Sleep(time.Second * 1) + return op +} + +func TestFn(t *testing.T) { + a, b := returnMultiValues() + t.Log(a, b) + + tsSF := timeSpent(slowFun) + t.Log(tsSF(10)) +} + +// 求和 +func Sum(ops ...int) int { + ret := 0 + for _, op := range ops { + ret += op + } + return ret +} + +func TestVarParam(t *testing.T) { + t.Log(Sum(1, 3, 5, 7, 9, 2, 6)) +} + +func Clear() { + fmt.Println("Clear resources") +} + +// defer 延迟执行 一般用于资源的释放和异常的捕捉 +func TestDefer(t *testing.T) { + defer Clear() + fmt.Println("Start") + panic("program error") //抛出异常 +} diff --git a/src/c11_customer_type/customer_type_test.go b/src/c11_customer_type/customer_type_test.go new file mode 100644 index 0000000..b83e93c --- /dev/null +++ b/src/c11_customer_type/customer_type_test.go @@ -0,0 +1,29 @@ +package customer_tpye_test + +import ( + "fmt" + "testing" + "time" +) + +// 自定义类型 +type IntConv func(op int) int + +func timeSpent(inner IntConv) IntConv { + return func(n int) int { + start := time.Now() + ret := inner(n) + fmt.Println("time spent: ", time.Since(start).Seconds()) + return ret + } +} + +func slowFun(op int) int { + time.Sleep(time.Second * 1) + return op +} + +func TestFn(t *testing.T) { + tsSF := timeSpent(slowFun) + t.Log(tsSF(10)) +} diff --git a/src/c11_encapsulate/encap_test.go b/src/c11_encapsulate/encap_test.go new file mode 100644 index 0000000..ebf4c70 --- /dev/null +++ b/src/c11_encapsulate/encap_test.go @@ -0,0 +1,48 @@ +package encap_test + +import ( + "fmt" + "testing" + "unsafe" +) + +type Employee struct { + Id string + Name string + Age int +} + +// 指针形式 复用 推荐使用 +func (e *Employee) String() string { + fmt.Printf("Address is %x \n", 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("Address is %x \n", unsafe.Pointer(&e.Name)) +// return fmt.Sprintf("Id: %s, Name: %s, Age: %d", e.Id, e.Name, e.Age) +// } + +func TestCreateEmployeeObj(t *testing.T) { + e := Employee{"1", "Frank", 20} + e1 := Employee{Name: "Mike", Age: 28} + e2 := new(Employee) //返回指针 + e2.Id = "3" + e2.Name = "Jack" + e2.Age = 32 + + t.Log(e) + t.Log(e1) + t.Log(e1.Id) //空字符串 + t.Log(e2) + + t.Logf("e is %T", e) + t.Logf("e2 is %T", e2) +} + +func TestStructOperations(t *testing.T) { + e := Employee{"1", "Frank", 20} + fmt.Printf("Address is %x \n", unsafe.Pointer(&e.Name)) + t.Log(e.String()) +} diff --git a/src/c11_interface/interface_test.go b/src/c11_interface/interface_test.go new file mode 100644 index 0000000..4133c15 --- /dev/null +++ b/src/c11_interface/interface_test.go @@ -0,0 +1,20 @@ +package interface_test + +import "testing" + +type Programmer interface { + WriteWord() string +} + +type GoProgrammer struct { +} + +func (p *GoProgrammer) WriteWord() string { + return "hello world" +} + +func TestClient(t *testing.T) { + var p Programmer + p = new(GoProgrammer) + t.Log(p.WriteWord()) +} diff --git a/src/c12_extend/extension_test.go b/src/c12_extend/extension_test.go new file mode 100644 index 0000000..38198b8 --- /dev/null +++ b/src/c12_extend/extension_test.go @@ -0,0 +1,68 @@ +package extension_test + +import ( + "fmt" + "testing" +) + +// golang是可以实现继承的,但是这种继承并不是严格意义上的继承, +// golang并不支持继承特性,因而也没有单继承,多继承,重写方法等复杂概念。 + +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("汪汪汪!") +} + +func TestDog(t *testing.T) { + dog := new(Dog) + dog.Speak() + // dog.SpeakTo("Zhang") Dog没有这个方法, 虽然Pet有,但是此时Dog不是匿名继承 ,如果要要调用需要指定继承事指定的名称。 + dog.p.Speak() + dog.p.SpeakTo("Li") +} + +// 匿名继承 + +type People struct { +} + +func (p *People) ShowA() { + fmt.Println("AAA") +} + +func (p *People) ShowB() { + fmt.Println("BBB") +} + +type Doctor struct { + People +} + +func (p *Doctor) ShowA() { + fmt.Println("Doctor AAA") +} + +func TestDoctor(t *testing.T) { + d := new(Doctor) + d.ShowA() + d.ShowB() // 因为Teacher没有ShowB()的方法,此时又匿名继承了People,所以会调到People实现的ShowB方法 + d.People.ShowA() + d.People.ShowB() +} diff --git a/src/c12_polymorphism/polymorphism_test.go b/src/c12_polymorphism/polymorphism_test.go new file mode 100644 index 0000000..b72e0c8 --- /dev/null +++ b/src/c12_polymorphism/polymorphism_test.go @@ -0,0 +1,41 @@ +package polymorphism + +import ( + "fmt" + "testing" +) + +// 多态 + +type Code string + +type Programmer interface { + WriteHelloWorld() Code +} + +type GoProgrammer struct { +} + +func (gp *GoProgrammer) WriteHelloWorld() Code { + return "fmt.Println(\"hello world\")" +} + +type JavaProgrammer struct { +} + +func (jp *JavaProgrammer) WriteHelloWorld() Code { + return "System.out.Println(\"hello world\")" +} + +func writeFirstProgram(p Programmer) { + // %T 输出类型 + fmt.Printf("%T %v\n", p, p.WriteHelloWorld()) +} + +func TestPolymorphism(t *testing.T) { + gp := new(GoProgrammer) + // gp := &GoProgrammer{} + jp := new(JavaProgrammer) + writeFirstProgram(gp) + writeFirstProgram(jp) +} diff --git a/src/c13_empty_interface/empty_interface_test.go b/src/c13_empty_interface/empty_interface_test.go new file mode 100644 index 0000000..7fe543b --- /dev/null +++ b/src/c13_empty_interface/empty_interface_test.go @@ -0,0 +1,40 @@ +package empty_interface + +import ( + "fmt" + "testing" +) + +// 空接口与断言 +// 1.空接口可以表示任何类型 +// 2.通过断言来将空接口转换为制定类型 + +// 空接口入参 +func DoSomething(p interface{}) { + // if i, ok := p.(int); ok { + // fmt.Println("Integer", i) + // return + // } + + // if s, ok := p.(string); ok { + // fmt.Println("String", s) + // return + // } + + // fmt.Println("Unknown Type") + switch v := p.(type) { + case int: + fmt.Println("Integer", v) + case string: + fmt.Println("String", v) + default: + fmt.Println("Unknown Type") + } +} + +// 测试空接口入参断言 +func TestEmptyInterfaceAssertion(t *testing.T) { + DoSomething(10) + DoSomething("1231") + DoSomething(8.88) +} diff --git a/src/c14_error_test/error_test.go b/src/c14_error_test/error_test.go new file mode 100644 index 0000000..528f0b3 --- /dev/null +++ b/src/c14_error_test/error_test.go @@ -0,0 +1,45 @@ +package error_test + +import ( + "errors" + "fmt" + "testing" +) + +/* + + Go的错误机制 + 1.没有异常机制 + 2.error类型实现了error接口 + 3.可以通过 errors.New 来快速创建错误实例 + +*/ + +var ErrorLessThanTwo = errors.New("n should be not less than 2") +var ErrorLargerThenHundred = errors.New("n should be not larger than 100") + +func GetFibonacci(n int) ([]int, error) { + if n < 2 { + return nil, ErrorLessThanTwo + } + if n > 100 { + return nil, ErrorLargerThenHundred + } + fibList := []int{1, 1} + for i := 2; i < n; i++ { + fibList = append(fibList, fibList[i-2]+fibList[i-1]) + } + return fibList, nil +} + +func TestGetFibonacci(t *testing.T) { + if ret, err := GetFibonacci(-10); err != nil { + if err == ErrorLessThanTwo { + fmt.Println("It is less.") + } + t.Error(err) + } else { + t.Log(ret) + } + +} diff --git a/src/c14_panic_recover/panic_recover_test.go b/src/c14_panic_recover/panic_recover_test.go new file mode 100644 index 0000000..720a970 --- /dev/null +++ b/src/c14_panic_recover/panic_recover_test.go @@ -0,0 +1,34 @@ +package panic_recover + +import ( + "errors" + "fmt" + "testing" +) + +// Let it Crash 往往是恢复不确定性错误的最好方法 +// panic 用于不可以恢复的错误 +// panic 退出前会执行 defer 指定的内容 + +// os.Exit 退出时不会调用 defer 指定的函数 +// os.Exit 退出时不输出当前调用栈信息 + +// 常见的错误恢复 recover() +// 当心 recover 成为"恶魔" 形成僵尸服务进程 导致health check失效 + +func TestPanicVxExit(t *testing.T) { + // defer func () { + // fmt.Println("Finally") + // }() + + defer func() { + // recover() 错误恢复 小心使用 + if err := recover(); err != nil { + fmt.Println("recovered from ", err) + } + }() + fmt.Println("Start") + panic(errors.New("Something wrong!")) + // os.Exit(-1) + // fmt.Println("end") +} diff --git a/src/c15_package_test/package_test.go b/src/c15_package_test/package_test.go new file mode 100644 index 0000000..51be2c3 --- /dev/null +++ b/src/c15_package_test/package_test.go @@ -0,0 +1,18 @@ +package client + +import ( + "go_study/src/c15_package_test/series" + "testing" +) + +// 可复用模块 包 +// 基本复用单元 以首字母大写来表明可被包外代码访问 +// 代码的 package 可以和所在的目录不一致 +// 同一目录里的 Go 代码的 package 要保持一致 + +// 测试复用方法 +func TestPackage(t *testing.T) { + t.Log(series.GetFibonacciSeries(5)) + + // t.Log(series.getSlice()) // 报错 无法访问 +} diff --git a/src/c15_package_test/series/my_series.go b/src/c15_package_test/series/my_series.go new file mode 100644 index 0000000..3686d90 --- /dev/null +++ b/src/c15_package_test/series/my_series.go @@ -0,0 +1,14 @@ +package series + +// GetFibonacciSeries 实现 斐波那契数列 +func GetFibonacciSeries(n int) []int { + fibList := []int{1, 1} + for i := 2; i < n; i++ { + fibList = append(fibList, fibList[i-2]+fibList[i-1]) + } + return fibList +} + +func getSlice() []int { + return []int{1, 2, 3, 4, 5} +} diff --git a/src/c16_groutine/groutine_test.go b/src/c16_groutine/groutine_test.go new file mode 100644 index 0000000..e263797 --- /dev/null +++ b/src/c16_groutine/groutine_test.go @@ -0,0 +1,18 @@ +package groutine_test + +import ( + "fmt" + "testing" + "time" +) + +// 测试协程机制 +func TestGroutine(t *testing.T) { + for i := 0; i < 10; i++ { + // 协程 + go func(i int) { + fmt.Println(i) + }(i) + } + time.Sleep(time.Millisecond * 50) +} diff --git a/src/c17_share_memory/share_mem_test.go b/src/c17_share_memory/share_mem_test.go new file mode 100644 index 0000000..e4eabf2 --- /dev/null +++ b/src/c17_share_memory/share_mem_test.go @@ -0,0 +1,89 @@ +package share_mem + +import ( + "fmt" + "sync" + "testing" + "time" +) + +// 共享内存并发机制 + +func TestCounter(t *testing.T) { + counter := 0 + for i := 0; i < 5000; i++ { + go func() { + // 不安全的写法 + counter++ + }() + } + time.Sleep(time.Second * 1) + t.Logf("counter= %d", counter) // 得到的结果会小于 5000 +} + +// Mutex的使用 +func TestCounterThread(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(time.Second * 1) //去掉 time.Sleep(), counter的结果不符合预期, 小于 5000 + t.Logf("counter= %d", counter) +} + +// 利用WaitGroup来确定所有任务是否结束 +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() //挂起 等待完成 + t.Logf("counter= %d", counter) +} + +var rwMutex *sync.RWMutex +var wg *sync.WaitGroup + +// 读写锁 +func TestRWMutex(t *testing.T) { + rwMutex = new(sync.RWMutex) + wg = new(sync.WaitGroup) + wg.Add(2) + go readData(1) + go readData(2) + + wg.Wait() + fmt.Println("func finish") +} + +func readData(i int) { + defer wg.Done() + fmt.Println(i, "start locking!") + // 给读操作 上锁 + rwMutex.RLock() + // 读数据 + fmt.Println(i, "Reading data") + // 休眠1s + time.Sleep(1 * time.Second) + // 读解锁 + rwMutex.RUnlock() + // 打印提示信息 + fmt.Println(i, "Read over") +} diff --git a/src/c18_csp/async_service_test.go b/src/c18_csp/async_service_test.go new file mode 100644 index 0000000..c22c45b --- /dev/null +++ b/src/c18_csp/async_service_test.go @@ -0,0 +1,41 @@ +package csp_test + +import ( + "fmt" + "testing" + "time" +) + +func service() string { + time.Sleep(time.Millisecond * 50) + return "Done" +} + +func otherTask() { + fmt.Println("执行一些其他的任务") + time.Sleep(time.Millisecond * 100) + fmt.Println("任务完成") +} + +func TestService(t *testing.T) { + fmt.Println(service()) + otherTask() +} + +func AsyncService() chan string { + retChan := make(chan string, 1) // buffered channel (非阻塞、异步模式) 不会阻塞service + // retChan := make(chan string) // unbuffered channel (阻塞、同步模式) 会阻塞service + go func() { + ret := service() + fmt.Println("返回service结果") + retChan <- ret + fmt.Println("service已退出") + }() + return retChan +} + +func TestAsyncService(t *testing.T) { + retCh := AsyncService() + otherTask() + fmt.Println(<-retCh) +} diff --git a/src/c19_select/select_test.go b/src/c19_select/select_test.go new file mode 100644 index 0000000..51b5640 --- /dev/null +++ b/src/c19_select/select_test.go @@ -0,0 +1,34 @@ +package select_test + +import ( + "fmt" + "testing" + "time" +) + +func service() string { + time.Sleep(time.Millisecond * 100) + return "Done" +} + +func AsyncService() chan string { + retChan := make(chan string, 1) // buffered channel (非阻塞、异步模式) 不会阻塞service + // retChan := make(chan string) // unbuffered channel (阻塞、同步模式) 会阻塞service + go func() { + ret := service() + fmt.Println("返回service结果") + retChan <- ret + fmt.Println("service已退出") + }() + return retChan +} + +// 多路选择 select 实现超时机制 +func TestSelect(t *testing.T) { + select { + case ret := <-AsyncService(): + t.Log(ret) + case <-time.After(time.Millisecond * 100): + t.Error("超时") + } +} diff --git a/src/c1_hello_world/hello_world.go b/src/c1_hello_world/hello_world.go new file mode 100644 index 0000000..860afa0 --- /dev/null +++ b/src/c1_hello_world/hello_world.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "os" +) + +// Go中 main 函数不支持任何返回值 +// main 函数不支持传入参数 +func main() { + // 通过 os.Args 获取命令行参数 + fmt.Println(os.Args) + fmt.Println("Hello Golang!") + os.Exit(0) // 通过os.Exit来返回状态 +} + +// package 必须是 main +// 方法名必须是 main() +// 文件名不一定是 main.go diff --git a/src/c20_cancel_by_close/cancel_test.go b/src/c20_cancel_by_close/cancel_test.go new file mode 100644 index 0000000..39b2f48 --- /dev/null +++ b/src/c20_cancel_by_close/cancel_test.go @@ -0,0 +1,43 @@ +package cancel_test + +import ( + "fmt" + "testing" + "time" +) + +func isCancelled(cancelChan chan struct{}) bool { + select { + case <-cancelChan: + return true + default: + return false + } +} + +func cancel1(cancelChan chan struct{}) { + cancelChan <- struct{}{} +} + +func cancel2(cancelChan chan struct{}) { + close(cancelChan) +} + +// 使用close来取消任务 +func TestCancel(t *testing.T) { + cancelChan := make(chan struct{}, 0) + for i := 0; i < 5; i++ { + go func(i int, cancelChan chan struct{}) { + for { + if isCancelled(cancelChan) { + break + } + time.Sleep(time.Millisecond * 5) + } + fmt.Println(i, "Done") + }(i, cancelChan) + } + // cancel1(cancelChan) + cancel2(cancelChan) + time.Sleep(time.Second * 1) +} diff --git a/src/c20_cancel_by_context/cancel_by_context_test.go b/src/c20_cancel_by_context/cancel_by_context_test.go new file mode 100644 index 0000000..4b33d5c --- /dev/null +++ b/src/c20_cancel_by_context/cancel_by_context_test.go @@ -0,0 +1,35 @@ +package cancel_by_context_test + +import ( + "context" + "fmt" + "testing" + "time" +) + +func isCancelled(ctx context.Context) bool { + select { + case <-ctx.Done(): + return true + default: + return false + } +} + +// 使用context来取消任务 +func TestCancel(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + 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, "Done") + }(i, ctx) + } + cancel() + time.Sleep(time.Second * 1) +} diff --git a/src/c20_channel_close/channel_close_test.go b/src/c20_channel_close/channel_close_test.go new file mode 100644 index 0000000..6ff846a --- /dev/null +++ b/src/c20_channel_close/channel_close_test.go @@ -0,0 +1,56 @@ +package channel_close_test + +import ( + "fmt" + "sync" + "testing" +) + +// 生产者 +func dataProducer(ch chan int, wg *sync.WaitGroup) { + go func() { + for i := 0; i < 10; i++ { + ch <- i + } + close(ch) // 关闭channel + wg.Done() + }() +} + +// 消费者 +// func dataReceiver(ch chan int, wg *sync.WaitGroup) { +// go func() { +// for i := 0; i < 10; i++ { +// data := <-ch +// fmt.Println(data) +// } +// wg.Done() +// }() +// } + +func dataReceiver(ch chan int, wg *sync.WaitGroup) { + go func() { + for { + if data, ok := <-ch; ok { + fmt.Println(data) + } else { + // 通道关闭 去获取值会立即返回通道定义类型的默认值 + fmt.Println(data) + 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.Add(1) + dataReceiver(ch, &wg) + wg.Wait() +} diff --git a/src/c21_singleton/once_test.go b/src/c21_singleton/once_test.go new file mode 100644 index 0000000..5f610c9 --- /dev/null +++ b/src/c21_singleton/once_test.go @@ -0,0 +1,37 @@ +package once_test + +import ( + "fmt" + "sync" + "testing" + "unsafe" +) + +// 利用sync.Once实现 单例模式 + +type Singleton struct { +} + +var singletonInstance *Singleton +var once sync.Once + +func GetSingletonObj() *Singleton { + once.Do(func() { + fmt.Println("创建对象") + singletonInstance = new(Singleton) + }) + return singletonInstance +} + +func TestGetSingletonObj(t *testing.T) { + var wg sync.WaitGroup + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + obj := GetSingletonObj() + t.Logf("%x", unsafe.Pointer(obj)) + wg.Done() + }() + } + wg.Wait() +} diff --git a/src/c22_util_anyone_reply/first_response_test.go b/src/c22_util_anyone_reply/first_response_test.go new file mode 100644 index 0000000..9417d5b --- /dev/null +++ b/src/c22_util_anyone_reply/first_response_test.go @@ -0,0 +1,33 @@ +package util_anyone_reply + +import ( + "fmt" + "runtime" + "testing" + "time" +) + +func runTask(id int) string { + time.Sleep(time.Millisecond * 10) + return fmt.Sprintf("结果来自: %d", id) +} + +func FirstResponse() string { + numOfRunner := 10 + // 使用 buffered channel (非阻塞、异步模式) 来防止协程泄露 + ch := make(chan string, numOfRunner) + for i := 0; i < numOfRunner; i++ { + go func(i int) { + ret := runTask(i) + ch <- ret + }(i) + } + return <-ch +} + +func TestFirstResponse(t *testing.T) { + t.Log("Before 当前系统协程数: ", runtime.NumGoroutine()) + t.Log(FirstResponse()) + time.Sleep(time.Second * 1) + t.Log("After 当前协程数: ", runtime.NumGoroutine()) +} diff --git a/src/c23_util_all_done/all_done_test.go b/src/c23_util_all_done/all_done_test.go new file mode 100644 index 0000000..ef9834a --- /dev/null +++ b/src/c23_util_all_done/all_done_test.go @@ -0,0 +1,36 @@ +package util_all_done + +import ( + "fmt" + "runtime" + "testing" + "time" +) + +func runTask(id int) string { + time.Sleep(time.Millisecond * 10) + return fmt.Sprintf("结果来自: %d", id) +} + +func AllResponse() string { + numOfRunner := 10 + ch := make(chan string, numOfRunner) + for i := 0; i < numOfRunner; i++ { + go func(i int) { + ret := runTask(i) + ch <- ret + }(i) + } + finalRet := "" + for j := 0; j < numOfRunner; j++ { + finalRet += <-ch + "\n" + } + return finalRet +} + +func TestAllResponse(t *testing.T) { + t.Log("Before 当前系统协程数: ", runtime.NumGoroutine()) + t.Log(AllResponse()) + time.Sleep(time.Second * 1) + t.Log("After 当前协程数: ", runtime.NumGoroutine()) +} diff --git a/src/c24_obj_pool/obj_pool.go b/src/c24_obj_pool/obj_pool.go new file mode 100644 index 0000000..ac7d1de --- /dev/null +++ b/src/c24_obj_pool/obj_pool.go @@ -0,0 +1,42 @@ +package obj_pool + +import ( + "errors" + "time" +) + +type MyObj struct { +} + +type ObjPool struct { + buffChan chan *MyObj +} + +// 利用channel实现的对象池 + +func NewObjPool(num int) *ObjPool { + objPool := ObjPool{} + objPool.buffChan = make(chan *MyObj, num) + for i := 0; i < num; i++ { + objPool.buffChan <- &MyObj{} + } + return &objPool +} + +func (p *ObjPool) GetObj(timeout time.Duration) (*MyObj, error) { + select { + case ret := <-p.buffChan: + return ret, nil + case <-time.After(timeout): // 超时控制 + return nil, errors.New("超时") + } +} + +func (p *ObjPool) PutObj(obj *MyObj) error { + select { + case p.buffChan <- obj: + return nil + default: + return errors.New("超出对象池最大容量") + } +} diff --git a/src/c24_obj_pool/obj_pool_test.go b/src/c24_obj_pool/obj_pool_test.go new file mode 100644 index 0000000..b93e9d3 --- /dev/null +++ b/src/c24_obj_pool/obj_pool_test.go @@ -0,0 +1,27 @@ +package obj_pool + +import ( + "fmt" + "testing" + "time" +) + +func TestObjPool(t *testing.T) { + num := 10 + pool := NewObjPool(num) + //if err := pool.PutObj(&MyObj{}); err != nil { // 尝试防止超出池大小的对象 + // t.Error(err) + //} + for i := 0; i < num; i++ { + if obj, err := pool.GetObj(time.Second * 1); err != nil { + t.Error(err) + } else { + fmt.Printf("%T\n", obj) + if err := pool.PutObj(obj); err != nil { + t.Error(err) + } + } + } + + t.Log("Done.") +} diff --git a/src/c25_obj_cache/README.MD b/src/c25_obj_cache/README.MD new file mode 100644 index 0000000..871178d --- /dev/null +++ b/src/c25_obj_cache/README.MD @@ -0,0 +1,22 @@ +### sync.Pool 对象获取 + +- 尝试从私有对象获取 +- 私有对象不存在, 尝试从当前Processor的共享池获取 +- 如果当前Processor共享池也是空的,那么就尝试去其他Processor的共享池获取 +- 如果所有子池都是空的, 最后就用用户指定的New函数产生一个新的对象返回 + +### sync.Pool 对象的生命周期 + +- GC会清除sync.pool缓存的对象 +- 对象的缓存有效期为下一次GC之前 + +### sync.Pool对象的放回 + +- 如果私有对象不存在则保存为私有对象 +- 如果私有对象存在, 放入当前Processor子池的共享池中 + +### sync.Pool总结 + +- 适合于通过复用, 降低复杂对象的创建和GC代价 +- 协程安全, 会有锁的开销 +- 生命周期受GC影响, 不适合于做连接池等, 需自己管理生命周期的资源的池化 \ No newline at end of file diff --git a/src/c25_obj_cache/sync_pool_test.go b/src/c25_obj_cache/sync_pool_test.go new file mode 100644 index 0000000..80dd8a7 --- /dev/null +++ b/src/c25_obj_cache/sync_pool_test.go @@ -0,0 +1,51 @@ +package obj_cache + +import ( + "runtime" + "sync" + "testing" +) + +func TestSyncPool(t *testing.T) { + pool := &sync.Pool{ + New: func() interface{} { + t.Log("Create a new object.") + return 100 + }, + } + + v := pool.Get().(int) + t.Log(v) + pool.Put(3) + + v1 := pool.Get().(int) + t.Log("put操作后: ", v1) + runtime.GC() // GC 会清除sync.pool中缓存的对象 + v2, _ := pool.Get().(int) + t.Log("GC操作后: ", v2) + +} + +// 协程测试 +func TestSyncPoolInMultiGroutine(t *testing.T) { + pool := &sync.Pool{ + New: func() interface{} { + t.Log("create object") + return 10 + }, + } + + pool.Put(100) + pool.Put(100) + pool.Put(100) + + var wg sync.WaitGroup + for i := 0; i < 10; i++ { + wg.Add(1) + go func(id int) { + t.Log("从pool获取对象: ", pool.Get()) + wg.Done() + }(i) + } + wg.Wait() +} diff --git a/src/c26_unit_test/functions.go b/src/c26_unit_test/functions.go new file mode 100644 index 0000000..280c39d --- /dev/null +++ b/src/c26_unit_test/functions.go @@ -0,0 +1,5 @@ +package unit_test + +func Square(i int) int { + return i * i +} diff --git a/src/c26_unit_test/functions_test.go b/src/c26_unit_test/functions_test.go new file mode 100644 index 0000000..9657a9b --- /dev/null +++ b/src/c26_unit_test/functions_test.go @@ -0,0 +1,46 @@ +package unit_test + +import ( + "fmt" + "testing" + + "github.com/go-playground/assert/v2" +) + +// 表格测试法 +func TestSquare(t *testing.T) { + // 输入值 + inputs := [...]int{1, 2, 3} + // 期待值 + expected := [...]int{1, 4, 9} + for i := 0; i < len(inputs); i++ { + ret := Square(inputs[i]) + if ret != expected[i] { + t.Errorf("input is %d, the expected is %d, the actual %d", inputs[i], expected[i], ret) + } + } +} + +func TestErrorInCode(t *testing.T) { + fmt.Println("Start") + t.Error("Error") // 程序继续执行 + fmt.Println("End") +} + +func TestFatalInCode(t *testing.T) { + fmt.Println("Start") + t.Fatal("Error") // 程序会中断 不会输出End + fmt.Println("End") +} + +// 使用断言Assert测试 +func TestSquareWithAssert(t *testing.T) { + // 输入值 + inputs := [...]int{1, 2, 3} + // 期待值 + expected := [...]int{1, 4, 9} + for i := 0; i < len(inputs); i++ { + ret := Square(inputs[i]) + assert.Equal(t, expected[i], ret) + } +} diff --git a/src/c2_const_test/constant_test.go b/src/c2_const_test/constant_test.go new file mode 100644 index 0000000..6570e67 --- /dev/null +++ b/src/c2_const_test/constant_test.go @@ -0,0 +1,36 @@ +package constant_test + +import "testing" + +// 声明常量 +const ( + // 连续常量写法 借助 iota + Monday = iota + 1 + Tuesday + Wednesday +) + +const ( + // 位运算 + Readable = 1 << iota + Writeable + Executable +) + +const ( + Cat = 1 + Dog = 2 + Pig = 3 +) + +func TestConstant(t *testing.T) { + t.Log(Monday, Tuesday) +} + +func TestConstant1(t *testing.T) { + a := 1 + t.Log(a&Readable, a&Writeable, a&Executable) + + t.Log(a&Readable == Readable, a&Writeable == Writeable, a&Executable == Executable) + +} diff --git a/src/c2_const_test/fib_test/fib_test.go b/src/c2_const_test/fib_test/fib_test.go new file mode 100644 index 0000000..6127775 --- /dev/null +++ b/src/c2_const_test/fib_test/fib_test.go @@ -0,0 +1,47 @@ +package fib + +import ( + "testing" +) + +func TestFibList(t *testing.T) { + // var a int = 1 + // var b int = 1 + + // var ( + // a int = 1 + // b int = 1 + // ) + + a := 1 + b := 1 + t.Log(a) + for i := 0; i < 5; i++ { + t.Log(b) + tmp := a + a = b + b = tmp + a + } +} + +func TestExchange(t *testing.T) { + // 其他编程语言常见写法 借助中间变量tmp + a := 1 + b := 2 + tmp := a + a = b + b = tmp + t.Log(a, b) + + // go 值替换 + a, b = b, a + t.Log(a, b) +} + +func TestDemo(t *testing.T) { + a := "123" + b := "321" + t.Log(&a, &b) + a, b = b, a + t.Log(&a, &b, a, b) +} diff --git a/src/c2_const_test/first_test.go b/src/c2_const_test/first_test.go new file mode 100644 index 0000000..e9b01ee --- /dev/null +++ b/src/c2_const_test/first_test.go @@ -0,0 +1,9 @@ +package constant_test + +import ( + "testing" +) + +func TestFirst(t *testing.T) { + t.Log("我的第一个go测试程序!") +} diff --git a/src/c3_type_test/type_test.go b/src/c3_type_test/type_test.go new file mode 100644 index 0000000..31fac0d --- /dev/null +++ b/src/c3_type_test/type_test.go @@ -0,0 +1,39 @@ +package tpye_test + +import ( + "math" + "testing" +) + +// 自定义类型 +type MyInt int64 + +// 类型转换 +func TestImplicit(t *testing.T) { + var a int32 = 1 + // 显示类型转换 + var b = int64(a) + var c = MyInt(b) + t.Log(a, b, c) +} + +// 指针 +func TestPoint(t *testing.T) { + a := 1 + aPtr := &a + t.Log(a, aPtr) + t.Logf("%T %T", a, aPtr) +} + +func TestString(t *testing.T) { + // 不赋值默认空字符串 + var s string + t.Log("*" + s + "*") + t.Log(len(s)) + t.Log(s == "") +} + +func TestMax(t *testing.T) { + t.Log(math.MaxFloat64) + t.Log(math.MaxInt64) +} diff --git a/src/c4_operator_test/operator_test.go b/src/c4_operator_test/operator_test.go new file mode 100644 index 0000000..cd4bd82 --- /dev/null +++ b/src/c4_operator_test/operator_test.go @@ -0,0 +1,30 @@ +package operator_test + +import "testing" + +// 数组比较 +func TestCompareArray(t *testing.T) { + a := [...]int{1, 2, 3, 4} + b := [...]int{1, 2, 3, 5} + d := [...]int{1, 2, 3, 4} + t.Log(a == b) + //c := [...]int{1, 2, 3, 4, 5} + //t.Log(a == c) //报错 长度不同不能比较 + t.Log(a == d) + +} + +const ( + // 位运算 + Readable = 1 << iota + Writeable + Executable +) + +func TestBitClear(t *testing.T) { + a := 7 + a = a &^ Readable + a = a &^ Executable + t.Log(a&Readable, a&Writeable, a&Executable) + t.Log(a&Readable == Readable, a&Writeable == Writeable, a&Executable == Executable) +} diff --git a/src/c5_condition/condition_test.go b/src/c5_condition/condition_test.go new file mode 100644 index 0000000..18b4dd3 --- /dev/null +++ b/src/c5_condition/condition_test.go @@ -0,0 +1,40 @@ +package condition_test + +import "testing" + +// if +func TestIfMultiSec(t *testing.T) { + if a := 1 == 1.0; a { + t.Log("1 == 1") + } else { + t.Log("!=") + } +} + +// switch +func TestSwitchMultiCase(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") + } + } +} + +// switch2 +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("it is default case") + } + } +} diff --git a/src/c6_array_test/array_test.go b/src/c6_array_test/array_test.go new file mode 100644 index 0000000..4685fae --- /dev/null +++ b/src/c6_array_test/array_test.go @@ -0,0 +1,39 @@ +package array_test + +import "testing" + +// 数组声明 +func TestArrayInit(t *testing.T) { + var arr [3]int + arr1 := [4]int{1, 2, 3, 4} + arr3 := [...]int{1, 3, 4, 5} + arr1[1] = 5 + t.Log(arr[1], arr[2]) + t.Log(arr1, arr3) +} + +// 数组遍历 +func TestArrayTravel(t *testing.T) { + arr3 := [...]int{1, 3, 4, 5} + for i := 0; i < len(arr3); i++ { + t.Log(arr3[i]) + } + + for idx, v := range arr3 { + t.Log(idx, v) + } + + for _, v := range arr3 { + t.Log(v) + } +} + +// 数组截断 +func TestArraySection(t *testing.T) { + arr3 := [...]int{1, 2, 3, 4, 5} + // 截取 arr[开始索引(包含), 结束索引(不包含)] + arr3_sec := arr3[1:4] + t.Log(arr3_sec) //2.3.4 + t.Log(arr3[1:]) //2,3,4,5 + t.Log(arr3[:3]) //1,2,3 +} diff --git a/src/c6_loop/loop_test.go b/src/c6_loop/loop_test.go new file mode 100644 index 0000000..a8ab636 --- /dev/null +++ b/src/c6_loop/loop_test.go @@ -0,0 +1,12 @@ +package loop_test + +import "testing" + +// for循环 +func TestWhileLoop(t *testing.T) { + n := 0 + for n < 5 { + t.Log(n) + n++ + } +} diff --git a/src/c6_slice_test/slice_test.go b/src/c6_slice_test/slice_test.go new file mode 100644 index 0000000..33808cf --- /dev/null +++ b/src/c6_slice_test/slice_test.go @@ -0,0 +1,51 @@ +package slice_test + +import "testing" + +// 切片定义 +func TestSlienceInit(t *testing.T) { + var s0 []int + t.Log(len(s0), cap(s0)) + s0 = append(s0, 1) + t.Log(len(s0), cap(s0)) + + s1 := []int{1, 2, 3, 4} + t.Log(len(s1), cap(s1)) + + s2 := make([]int, 3, 5) + t.Log(len(s2), cap(s2)) + t.Log(s2[0], s2[1], s2[2]) + s2 = append(s2, 4) + t.Log(len(s2), cap(s2)) + t.Log(s2[0], s2[1], s2[2], s2[3]) +} + +// 切片增长演示 +func TestSliceGrowing(t *testing.T) { + s := []int{} + for i := 0; i < 10; i++ { + s = append(s, i) + t.Log(len(s), cap(s)) + } +} + +// 切片的共享内存 演示 +func TestSliceShareMemory(t *testing.T) { + letter := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "L"} + letter2 := letter[3:6] + t.Log(letter2, len(letter2), cap(letter2)) + l3 := letter[5:8] + t.Log(l3, len(l3), cap(l3)) + l3[0] = "U" + t.Log(letter2) + t.Log(letter) +} + +// 切片不能比较 +func TestSliceCompare(t *testing.T) { + // a := []int{1, 2, 3, 4} + // b := []int{1, 2, 3, 4} + // if a == b { + // t.Log("equals") + // } +} diff --git a/src/c7_map_test/map_test.go b/src/c7_map_test/map_test.go new file mode 100644 index 0000000..0b6e90e --- /dev/null +++ b/src/c7_map_test/map_test.go @@ -0,0 +1,50 @@ +package map_test + +import ( + "testing" +) + +// map的声明 +func TestMapInit(t *testing.T) { + m1 := map[string]int{"a": 1, "b": 2, "c": 3} + t.Log(m1["b"]) + t.Logf("len m1=%d", len(m1)) + + m2 := map[string]int{} + m2["d"] = 4 + t.Logf("len m2=%d", len(m2)) + m3 := make(map[string]int, 10) + t.Logf("len m3=%d", len(m3)) +} + +// map判断键key是否存在 +func TestAccessNotExistingKey(t *testing.T) { + m1 := map[int]int{} + // 不存在会默认为0 而不是nil + t.Log(m1[1]) + m1[2] = 0 + t.Log(m1[2]) + + if v, ok := m1[3]; ok { + t.Logf("Key 3 value is %d", v) + } else { + t.Log("Key 3 is not exist") + } + + // 赋值后 + m1[3] = 0 + if v, ok := m1[3]; ok { + t.Logf("Key 3 value is %d", v) + } else { + t.Log("Key 3 is not exist") + } + +} + +// 遍历Map +func TestTravelMap(t *testing.T) { + m1 := map[string]int{"a": 1, "b": 2, "c": 3} + for k, v := range m1 { + t.Logf("Key: %s, Value: %d", k, v) + } +} diff --git a/src/c8_map_ext/map_ext_test.go b/src/c8_map_ext/map_ext_test.go new file mode 100644 index 0000000..86fa062 --- /dev/null +++ b/src/c8_map_ext/map_ext_test.go @@ -0,0 +1,48 @@ +package map_ext + +import ( + "fmt" + "testing" +) + +// 值为方法的map +func TestMapWithFunValue(t *testing.T) { + m := map[int]func(op int) int{} + m[1] = func(op int) int { return op } + m[2] = func(op int) int { return op * op } + m[3] = func(op int) int { return op + op + op } + t.Log(m[1](2), m[2](2), m[3](2)) +} + +// Map实现Set操作 +func TestMapForSet(t *testing.T) { + mySet := map[int]bool{} + + // 添加元素 + mySet[1] = true + mySet[2] = false + t.Logf("set len=%d", len(mySet)) + + // 判断元素是否存在 + n := 3 + if mySet[n] { + t.Logf("%d is exist", n) + } else { + t.Logf("%d is not exist", n) + } + + //删除元素 + delete(mySet, 1) + ValueIsExit(mySet, 1) + +} + +func ValueIsExit(s map[int]bool, v int) bool { + if s[v] { + fmt.Printf("%d is exist", v) + return true + } else { + fmt.Printf("%d is not exist", v) + return false + } +} diff --git a/src/c9_string/string_func_test.go b/src/c9_string/string_func_test.go new file mode 100644 index 0000000..3e4fdd4 --- /dev/null +++ b/src/c9_string/string_func_test.go @@ -0,0 +1,31 @@ +package string_test + +import ( + "strconv" + "strings" + "testing" +) + +func TestStringFn(t *testing.T) { + s := "A,B,C" + // 切割 + parts := strings.Split(s, ",") + for _, v := range parts { + t.Log(v) + } + + // 连接 + t.Log(strings.Join(parts, "-")) +} + +func TestStrConv(t *testing.T) { + // int to string + s := strconv.Itoa(10) + t.Log(s) + + // string to int + if i, err := strconv.Atoi("10"); err == nil { + t.Log(10 + i) + } + +} diff --git a/src/c9_string/string_test.go b/src/c9_string/string_test.go new file mode 100644 index 0000000..f11e3ea --- /dev/null +++ b/src/c9_string/string_test.go @@ -0,0 +1,29 @@ +package string_test + +import "testing" + +func TestString(t *testing.T) { + var s string + t.Log(s) //初始化值为空字符串 "" + s = "hello" + t.Log(len(s)) + + s = "\xE4\xB8\xA5" // 可以存储任何二进制数据 + // s = "\xE4\xBA\xB5\xFF" + t.Log(s) + + s = "中" + t.Log(len(s)) // 是byte数 + + c := []rune(s) + t.Log(len(c)) + t.Logf("中 unicode %x", c[0]) + t.Logf("中 UTF8 %x", s) +} + +func TestStringToRune(t *testing.T) { + s := "中华人民共和国" + for _, c := range s { + t.Logf("%[1]c %[1]d, %[1]x", c) + } +}