关于go:Go-快速入门指南-写文件

概述调用 os 包即可。倡议先浏览 创立文件。 例子间接写入package mainimport "os"func main() { code := `package mainfunc main() { println("hello world")}` err := os.WriteFile("/tmp/test_main.go", []byte(code), 0755) if err != nil { panic(err) }}// $ go run main.go// cat /tmp/test_main.go// 输入如下 /** package main func main() { println("hello world") }*/先获取文件句柄,而后写入package mainimport ( "fmt" "os")func main() { file, err := os.OpenFile("/tmp/test_main.go", os.O_RDWR, 0755) if err != nil { panic(err) } // 记得敞开文件句柄 defer func() { err = file.Close() if err != nil { panic(err) } }() code := `package mainfunc main() { println("hello world")}` n, err := file.WriteString(code) if err != nil { panic(err) } err = file.Sync() // 同步到硬盘 if err != nil { panic(err) } fmt.Printf("%d characters were successfully written\n", n)}// $ go run main.go// 输入如下 /** 55 characters were successfully written*/分割我 ...

December 25, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-创建-删除文件

概述调用 os 包即可。 例子创立文件package mainimport ( "fmt" "os")func main() { file, err := os.Create("/tmp/test_main.go") if err != nil { panic(err) } // 记得敞开文件句柄 defer func() { err = file.Close() if err != nil { panic(err) } }() fmt.Printf("file name is %s\n", file.Name())}// $ go run main.go// 输入如下 /** file name is /tmp/test_main.go*/删除文件package mainimport ( "fmt" "os")func main() { file, err := os.Create("/tmp/test_main.go.bak") if err != nil { panic(err) } // 记得敞开文件句柄 defer func() { err = file.Close() if err != nil { panic(err) } }() fmt.Printf("file name is %s\n", file.Name()) err = os.Remove("/tmp/test_main.go.bak") if err != nil { panic(err) } else { fmt.Printf("%s has been deleted\n", file.Name()) }}// $ go run main.go// 输入如下 /** file name is /tmp/test_main.go.bak /tmp/test_main.go.bak has been deleted*/分割我 ...

December 25, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-原子操作

概述调用 sync/atomic 包即可。 谬误的并发操作先来看一个谬误的示例。 通过启动 1000 个 goroutine 来模仿并发调用,在函数外部对变量 number 进行自增操作,那么可能存在的一个问题是,当多个 goroutine 同时对变量操作时,只有一个胜利了,其余的全副失败,造成的结果就是变量最终的值小于 1000 (失常状况应该是等于 1000)。 package mainimport ( "fmt" "sync")func main() { var number uint32 var wg sync.WaitGroup for i := 0; i < 1000; i++ { wg.Add(1) go func() { defer func() { wg.Done() }() number++ }() } wg.Wait() fmt.Printf("number = %d\n", number)}// $ go run main.go// 输入如下,你的输入可能和这里的不一样,多试几次,会发现每次的后果都不一样/** number = 971*/正确的并发操作package mainimport ( "fmt" "sync" "sync/atomic")func main() { var number uint32 var wg sync.WaitGroup for i := 0; i < 1000; i++ { wg.Add(1) go func() { defer func() { wg.Done() }() //number++ atomic.AddUint32(&number, 1) // 应用原子操作 }() } wg.Wait() fmt.Printf("number = %d\n", number)}// $ go run main.go// 输入如下,多试几次,会发现后果都是一样的/** number = 1000*/分割我 ...

December 25, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-定时器

概述调用 time.NewTicker() 办法即可。 例子package mainimport ( "fmt" "time")func main() { ticker := time.NewTicker(time.Second) defer ticker.Stop() done := make(chan bool) go func() { time.Sleep(5 * time.Second) // 模仿耗时操作 done <- true }() for { select { case <-done: fmt.Println("Done!") return case <-ticker.C: fmt.Println(time.Now().Format("2006-01-02 15:04:05")) } }}// $ go run main.go// 输入如下,你的输入可能和这里的不一样/** 2021-01-03 15:40:21 2021-01-03 15:40:22 2021-01-03 15:40:23 2021-01-03 15:40:24 2021-01-03 15:40:25 Done!*/分割我

December 25, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-超时控制

概述利用 channel (通道) 和 time.After() 办法实现超时管制。 例子package mainimport ( "fmt" "time")func main() { ch := make(chan bool) go func() { defer func() { ch <- true }() time.Sleep(2 * time.Second) // 模仿超时操作 }() select { case <-ch: fmt.Println("ok") case <-time.After(time.Second): fmt.Println("timeout!") }}// $ go run main.go// 输入如下/** timeout!*/分割我

December 25, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-互斥锁

概述对于任一共享资源,同一时间保障只有一个操作者,这种办法称为 互斥机制。 关键字 Mutex 示意互斥锁类型,它的 Lock 办法用于获取锁,Unlock 办法用于开释锁。在 Lock 和 Unlock 之间的代码,能够读取和批改共享资源,这部分区域称为 临界区。 谬误的并发操作先来看一个谬误的示例。 在 Map 大节中讲到, Map 不是并发平安的, 也就是说,如果在多个线程中,同时对一个 Map 进行读写,会报错。当初来验证一下, 通过启动 100 个 goroutine 来模仿并发调用,每个 goroutine 都对 Map 的 key 进行设置。 package mainimport "sync"func main() { m := make(map[int]bool) var wg sync.WaitGroup for j := 0; j < 100; j++ { wg.Add(1) go func(key int) { defer func() { wg.Done() }() m[key] = true // 对 Map 进行并发写入 }(j) } wg.Wait()}// $ go run main.go// 输入如下,报错信息/** fatal error: concurrent map writes fatal error: concurrent map writes goroutine 104 [running]: main.main.func1(0x0?) /home/codes/Go-examples-for-beginners/main.go:18 +0x66 created by main.main /home/codes/Go-examples-for-beginners/main.go:13 +0x45 goroutine 1 [semacquire]: sync.runtime_Semacquire(0xc0000112c0?) /usr/local/go/src/runtime/sema.go:62 +0x25 sync.(*WaitGroup).Wait(0x60?) /usr/local/go/src/sync/waitgroup.go:139 +0x52 main.main() /home/codes/Go-examples-for-beginners/main.go:22 +0x105 ... ... ...*/通过输入信息 fatal error: concurrent map writes 能够看到,并发写入 Map 的确会报错。 ...

December 25, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-select

概述select 相似 switch, 蕴含一系列逻辑分支和一个可选的默认分支。每一个分支对应通道上的一次操作 (发送或接管),能够将 select 了解为专门针对通道操作的 switch 语句。 语法规定select {case v1 := <- ch1:// do something ... case v2 := <- ch2:// do something ...default:// do something ...}执行程序当同时存在多个满足条件的通道时,随机抉择一个执行如果没有满足条件的通道时,检测是否存在 default 分支 如果存在则执行否则阻塞期待通常状况下,把含有 default 分支 的 select 操作称为 无阻塞通道操作。 例子随机执行一个package mainimport ( "fmt" "time")func main() { ch1 := make(chan string) ch2 := make(chan string) done := make(chan bool) go func() { ch1 <- "hello" }() go func() { ch2 <- "world" }() go func() { done <- true }() time.Sleep(time.Second) // 休眠 1 秒 // 此时 3 个通道应该都满足条件,select 会随机抉择一个执行 select { case msg := <-ch1: fmt.Printf("ch1 msg = %s\n", msg) case msg := <-ch2: fmt.Printf("ch2 msg = %s\n", msg) case <-done: fmt.Println("done !") } close(ch1) close(ch2) close(done)}// $ go run main.go// 输入如下,你的输入可能和这里的不一样, 多运行几次看看成果/** ch1 msg = hello*/default (无阻塞通道操作)package mainimport ( "fmt" "time")func main() { ch1 := make(chan string) ch2 := make(chan string) done := make(chan bool) go func() { time.Sleep(time.Second) ch1 <- "hello" }() go func() { time.Sleep(time.Second) ch2 <- "world" }() go func() { time.Sleep(time.Second) done <- true }() // 此时 3 个通道都在休眠中, 不满足条件,select 会执行 default 分支 select { case msg := <-ch1: fmt.Printf("ch1 msg = %s\n", msg) case msg := <-ch2: fmt.Printf("ch2 msg = %s\n", msg) case <-done: fmt.Println("done !") default: fmt.Println("default !") } close(ch1) close(ch2) close(done)}// $ go run main.go// 输入如下/** default !*/和 for 搭配应用通过在 select 外层加一个 for 循环,能够达到 有限轮询 的成果。 ...

December 25, 2022 · 2 min · jiezi

关于go:Go-120要来了看看都有哪些变化第3篇

前言Go官网团队在2022.12.08公布了Go 1.20 rc1(release candidate)版本,Go 1.20的正式release版本预计会在2023年2月份公布。 让咱们先睹为快,看看Go 1.20给咱们带来了哪些变动。 装置办法: $ go install golang.org/dl/go1.20rc1@latest$ go1.20rc1 download这是Go 1.20版本更新内容详解的第3篇,欢送大家关注公众号,及时获取本系列最新更新。 Go 1.20公布清单和Go 1.19相比,改变内容适中,次要波及语言(Language)、可移植性(Ports)、工具链(Go Tools)、运行时(Runtime)、编译器(Compiler)、汇编器(Assembler)、链接器(Linker)和外围库(Core library)等方面的优化。 第1篇次要波及Go 1.20在语言、可移植性方面的优化,原文链接:Go 1.20版本升级内容第1篇。 第2篇次要波及Go命令和工具链方面的优化,原文链接:Go 1.20版本升级内容第2篇。 本文重点介绍Go 1.20在运行时、编译器、汇编器和链接器等方面的优化。 RuntimeGo 1.20版本的运行时新增了arena内存调配这个新性能的实验性反对,能够让Go程序开释更多内存空间,节俭内存占用。 想理解什么是arena内存调配的,能够参考:https://github.com/golang/go/...。 如果area内存调配应用失当,对于须要频繁内存调配的利用,能够晋升多达15%的CPU性能。 应用形式为编译Go程序时,增加 GOEXPERIMENT=arenas参数。代码里如果有import arena,也须要增加这个编译参数。 此外,垃圾回收器的一些外部数据结构的设计做了优化,在工夫和空间上更高效,能够节俭内存开销,晋升2%左右的CPU总体性能。 Go 1.20还新增了一个runtime/coverage包,调用这个包的API能够把程序运行的代码覆盖率数据输入到指定文件。 CompilerGo 1.20新增了PGO(profile-guided optimization)个性,能够帮忙开发者做程序性能优化。 目前,编译器反对pprof CPU profile,这种类型的profile能够通过例如runtime/pprof或net/http/pprof收集失去。 如果要开启PGO,在应用go build编译程序的时候,要减少-pgo参数。-pgo指定的是profile文件的门路。如果-pgo=auto,那go命令会在main这个包的门路上来找名为default.pgo的文件。-pgo=off能够敞开优化。详情能够参考:PGO Proposal。 如果应用了PGO,编译器会对被调用比拟多的函数,更多地应用inline function的形式去做性能优化。 性能测试表明,如果开启了profile-guided inlining optimization,能够晋升3%-4%的性能,前期Go会退出更多的PGO优化反对。 留神,因为PGO并不是稳固版本,生产环境应用须要小心。 此外,从Go 1.20开始,编译器禁止匿名interface嵌套,如下代码会编译失败。 type I interface { m() interface { I }}Linker在Linux环境,链接器会在进行link操作的时候,为glibc或者musl抉择动静解释器。 在Windows环境,链接器当初反对基于LLVM的C语言工具链。 Go 1.20版本开始,应用go:和type:作为前缀,用于编译器生成的符号,而摈弃应用go.和type.作为前缀。 这能够防止歧义,因为有的go package的命名是以go.开始的。 Bootstrap(自举)如果要编译Go语言自身的源代码,必须要设置GOROOT_BOOTSTRAP环境变量。 ...

December 25, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-waitgroup

概述倡议先浏览 goroutine。 在 goroutine 大节中,为了让并发的 3 个 goroutine 失常执行实现,调用 time.Sleep() 睡眠期待。这样的形式存除了实现不优雅之外,最大的问题在于: time.Sleep() 承受的是一个硬编码的工夫参数,这就要求咱们实现必须晓得每个goroutine 的执行工夫并且要以执行工夫最长的 goroutine 为基准,这在大多数场景下是没方法做到的。 如果主过程可能晓得每个 goroutine 是何时完结的,并且在完结之后发一个告诉给主过程, 那么问题就能够完满解决了。Go 提供的 sync.WaitGroup 就是针对这种场景的解决方案。 调用规定Add() 和 Done() 办法必须配对,Wait() 办法必须在程序退出前调用。Add(), Done(), Wait() 三者必须同属一个作用域。例子读者能够多运行几次上面的例子,领会 sync.WaitGroup 的用法和细节。 调用 3 个 goroutinepackage mainimport ( "fmt" "sync")func main() { var wg sync.WaitGroup // 申明一个 sync.WaitGroup 实例 wg.Add(3) // 参数 为 3,正好对应了 3 个 goroutine // 3 个 goroutine 是并发运行的,所以程序不肯定是 1, 2, 3 // 读者能够多运行几次,看看输入后果 go func() { defer func() { wg.Done() // 告诉主过程,这个 goroutine 曾经执行实现 }() fmt.Println("goroutine 1") }() go func() { defer func() { wg.Done() // 告诉主过程,这个 goroutine 曾经执行实现 }() fmt.Println("goroutine 2") }() go func() { defer func() { wg.Done() // 告诉主过程,这个 goroutine 曾经执行实现 }() fmt.Println("goroutine 3") }() wg.Wait() // 期待所有 goroutine 全副执行完}// $ go run main.go// 输入如下, 3 个 goroutine 是并发运行的,程序不肯定,所以你的输入可能和这里的不一样/** goroutine 3 goroutine 1 goroutine 2*/分割我 ...

December 24, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-遍历通道

概述倡议先浏览 range, 非缓冲通道, 缓冲通道。 range 除了能够遍历字符串、切片、数组等数据结构外,还能够遍历通道。 语法规定和遍历其余数据结构不同,遍历通道时没有 索引 的概念,只有值,语法如下: for v := range ch { // v 是从通道接管到的值// do something ...}应用规定遍历一个空的通道 (值为 nil) 时,阻塞遍历一个阻塞 && 未敞开的通道时,阻塞遍历一个阻塞 && 已敞开的通道时,不做任何事件遍历一个非阻塞 && 未敞开的通道时,就接管通道内的所有缓存数据,而后阻塞遍历一个非阻塞 && 已敞开的通道时,就接管通道内的所有缓存数据,而后返回例子遍历一个空的通道package mainimport ( "fmt" "time")func main() { var done chan bool go func() { for v := range done { fmt.Printf("v = %v\n", v) break } fmt.Println("range broken") // 执行不到这里 }() time.Sleep(time.Second)}// $ go run main.go// 没有任何输入遍历一个阻塞 && 未敞开的通道package mainimport ( "fmt" "time")func main() { done := make(chan bool) go func() { for v := range done { fmt.Printf("v = %v\n", v) break } fmt.Println("range broken") // 执行不到这里 }() time.Sleep(time.Second)}// $ go run main.go// 没有任何输入遍历一个阻塞 && 已敞开的通道package mainfunc main() { ch := make(chan string) close(ch) for v := range ch { println(v) }}// $ go run main.go// 没有任何输入遍历一个非阻塞 && 未敞开的通道通道中无缓存数据,阻塞package mainimport ( "fmt" "time")func main() { ch := make(chan string, 1) go func() { for v := range ch { fmt.Printf("v = %v\n", v) break } fmt.Println("range broken") // 执行不到这里 }() time.Sleep(time.Second)}// $ go run main.go// 没有任何输入通道中有 1 个数据package mainfunc main() { ch := make(chan string, 1) ch <- "hello world" for v := range ch { println(v) } // 代码执行不到这里 close(ch)}// $ go run main.go// 输入如下/** v = hello world*/通道中有多个数据package mainimport ( "fmt" "time")func main() { ch := make(chan string, 3) for i := 0; i < 3; i++ { ch <- "hello world" } go func() { for v := range ch { fmt.Printf("v = %v\n", v) } fmt.Println("range broken") // 执行不到这里 }() time.Sleep(time.Second)}// $ go run main.go// 输入如下/** v = hello world v = hello world v = hello world*/遍历一个非阻塞 && 已敞开的通道时通道中无缓存数据,间接返回package mainfunc main() { ch := make(chan string, 1) close(ch) for v := range ch { println(v) }}// $ go run main.go// 没有输入通道中有 1 个数据package mainfunc main() { ch := make(chan string, 1) ch <- "hello world" close(ch) for v := range ch { println(v) }}// $ go run main.go// 输入如下/** hello world*/通道中有多个数据package mainfunc main() { ch := make(chan string, 3) for i := 0; i < 3; i++ { ch <- "hello world" } close(ch) for v := range ch { println(v) }}// $ go run main.go// 输入如下/** hello world hello world hello world*/分割我 ...

December 24, 2022 · 2 min · jiezi

关于go:Go-快速入门指南-检测通道是否关闭

概述倡议先浏览 非缓冲通道, 缓冲通道, 敞开通道,通道方向。 Go 语言没有提供函数或办法判断一个通道是否敞开。因而只能应用一个变通的方法:接管通道元素,依据返回的布尔值确定通道是否敞开。 例子双向通道检测package mainfunc main() { ch := make(chan string) close(ch) if _, open := <-ch; !open { println("channel closed") }}// $ go run main.go// 输入如下/** channel closed*/单向 (只读) 通道检测package mainimport "time"func main() { ch := make(chan string) go func(c <-chan string) { if _, open := <-c; !open { println("channel closed") } }(ch) close(ch) time.Sleep(time.Second)}// $ go run main.go// 输入如下/** channel closed*/单向 (只写) 通道检测对于只写通道,须要采纳一个折中的方法: ...

December 24, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-通道方向

概述通道的方向分为 发送 和 接管。默认状况下,通道是双向的 (同时发送和接管),然而能够通过标识符指明通道为单向 (只读或只写)。 语法规定可读写通道 (同时反对发送和接管)变量 := make(chan 数据类型)# 例子ch := make(chan string)只读通道 (只反对接管)变量 := make(<-chan 数据类型)# 例子ch := make(<-chan string)只写通道 (只反对发送)变量 := make(chan<- 数据类型)# 例子ch := make(chan<- string)类型转换双向通道能够转换为单向通道,然而单向通道无奈转换为双向通道。 例子package main// 参数是一个写入通道func ping(pings chan<- string) { //<-pings // 谬误: pings 通道只能写入 pings <- "hello world"}func pong(pings <-chan string, pongs chan<- string) { //pings <- "hello world" // 谬误: pings 通道只能读取 //<-pongs // 谬误: pongs 通道只能写入 msg := <-pings pongs <- msg}func main() { pings := make(chan string) pongs := make(chan string) done := make(chan bool) go ping(pings) go pong(pings, pongs) go func() { msg := <-pongs println(msg) done <- true }() <-done close(pings) close(pongs) close(done)}// $ go run main.go// 输入如下/** hello world*/分割我 ...

December 24, 2022 · 1 min · jiezi

关于go:在Dubbogo中使用TLS加密进行安全通信

1 背景Dubbo-go在Getty/Triple/Grpc三个通信层面反对TLS链路平安通信。 2 原理2.1 证书机制:ps: 能够先提前理解非对称加密机制。 CA(Certification Authority)负责生成根证书、签发证书等等。CA自签证书的过程如下: CA生成公钥 ca_KeyPub 和私钥 ca_KeyPri,以及根本信息表 ca_Info。ca_Info 中个别蕴含了CA的名称、证书的有效期等信息。CA对(ca_KeyPub + ca_Info)进行散列运算,失去散列值 ca_Hash。CA应用其私钥 ca_KeyPri 对 ca_Hash 进行非对称加密,失去加密的散列值 enc_ca_Hash。CA将(ca_KeyPub + ca_Info + enc_ca_Hash)组合生成自签名的数字证书「ca_Cert」。这张证书称之为根证书。根证书(ca_Cert)蕴含的内容:ca_KeyPub + ca_Info + enc_ca_Hash。(ca_Cert)可用于签订下一级的证书。根证书是自签名的,不须要其余机构认证。公钥私钥的生成能够利用OpenSSL等工具。当须要签发证书时,在本地生成公钥和私钥,向CA发动CSR申请(Certificate Signing Request), CA校验此申请之后,颁发证书。过程如下: 证书申请者(S)在本地生成公钥 s_KeyPub 和私钥 s_KeyPri,以及根本信息表 s_Info。s_Info 中个别蕴含了证书申请者的名称、证书有效期等信息。证书申请者将 s_KeyPub、s_Info 发送给认证机构CA,即发动CSR申请。CA通过某种形式验证申请者的身份之后,再加上根认证机构本人的一些信息 ca_Info,而后对它们(s_KeyPub + s_Info + ca_Info)进行散列运算,失去散列值 s_Hash。CA应用其私钥 ca_KeyPri 对 s_Hash 进行非对称加密,失去加密的散列值 enc_s_Hash。CA将(s_KeyPub + s_Info + ca_Info + enc_s_Hash)组合签订成数字证书(s_Cert)并发送给申请者。申请者的证书(s_Cert)蕴含的内容为:s_KeyPub + s_Info + ca_Info + enc_s_Hash。 证书校验: 用CA的公钥对 enc_s_Hash进行解密,失去s_Hash,比照其是否与 hash(s_KeyPub + s_Info + ca_Info)统一。 ...

December 24, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-关闭通道

概述倡议先浏览 阻塞通道 和 非阻塞通道 大节。在后面的两个大节中, 为了最小化代码达到演示成果,省略了 敞开通道 的步骤,正确的做法应该是在通道应用实现后敞开。 应用规定通过关键字 clsoe 敞开通道。 敞开一个空的通道 (值为 nil) 时,panic敞开一个非空 && 已敞开的通道时,panic敞开一个非空 && 未敞开的通道时,失常敞开这里的规定不用死记硬背,笔者遇到的大多数状况属于第二种,也就是 反复敞开一个通道,读者做到理论开发中遇到 敞开通道 的场景时,分割上下文,确认通道不会呈现反复敞开的状况即可。 例子敞开一个空的通道package mainfunc main() { var ch chan bool close(ch)}// $ go run main.go// 输入如下/** panic: close of nil channel ... ... exit status 2*/敞开一个非空 && 已敞开通道package mainfunc main() { ch := make(chan bool) close(ch) close(ch) // 反复敞开}// $ go run main.go// 输入如下/** panic: close of nil channel ... ... exit status 2*/敞开一个非空 && 未敞开的通道package mainfunc main() { ch := make(chan bool) close(ch) println("channel closed")}// $ go run main.go// 输入如下/** channel closed*/分割我 ...

December 24, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-缓存通道

概述Go 箴言: 不要通过共享内存来通信,而要通过通信来共享内存。 阻塞通道与非阻塞通道通过关键字 chan + 数据类型 来表明通道数据类型,调用 make() 函数来初始化一个通道。make() 函数的第二个参数为通道长度,如果未指定或指定为 0,则该通道为非缓冲通道 (阻塞通道),否则该通道为缓冲通道 (非阻塞通道)。 非阻塞通道 图片起源: https://stackoverflow.com/que... 例子ch := make(chan string, 10) // 缓冲通道, 容量为 103 种操作发送如果通道已满 (元素数量达到容量), 发送操作将会阻塞,直到另一个 goroutine 在对应的通道下面实现接管操作,两个 goroutine 才能够继续执行如果通道未满,发送操作不会阻塞语法规定通道变量 <- 数据# 例如: 将变量 x 发送到通道 chch <- x 接管如果通道已空 (元素数量为 0),接管操作将会阻塞,直到另一个 goroutine 在对应的通道下面实现发送操作,两个 goroutine 才能够继续执行如果通道不为空,接管操作不会阻塞语法规定<- 通道变量# 例如: 从通道 ch 接管一个值,并且抛弃<-ch 接管变量 <- 通道变量# 例如: 从通道 ch 接管一个值,并且赋值给变量 xx := <-ch 敞开详情见 敞开通道。 例子缓冲通道容量为 2package mainfunc main() { ch := make(chan string, 2) ch <- "hello" // 不会死锁,因为 ch 是缓冲通道 ch <- "world" println(<-ch) println(<-ch)}// $ go run main.go// 输入如下/** hello world*/分割我 ...

December 24, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-非缓冲通道

概述倡议先浏览 goroutine 大节。 Go 箴言: 不要通过共享内存来通信,而要通过通信来共享内存。goroutine 是 Go 程序并发执行的实体,channel (通道) 则是它们之间的连贯,用于多个 goroutine 之间相互通信。通道能够让一个 goroutine 发送特定类型值到另一个 goroutine,每一个通道能够发送数据类型称为通道的 元素类型。 阻塞通道与非阻塞通道通过关键字 chan + 数据类型 来表明通道数据类型,调用 make() 函数来初始化一个通道。make() 函数的第二个参数为通道长度,如果未指定或指定为 0,则该通道为非缓存通道 (阻塞通道),否则该通道为缓存通道 (非阻塞通道)。 阻塞通道 图片起源: https://stackoverflow.com/que... 例子ch := make(chan string) // 非缓冲通道ch := make(chan string, 0) // 非缓冲通道ch := make(chan string, 10) // 缓冲通道, 容量为 103 种操作发送无缓冲通道下面的发送操作将会阻塞,直到另一个 goroutine 在对应的通道下面实现接管操作,两个 goroutine 才能够继续执行。 语法规定通道变量 <- 数据# 例如: 将变量 x 发送到通道 chch <- x 接管无缓冲通道下面的接管操作将会阻塞,直到另一个 goroutine 在对应的通道下面实现发送操作,两个 goroutine 才能够继续执行。 ...

December 24, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-goroutine

概述goroutine 是 Go 程序并发执行的实体,对于初学者来讲,能够简略地将 goroutine 了解为一个 超轻量的线程。 当一个程序启动时,只有一个 goroutine 调用 main 函数,称为 主 goroutine, 当 main 函数返回时,所有 goroutine 都会被终止 (不管其是否运行实现),而后程序退出。 语法规定关键字 go 启动一个 goroutine (能够了解为在后盾运行一个函数), 须要留神的是: 应用 go 启动的函数没有返回值。 # 间接调用一个匿名函数go func() { // 无参数 // do something ...}go func(x int, y bool ...) { // 有参数 // do something ...}# 调用一个已定义的函数go foo() // 无参数go bar(x int, y bool ...) // 有参数例子间接调用一个匿名函数package mainimport ( "fmt" "time")func main() { // 3 个 goroutine 是并发运行的,所以程序不肯定是 1, 2, 3 // 读者能够多运行几次,看看输入后果 go func() { fmt.Println("goroutine 1") }() go func() { fmt.Println("goroutine 2") }() go func() { fmt.Println("goroutine 3") }() // 这一行代码不可省略 // 如果省略掉,意味着主过程不期待 3 个 goroutine 执行实现就退出了,也就不会有 goroutine 的输入信息了 // 读者能够正文掉这行代码,而后运行看看输入后果 time.Sleep(1 * time.Second)}// $ go run main.go// 输入如下, 3 个 goroutine 是并发运行的,程序不肯定,所以你的输入可能和这里的不一样/** goroutine 3 goroutine 1 goroutine 2*/调用 time.Sleep() 睡眠期待 3 个 goroutine 执行实现,尽管达到了演示成果,然而有很多潜在问题。更好的解决方案请看 waitgroup。 ...

December 24, 2022 · 2 min · jiezi

关于go:Go-快速入门指南-类型比较

概述问题:Go 中 Map 的 key 能够是哪些数据类型呢? 咱们来看看具体的规定。 比拟运算符 用来比拟两个操作数并返回一个 bool 值,常见的比拟运算符: == 等于!= 不等于< 小于<= 小于等于> 大于>= 大于等于在任何比拟中,第一个操作数必须能够赋值给第二个操作数的类型,反过来也一样。 不可比拟类型Go 中有 3 种数据类型不能比拟,别离是 slice, map, func,如果要比拟这 3 种类型,应用 reflect.DeepEqual 函数。 为什么 slice 不能比拟(集体猜想,待验证) 切片是援用类型,比拟地址没有意义多个切片援用同一数组时,批改时会相互影响,无奈保障 key 的一致性切片除了 len 属性外,还有 cap 属性,比拟的维度没方法准确掂量基于上述起因,官网没有反对 slice 类型的比拟。 为什么 map 不能比拟(集体猜想,待验证) map 遍历是随机的,无奈保障 key 的一致性为什么 func 不能比拟(集体猜想,待验证) 函数的可变参数机制,无奈保障 key 的一致性函数参数能够为 slice, map, 这两种类型不可比拟可比拟类型的具体规定布尔值 可比拟整型 可比拟浮点型 可比拟。如果两个浮点型值一样 (由 IEEE-754 规范定义),则两者相等复数型 可比拟。如果两个复数型值的 real() 办法 和 imag() 办法都相等,则两者相等字符串 可比拟指针 可比拟。如果两个指针指向雷同的 地址 或者两者都为 nil,则两者相等,然而指向不同的零大小变量的指针可能不相等通道 可比拟。如果两个通道是由同一个 make 创立的 (援用的是同一个 channel 指针),或者两者都为 nil, 则两者相等接口 可比拟。interface 的外部实现蕴含了 2 个字段,类型 T 和 值 V。 如果两个 接口具备雷同的动静类型和动静值,或者两者都为 nil, 则两者相等构造体 可比拟 (如果两个构造体的所有字段都是可比拟的)。如果两个构造体对应的非空白字段相等,则两者相等数组 可比拟 (如果两个数组的所有元素都是可比拟的)。如果两个数组的所有对应元素相等,则两者相等例子指针的比拟指向雷同的地址的指针package mainimport "fmt"func main() { n := 1024 p := &n p2 := &n fmt.Printf("p == p2: %t\n", p == p2)}// $ go run main.go// 输入如下/** p == p2: true*/指向 nil 的指针package mainimport "fmt"func main() { var p *string var p2 *string fmt.Printf("p == p2: %t\n", p == p2)}// $ go run main.go// 输入如下/** p == p2: true*/通道的比拟同一个 make() 创立的通道package mainimport "fmt"func main() { ch := make(chan bool) ch2 := make(chan bool) p := &ch p2 := &ch2 fmt.Printf("p == p2: %t\n", p == p2) p3 := &ch fmt.Printf("p == p3: %t\n", p == p3)}// $ go run main.go// 输入如下/** p == p2: false p == p3: true*/通道为 nilpackage mainimport "fmt"func main() { var p *chan bool var p2 *chan bool fmt.Printf("p == p2: %t\n", p == p2)}// $ go run main.go// 输入如下/** p == p2: true*/构造体的比拟比拟的前提: 两个构造体的所有字段都是可比拟的,相等是指: 字段类型、字段个数、字段程序、字段值完全一致。 ...

December 24, 2022 · 3 min · jiezi

关于go:Go-快速入门指南-零值

概述当一个变量应用 var 进行申明后并未进行初始化 (变量前面没有赋值符 =) 操作,会默认调配一个零值 (zero value)。 不同类型对应的零值类型零值boolfalseint0float0string""byte (uint8)0pointernilfuncnilinterfacenilslicenilchannelnilmapnil例子bool 类型package mainimport "fmt"func main() { var b bool fmt.Printf("b = %t\n", b)}// $ go run main.go// 输入如下/** b = false*/byte 类型package mainimport "fmt"func main() { var b byte fmt.Printf("b = %c\n", b)}// $ go run main.go// 输入如下/** b =*/func 类型package mainimport "fmt"func main() { var f func() fmt.Printf("f = %v\n", f)}// $ go run main.go// 输入如下/** f = <nil>*/channel 类型package mainimport "fmt"func main() { var ch chan bool fmt.Printf("ch = %v\n", ch)}// $ go run main.go// 输入如下/** ch = <nil>*/分割我 ...

December 24, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-错误

概述倡议先浏览 函数 和 接口 大节。 例子errors.New() 创立谬误package mainimport ( "errors" "fmt")// 自定义除法函数func myDivide(dividend, divisor float64) (float64, error) { if divisor == 0 { // 除数不能为 0 return 0, errors.New("divide by zero") // 返回一个谬误 } return dividend / divisor, nil}func main() { divide, err := myDivide(100, 0) if err != nil { fmt.Printf("Error: %s\n", err) // 输入错误信息 } else { fmt.Printf("100 / 0 = %.2f\n", divide) // 代码执行不到这里 }}// $ go run main.go// 输入如下/** Error: divide by zero*/fmt.Errorf() 创立谬误不同于 errors.New(), fmt.Errorf() 在构建谬误时,能够进行格式化。 ...

December 24, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-判断是否实现接口

概述Go 外面没有 implements 关键字来判断一个构造体 (对象) 是否实现了某个接口, 要实现相似的性能,须要用到 类型断言。 类型断言// 判断 v 是否实现了接口 i// 返回值 ok 为 true 示意已实现,ok 为 false 示意未实现if _, ok := v.(i); ok {// do something...} 例子判断是否实现接口package mainimport "fmt"type geometry interface { area() float64 perimeter() float64}type rectangle struct { width, height float64}type circle struct { radius float64}func (r *rectangle) area() float64 { return r.width * r.height}func (r *rectangle) perimeter() float64 { return (r.width + r.height) * 2}func main() { var r interface{} r = &rectangle{ width: 10, height: 5, } if v, ok := r.(geometry); ok { fmt.Printf("r implements interface geometry, area = %.2f, perimeter = %.2f \n", v.area(), v.perimeter()) } var c interface{} c = &circle{ radius: 10, } if _, ok := c.(geometry); !ok { fmt.Println("c does not implement interface geometry") }}// $ go run main.go// 输入如下 /** r implements interface geometry, area = 50.00, perimeter = 30.00 c does not implement interface geometry*/分割我 ...

December 24, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-实现系统错误接口

概述规范库 error 数据类型实质上就是一个接口,原型如下: package builtintype error interface { Error() string}例子实现 error 接口package mainimport ( "fmt")// 自定义谬误构造体type divideError struct { msg string}// 实现 error 接口func (d *divideError) Error() string { return d.msg}func newDivideError() *divideError { return &divideError{ msg: "divide by zero", }}// 自定义除法函数func myDivide(dividend, divisor float64) (float64, error) { if divisor == 0 { // 除数不能为 0 return 0, newDivideError() // 返回一个自定义谬误 } return dividend / divisor, nil}func main() { divide, err := myDivide(100, 0) if err != nil { fmt.Printf("Error: %s\n", err) // 输入错误信息 } else { fmt.Printf("100 / 0 = %.2f\n", divide) // 代码执行不到这里 }}// $ go run main.go// 输入如下/** Error: divide by zero*/分割我 ...

December 24, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-接口

概述Go 接口是隐式实现。 对于一个数据类型,无需申明它实现了哪些接口,只须要实现接口必须的办法即可。当然了,存在一个小问题就是: 咱们可能无意间实现了某个接口:) ,所以 命名 是如许重要的一件事件。 语法规定type 接口名称 interface { 办法1名称(参数列表 ...) 返回值列表... 办法2名称(参数列表 ...) 返回值列表... 办法3名称(参数列表 ...) 返回值列表... ... ...}例子求矩形和圆的周长, 面积package mainimport ( "fmt" "math")// 申明一个图形接口type geometry interface { area() float64 perimeter() float64}// 多个字段类型雷同时,能够并列申明type rectangle struct { width, height float64}type circle struct { radius float64}// rectangle 隐式实现了 geometry 接口的 area 办法func (r *rectangle) area() float64 { return r.width * r.height}// rectangle 隐式实现了 geometry 接口的 perimeter 办法func (r *rectangle) perimeter() float64 { return (r.width + r.height) * 2}// circle 隐式实现了 geometry 接口的 area 办法func (c *circle) area() float64 { return math.Pi * c.radius * c.radius}// circle 隐式实现了 geometry 接口的 perimeter 办法func (c *circle) perimeter() float64 { return 2 * math.Pi * c.radius}func main() { r := &rectangle{ width: 10, height: 5, } fmt.Printf("Rectangle area = %.2f, perimeter = %.2f \n", r.area(), r.perimeter()) c := &circle{ radius: 10, } fmt.Printf("Circle area = %.2f, perimeter = %.2f \n", c.area(), c.perimeter())}// $ go run main.go// 输入如下 /** Rectangle area = 50.00, perimeter = 30.00 Circle area = 314.16, perimeter = 62.83*/referenceGo 圣经分割我 ...

December 23, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-方法

概述办法的申明和一般函数的申明相似,只是在函数名字后面多了一个 接收者参数 (接收者参数将办法绑定到其对应的数据类型上)。办法能够绑定到任何数据类型上,然而大多数状况下,绑定的都是 构造体。 语法规定func (接收者参数) 办法名称(参数列表 ...) 返回值列表... { // do something}例子构造体办法package mainimport "fmt"type person struct { name string age int16}func (p person) sayName() { fmt.Printf("Hi, my name is %s\n", p.name)}func (p person) sayAge() { fmt.Printf("Hi, my age is %d\n", p.age)}func main() { tom := &person{ name: "Tom", age: 6, } tom.sayName() tom.sayAge()}// $ go run main.go// 输入如下 /** Hi, my name is Tom Hi, my age is 6*/构造体指针办法相比构造体办法,指针构造体办法除了将办法参数变为指针外,在援用对应的字段时,无需加 * 标识符,这一点和一般指针变量援用时有所区别,须要留神。 ...

December 23, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-嵌套结构体

概述Go 反对将多个构造体通过嵌套的形式,组成一个大的构造体,升高了单个构造体复杂度,同时进步了构造体之间组合的灵活性。 例子为了省略篇幅,本大节只应用 字面量 形式初始化,new() 的初始化形式请参照 构造体 大节。 每个构造体独自初始化,最初组装package mainimport ( "fmt")type person struct { name string age int16 hobby profession address}type hobby struct { values []string}type profession struct { desc string}type address struct { tel string area string}func main() { // 这里应用单个字符作为变量名称,仅仅是为了演示,理论开发中要恪守命名标准 h := hobby{ values: []string{"读书", "羽毛球", "电影"}, } p := profession{ desc: "学生", } a := address{ tel: "123-456789", area: "XX 小区 1 栋 2 单元 304", } li := person{ name: "小李", age: 12, hobby: h, profession: p, address: a, } fmt.Printf(" 姓名: %s\n 年龄: %d\n 职业: %s\n 喜好: %v\n 电话: %s\n 住址: %s\n", li.name, li.age, li.profession.desc, li.hobby.values, li.address.tel, li.area) // 其中,嵌套的字段名能够省略 fmt.Println("\n 省略掉嵌套的字段名,打印后果一样\n") fmt.Printf(" 姓名: %s\n 年龄: %d\n 职业: %s\n 喜好: %v\n 电话: %s\n 住址: %s\n", li.name, li.age, li.desc, li.values, li.tel, li.area)}// $ go run main.go// 输入如下 /** 姓名: 小李 年龄: 12 职业: 学生 喜好: [读书 羽毛球 电影] 电话: 123-456789 住址: XX 小区 1 栋 2 单元 304 省略掉嵌套的字段名,打印后果一样 姓名: 小李 年龄: 12 职业: 学生 喜好: [读书 羽毛球 电影] 电话: 123-456789 住址: XX 小区 1 栋 2 单元 304*/整个构造体初始化package mainimport ( "fmt")type person struct { name string age int16 hobby profession address}type hobby struct { values []string}type profession struct { desc string}type address struct { tel string area string}func main() { li := person{ name: "小李", age: 12, hobby: hobby{ values: []string{"读书", "羽毛球", "电影"}, }, profession: profession{ desc: "学生", }, address: address{ tel: "123-456789", area: "XX 小区 1 栋 2 单元 304", }, } fmt.Printf(" 姓名: %s\n 年龄: %d\n 职业: %s\n 喜好: %v\n 电话: %s\n 住址: %s\n", li.name, li.age, li.desc, li.values, li.tel, li.area)}// $ go run main.go// 输入如下 /** 姓名: 小李 年龄: 12 职业: 学生 喜好: [读书 羽毛球 电影] 电话: 123-456789 住址: XX 小区 1 栋 2 单元 304*/打印构造体package mainimport ( "fmt")type person struct { name string age int16 hobby profession address}type hobby struct { values []string}type profession struct { desc string}type address struct { tel string area string}func main() { li := person{ name: "小李", age: 12, hobby: hobby{ values: []string{"读书", "羽毛球", "电影"}, }, profession: profession{ desc: "学生", }, address: address{ tel: "123-456789", area: "XX 小区 1 栋 2 单元 304", }, } fmt.Println("默认打印输出如下") fmt.Println(li) fmt.Println("打印时加上字段名") fmt.Printf("%+v\n", li) fmt.Println("打印时加上嵌套字段名") fmt.Printf("%#v\n", li)}// $ go run main.go// 输入如下 /** 默认打印输出如下 {小李 12 {[读书 羽毛球 电影]} {学生} {123-456789 XX 小区 1 栋 2 单元 304}} 打印时加上字段名 {name:小李 age:12 hobby:{values:[读书 羽毛球 电影]} profession:{desc:学生} address:{tel:123-456789 area:XX 小区 1 栋 2 单元 304}} 打印时加上嵌套字段名 main.person{name:"小李", age:12, hobby:main.hobby{values:[]string{"读书", "羽毛球", "电影"}}, profession:main.profession{desc:"学生"}, address:main.address{tel:"123-456789", area:"XX 小区 1 栋 2 单元 304"}}*/扩大浏览为什么有“组合优于继承”的说法 - 知乎分割我 ...

December 23, 2022 · 2 min · jiezi

关于go:Go-快速入门指南-结构体

概述构造体 是将零个或多个字段 (变量) 组合在一起的复合数据类型,相似于面向对象语言中的 对象。 构造体以及其字段都应用 可见性 规定。 语法规定type 构造体名称 struct { 字段1名称 字段1数据类型 字段2名称 字段2数据类型 ...}例子空构造体var s struct{}没有长度,也不携带任何字段信息。 申明及初始化package mainimport "fmt"type person struct { name string age int16}func main() { tom := person{ // 应用字面量初始化 name: "Tom", age: 6, } fmt.Printf("Tom's name is %s, age is %d\n", tom.name, tom.age) jerry := new(person) // 应用 new 关键字创立 jerry.name = "Jerry" jerry.age = 8 fmt.Printf("Jerry's name is %s, age is %d\n", jerry.name, jerry.age)}// $ go run main.go// 输入如下 /** Tom's name is Tom, age is 6 Jerry's name is Jerry, age is 8*/构造体指针和指针变量一样,在构造体中,也能够通过 构造体 指针间接批改构造体字段的值。 ...

December 23, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-recover

概述倡议先浏览 defer 大节。 recover 会终止 panic 状态并且返回 panic 的值,函数会从 panic 之前执行到的中央间接返回,不会持续往下执行。 语法规定**recover 和 defer 必须配套应用, 如果 recover 在其余中央执行会返回 nil,不会产生任何成果。defer 必须在 panic 之前申明,否则 panic 会间接终止程序。** 例子谬误捕捉package mainimport "fmt"func main() { if r := recover(); r != nil { fmt.Printf("捕捉到 1 个谬误: %v\n", r) } panic("测试") println("程序执行不到这里")}// $ go run main.go// 输入如下 /** panic: 测试 ... ... exit status 2*/正确捕捉package mainimport "fmt"func main() { defer func() { if r := recover(); r != nil { fmt.Printf("捕捉到 1 个谬误: %v\n", r) } }() panic("测试") println("程序执行不到这里")}// $ go run main.go// 输入如下 /** 捕捉到 1 个谬误: 测试*/分割我 ...

December 23, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-defer

概述一个 defer 语句就是一个一般的函数或办法调用。 defer 语句保障了不论是在失常状况下 (return 返回),还是非正常状况下 (产生谬误, 程序终止),函数或办法都可能执行。 次要个性一个函数可定义多个 defer 语句defer 表达式中的变量值在 defer 表达式定义时曾经确定defer 表达式能够批改函数中的命名返回值次要作用简化异样解决 ( 应用 defer + recover),防止异样与控制流混合在一起 (try … catch … finally)在 defer 做资源开释和配置重置等收尾工作语法规定如果 defer 函数只有一行语句,能够省略 func() { ... } 代码块,否则就须要用 func() { ... } 代码块包起来。 多个 defer 执行程序如果一个函数中注册了多个 defer 函数,这些函数会依照 后进先出 的程序执行 (和 栈 的出栈程序统一)。也就是最初注册的 defer 函数会第一个执行,而第一个注册的 defer 函数会最初执行。 例子函数退出前打印字符package mainfunc A() { defer println("A 函数执行实现") println("A 函数开始执行")}func B() { defer println("B 函数执行实现") println("B 函数开始执行")}func main() { A() B()}// $ go run main.go// 输入如下 /** A 函数开始执行 A 函数执行实现 B 函数开始执行 B 函数执行实现*/敞开文件句柄package mainimport ( "fmt" "os")func createFile(name string) *os.File { file, err := os.Create(name) if err != nil { panic(err) } return file}func writeFile(file *os.File) { n, err := file.WriteString("hello world") if err != nil { panic(err) } else { fmt.Printf("胜利写入 %d 个字符\n", n) }}func closeFile(file *os.File) { err := file.Close() if err != nil { panic(err) }}func main() { file := createFile("/tmp/defer_test.txt") defer closeFile(file) // 获取到文件句柄后,第一工夫注册 defer 函数 writeFile(file)}// $ go run main.go// 输入如下 /** 胜利写入 11 个字符*/// $ cat /tmp/defer_test.txt// 输入如下/** hello world*/多个 defer 函数package mainfunc A() { defer println("第 1 个 defer 函数") defer func() { // 这里为了演示 func() { ... } 的语法 defer println("第 2 个 defer 函数") }() defer println("第 3 个 defer 函数") println("A 函数开始执行")}func main() { A()}// $ go run main.go// 输入如下/** A 函数开始执行 第 3 个 defer 函数 第 2 个 defer 函数 第 1 个 defer 函数*/referenceGo 圣经分割我 ...

December 23, 2022 · 2 min · jiezi

关于go:Go-快速入门指南-panic

概述panic 会终止程序并退出,因而只有在产生重大的谬误时才会应用 panic。 例子被动触发package mainfunc main() { panic("some error...")}// $ go run main.go// 输入如下 /** panic: some error...goroutine 1 [running]:main.main() /home/codes/Go-examples-for-beginners/main.go:4 +0x27exit status 2*/除 0package mainimport "fmt"func main() { fmt.Println("除数不能为 0") n := 0 fmt.Printf("5 / 0 = %d", 5/n)}// $ go run main.go// 输入如下 /** 除数不能为 0 panic: runtime error: integer divide by zero goroutine 1 [running]: main.main() /home/codes/Go-examples-for-beginners/main.go:15 +0x57 exit status 2*/分割我

December 23, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-内部函数

概述应用的场景:在函数外部有很多重复性代码并且重大依赖上下文变量。此时能够在函数外部申明一个函数,专门用来解决重复性的代码。 例子外部求和函数package mainimport "fmt"func main() { var sum func(...int) int // 申明 sum 函数 sum = func(numbers ...int) int { // 定义 sum 函数 total := 0 for _, num := range numbers { total += num } return total } fmt.Printf("1 + 2 + 3 = %d\n", sum(1, 2, 3))}// $ go run main.go// 输入如下 /** 1 + 2 + 3 = 6*/分割我

December 23, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-递归

概述经典语录: 要想了解递归,首先要了解递归。递归的概念参考 递归 - 维基百科。 例子阶乘package mainimport "fmt"func factorial(n int) int { if n == 0 { return 1 } return n * factorial(n-1)}func main() { fmt.Printf("1 * 2 * 3 * 4 * 5 = %d\n", factorial(5))}// $ go run main.go// 输入如下 /** 1 * 2 * 3 * 4 * 5 = 120*/分割我

December 23, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-闭包

概述闭包的概念参考 闭包 - 维基百科)。 例子自增序列号生成器package mainimport "fmt"// 自增序列号生成器func incSeq() func() int { i := 0 return func() int { i++ return i }}func main() { next := incSeq() fmt.Printf("初始值 = %d\n", next()) for i := 1; i <= 5; i++ { fmt.Printf("第 %d 次迭代后, 值 = %d\n", i, next()) }}// $ go run main.go// 输入如下 /** 初始值 = 1 第 1 次迭代后, 值 = 2 第 2 次迭代后, 值 = 3 第 3 次迭代后, 值 = 4 第 4 次迭代后, 值 = 5 第 5 次迭代后, 值 = 6*/分割我 ...

December 23, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-指针参数

概述倡议先浏览 指针 大节。 例子指针变量参数package mainimport "fmt"func double(n int) { n *= 2}func doubleWithPtr(n *int) { *n *= 2}func main() { n := 100 double(n) fmt.Printf("应用一般变量作为函数参数执行实现后, n = %d\n", n) // 能够看到,变量值并未发生变化 doubleWithPtr(&n) fmt.Printf("应用指针变量作为函数参数执行实现后, n = %d\n", n) // 变量值曾经发生变化}// $ go run main.go// 输入如下 /** 应用一般变量作为函数参数执行实现后, n = 100 应用指针变量作为函数参数执行实现后, n = 200*/指针数组变量参数package mainimport "fmt"func double(numbers [3]int) { for _, v := range numbers { v *= 2 }}func doubleWithPtr(numbers *[3]int) { for i := range numbers { numbers[i] *= 2 }}func main() { numbers := [3]int{100, 200, 300} double(numbers) fmt.Printf("应用数组变量作为函数参数执行实现后, n = %d\n", numbers) // 能够看到,数组元素并未发生变化 doubleWithPtr(&numbers) fmt.Printf("应用指针数组变量作为函数参数执行实现后, n = %d\n", numbers) // 数组元素曾经发生变化}// $ go run main.go// 输入如下 /** 应用数组变量作为函数参数执行实现后, n = [100 200 300] 应用指针数组变量作为函数参数执行实现后, n = [200 400 600]*/切片参数在 切片 大节中讲到,切片的底层援用了一个数组,能够简略地了解为:切片自身是一个指针,指向底层数组的元素,所以罕用的形式的是将函数参数定义为切片类型。 ...

December 23, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-变长参数

概述在函数的最初一个参数的数据类型之前加上省略号 ... ,示意该参数的数据类型是 变长类型,调用该函数时能够传递任意数量 ( 0 - N ) 的该类型的参数。 一个函数只能有一个变长参数,且变长参数必须为最初一个参数。 例子传递一个参数package mainimport "fmt"func sum(numbers ...int) int { total := 0 for _, num := range numbers { total += num } return total}func main() { fmt.Printf("1 = %d\n", sum(1))}// $ go run main.go// 输入如下 /** 1 = 1*/传递多个参数package mainimport "fmt"func sum(numbers ...int) int { total := 0 for _, num := range numbers { total += num } return total}func main() { fmt.Printf("1 + 2 + 3 = %d\n", sum(1, 2, 3))}// $ go run main.go// 输入如下 /** 1 + 2 + 3 = 6*/传递切片参数如果要传递 切片类型 数据作为函数参数,只须要在参数前面加上省略号 ...。 ...

December 23, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-函数

概述函数 是将一个或者一类问题包装为一个代码块,能够被屡次调用,进步代码重用性。 Go 函数中申明、定义、参数、返回值这些根底概念,和其余编程语言中的统一,这里不再赘述。 语法规定Go 函数反对单个返回值和多个返回值。 # 单个返回值# 参数可省略func 函数名称(参数 1 值 参数 1 类型, 参数 2 值 参数 2 类型 ...) 返回值类型 { // do something}# 多个返回值,不指定名称# 参数可省略func 函数名称(参数 1 值 参数 1 类型, 参数 2 值 参数 2 类型 ...) (返回值 1 类型, 返回值 2 类型) { // do something}# 多个返回值,指定名称# 参数可省略func 函数名称(参数 1 值 参数 1 类型, 参数 2 值 参数 2 类型 ...) (返回值 1 名称 返回值 1 类型, 返回值 2 名称 返回值 2 类型) { // do something}例子单个返回值package mainimport "fmt"func max(x, y int) int { if x > y { return x } else { return y }}func main() { fmt.Printf("max = %d\n", max(1, 2))}// $ go run main.go// 输入如下 /** max = 2*/多个返回值,不指定名称package mainimport "fmt"func getNumbers() (int, int, int) { return 1, 2, 3}func main() { x, y, z := getNumbers() fmt.Printf("x = %d, y = %d, z = %d\n", x, y, z)}// $ go run main.go// 输入如下 /** x = 1, y = 2, z = 3*/多个返回值,指定名称package mainimport "fmt"func getNumbers() (x int, y float64, z string) { x = 1024 y = 3.14 z = "hello world" return}func main() { x, y, z := getNumbers() fmt.Printf("x = %d, y = %.2f, z = %s\n", x, y, z)}// $ go run main.go// 输入如下 /** x = 1024, y = 3.14, z = hello world*/参数/返回值 类型雷同简化当参数类型雷同时,能够将类型放在最初一个参数变量前面当返回值类型雷同时,能够将类型放在最初一个返回值变量前面package mainimport "fmt"func getMaxAndMin(x, y, z int) (max, min int) { if x > y { max = x min = y if x < z { max = z } else if z < y { min = z } } else { max = y min = x if y < z { max = z } else if z < x { min = x } } return}func main() { max, min := getMaxAndMin(100, 200, 300) fmt.Printf("max = %d, min = %d\n", max, min)}// $ go run main.go// 输入如下 /** max = 300, min = 100*/扩大浏览https://zh.wikipedia.org/wiki...分割我 ...

December 23, 2022 · 2 min · jiezi

关于go:Go-快速入门指南-make-和-new

概述new() 函数为数据类型 T 调配一块内存,初始化为类型 T 的零值,返回类型为指向数据的指针,能够用于所有数据类型。 make() 函数除了为数据类型 T 分配内存外,还能够指定长度和容量,返回类型为数据的初始化构造,只限于 切片, Map, 通道。 make什么时候用申明并初始化 切片, Map, 通道(前面会讲到)。 为什么定义为什么专门针对切片, Map 和 通道类型定义一个 make 函数呢?因为这 3 种数据类型要求应用时必须实现初始化,未初始化就应用可能会引发谬误,具体规定如下: 未初始化的切片值为 nil, 如果间接获取或设置元素数据会报错未初始化的 Map 值为 nil, 如果间接设置元素数据会报错未初始化的 通道 值为 nil, 发送数据和接收数据会阻塞 (详情在前面通道章节介绍)未初始化的切片package mainfunc main() { var s []int // 间接获取值: 报错 _ = s[0] // 间接设置值: 同样报错 //s[0] = 100}// $ go run main.go// 输入如下 /**panic: runtime error: index out of range [0] with length 0...exit status 2*/未初始化的 Mappackage mainfunc main() { var m map[int]string // 间接设置值: 报错 m[100] = "hello world"}// $ go run main.go// 输入如下 /**panic: panic: assignment to entry in nil map...exit status 2*/append()为什么切片即便是 nil, 却能够调用 append() 函数呢? 因为 append() 函数外部实现中做了兼容,如果切片为 nil,那么会先申请好须要的内存空间,而后在复制给切片,等于 笼罩掉原来的切片,这样就不会报错了。 ...

December 23, 2022 · 2 min · jiezi

关于go:Go-快速入门指南-有序-Map

概述Map 的遍历是无序的,这意味着不能依赖遍历的键值程序。如果想实现 Map 遍历时程序永远统一,一个折中的计划时事后给 Map 的 键 排序,而后依据排序后的键序列遍历 Map, 这样能够保障每次遍历程序都是一样的。 例子package mainimport ( "fmt" "sort")func main() { var m = make(map[int]string) m[0] = "zero" m[1] = "one" m[2] = "two" keys := make([]int, len(m)) // 将所有的键放入一个切片中 index := 0 for k, _ := range m { keys[index] = k index++ } sort.Ints(keys) // 将所有的键进行排序 for i := 0; i < 5; i++ { for _, key := range keys { // 依据排序后的键遍历 Map fmt.Printf("key = %d, val = %s\n", key, m[key]) } fmt.Printf("第 %d 次遍历实现\n", i+1) }}// $ go run main.go// 输入如下 /** key = 0, val = zero key = 1, val = one key = 2, val = two 第 1 次遍历实现 key = 0, val = zero key = 1, val = one key = 2, val = two 第 2 次遍历实现 key = 0, val = zero key = 1, val = one key = 2, val = two 第 3 次遍历实现 key = 0, val = zero key = 1, val = one key = 2, val = two 第 4 次遍历实现 key = 0, val = zero key = 1, val = one key = 2, val = two 第 5 次遍历实现*/从输入的后果中能够看到,每次遍历的程序都是统一的。 ...

December 23, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-init-函数

概述init() 函数 是一个非凡的函数,个别称为初始化函数,不能被调用。 在每个文件外面,当程序启动或者文件被作为包援用的时候,init() 函数就会主动执行,个别用来做一些包的初始化操作。 语法规定init() 函数 没有参数,也没有返回值。 func init() { // do something}执行程序包的初始化函数依照程序中引入的程序执行,依赖于具体的程序优先级,每次初始化一个包。例如 包 a 引入了 包 b, 那么确保 包 b 的初始化操作 在 包 a 的初始化操作之前实现。初始化过程是自下而上的,main 包 最初初始化,也就是说,在 main 函数 执行前,所援用到的包曾经全副初始化实现。 import -> const -> var -> init() -> main()例子包变量初始化package mainimport "fmt"var ( pageIndex int pageSize int)func init() { pageIndex = 1 pageSize = 20}func main() { fmt.Printf("page index = %d\n", pageIndex) fmt.Printf("page size = %d\n", pageSize)}// $ go run main.go// 输入如下 /** page index = 1 page size = 20*/多个包之间援用初始化程序新建一个目录 A, 并将如下代码写入 A/a.go 文件 ...

December 23, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-Map

概述Map 是一种键值对的无序汇合,在其余编程语言中也被称为 字典, Hash, 关联数组。 重要的一点是: Map 键 的数据类型必须是能够比拟的,例如 string, int, float64,类型比拟能够参考这篇文档 。 Map 值 的数据类型能够是任意的。 语法规定# 申明var 变量名 map[键数据类型]值数据类型# 申明及初始化var 变量名 = make(map[键数据类型]值数据类型, 长度) // 长度参数能够省略获取值/扭转值package mainimport "fmt"func main() { var m = make(map[string]int) fmt.Printf("Map 长度 = %d\n", len(m)) m["zero"] = 0 m["one"] = 1 m["two"] = 2 fmt.Printf("Map 长度 = %d\n", len(m)) fmt.Printf("zero = %T, %v\n", m["zero"], m["zero"]) fmt.Printf("one = %T, %v\n", m["one"], m["one"]) fmt.Printf("two = %T, %v\n", m["two"], m["two"])}// $ go run main.go// 输入如下 /** Map 长度 = 0 Map 长度 = 3 zero = int, 0 one = int, 1 two = int, 2*/删除元素调用 delete() 办法实现。 ...

December 23, 2022 · 2 min · jiezi

关于go:Go-快速入门指南-字符切片

概述倡议先浏览 字符串, 切片 两个大节。 因为字符串不可变,如果每次以 从新赋值 的形式扭转字符串,效率会非常低,这时应该应用 []byte 类型,[]byte 元素能够被批改。 因为 byte 类型是 uint8 类型的别名,所以 []byte 也就是 []uint8。 语法规定字符串转化为 []bytepackage mainimport "fmt"func main() { s := "hello world" b := []byte(s) fmt.Printf("b type = %T, val = %s\n", b, b)}// $ go run main.go// 输入如下/** b type = []uint8, val = hello world*/[]byte 转换为字符串package mainimport "fmt"func main() { b := []byte{'h', 'e', 'l', 'l', '0', ' ', 'w', 'o', 'r', 'l', 'd'} s := string(b) fmt.Printf("s type = %T, val = %s\n", s, s)}// $ go run main.go// 输入如下/** s type = string, val = hell0 world*/长度计算对于字符串不同编码对长度的计算形式,感兴趣的读者能够参考扩大浏览。 ...

December 23, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-切片

概述浏览本大节之前,倡议先浏览 数组 大节。 切片 是对数组的一个间断片段的援用。片段能够是整个数组,也能够是数组的一部分 (例如数组的第 3 个元素到第 8 个元素)。所以 切片 是一个援用类型,扭转切片的值也就扭转了底层数组的值。 一个数组能够被多个 切片 援用,它们之间共享数组的数据。当数组或者任一 切片 数据扭转时,会影响到其余切片的数据。切片的长处在于节俭了存储数据的空间 (当然切片自身还是须要占用存储空间的),只需援用数据即可,所以应用频率十分高。 长度和容量切片有两个属性,别离是 长度 和 容量。 长度示意切片以后有多少个元素,容量示意切片最多能够有多少容量,两者的关系是: 长度 <= 容量 。 语法规定申明var 变量名称 []数据类型# 例子var sli []int能够看到,和数组的语法规定简直统一,惟一的差别在于切片不须要指明长度。 申明及初始化var 变量名 = make([]int, 长度, 容量) // 容量参数能够省略# 例子var sli = make([]int, 5, 10)获取值/扭转值package mainimport "fmt"func main() { var s []int // 申明一个整型切片 fmt.Printf("切片长度 = %d, 容量 = %d\n", len(s), cap(s)) var s2 = make([]int, 3, 10) // 申明并初始化一个整型切片 fmt.Printf("切片长度 = %d, 容量 = %d\n", len(s2), cap(s2)) s2[0] = 100 // 为切片第 1 个元素赋值 s2[1] = 200 // 为切片第 2 个元素赋值 s2[2] = 300 // 为切片第 3 个元素赋值 println(s2[0]) // 输入切片第 1 个元素 println(s2[1]) // 输入切片第 2 个元素 println(s2[2]) // 输入切片第 3 个元素}// $ go run main.go// 输入如下 /** 切片长度 = 0, 容量 = 0 切片长度 = 3, 容量 = 10 100 200 300*/多个切片援用一个数组package mainimport "fmt"func main() { arr := [...]int{1, 2, 3, 4, 5} s := arr[0:5] // 切片 1 s2 := arr[0:3] // 切片 2 fmt.Println("批改前") fmt.Println(arr) // 输入数组所有元素 fmt.Println(s) // 输入切片 1 所有元素 fmt.Println(s2) // 输入切片 2 所有元素 s[0] = 100 // 批改切片 1 第一个元素 s2[1] = 200 // 批改切片 2 第二个元素 fmt.Println("批改后") fmt.Println(arr) // 输入数组所有元素 fmt.Println(s) // 输入切片 1 所有元素 fmt.Println(s2) // 输入切片 2 所有元素}// $ go run main.go// 输入如下 /** 批改前 [1 2 3 4 5] [1 2 3 4 5] [1 2 3] 批改后 [100 200 3 4 5] [100 200 3 4 5] [100 200 3]*/追加值调用 append 函数 ...

December 23, 2022 · 3 min · jiezi

关于go:Go-快速入门指南-数组

概述数组 是具备雷同数据类型的一组长度固定的数据项序列,调配在间断的内存地址上。其中数据类型能够是整型、布尔型等根底数据类型,也能够是自定义数据类型。数组长度 必须是一个常量表达式,并且必须是一个非正数。同时,数组长度 也是数组类型的一部分, 例如 [3]int 和 [5]int是不同的类型。 语法规定在不确定数组元素的具体数量时,能够通过指定 长度 调配空间。 var 变量名称 [长度]数据类型# 例子var arr [5]int在确定数组元素的具体数量以及值时,能够省略 长度 变量,这种形式称为 数组常量。 var 变量名称 [...]数据类型{值1, 值2, 值3...}# 例子var arr [...]int{v1, v2, v3...}获取值/扭转值和其余编程语言一样,数组的元素能够通过 索引 来获取或批改,索引 从 0 开始。 package mainfunc main() { var arr [3]int arr[0] = 100 // 为数组第 1 个元素赋值 arr[1] = 200 // 为数组第 2 个元素赋值 arr[2] = 300 // 为数组第 3 个元素赋值 println(arr[0]) // 输入数组第 1 个元素 println(arr[1]) // 输入数组第 2 个元素 println(arr[2]) // 输入数组第 3 个元素 var arr2 = [...]int{400, 500, 600} // 应用 数组常量 定义 println(arr2[0]) // 输入数组第 1 个元素 println(arr2[1]) // 输入数组第 2 个元素 println(arr2[2]) // 输入数组第 3 个元素}// $ go run main.go// 输入如下 /** 100 200 300 400 500 600*/获取数组长度通过 len() 函数获取。 ...

December 23, 2022 · 3 min · jiezi

关于go:Go-快速入门指南-可见性和作用域

可见性包通过 导出 机制管制 变量、构造体、函数 等数据可见性。 只有 1 个简略的规定: 首字母大写,可导出,首字母小写,不可导出。 也就是说,Go 的访问控制只有两种模式: 包内可见全局可见例子package hellovar ( privateName string // 只能包内拜访 PublicName string // 包内,包外都能够拜访)作用域词法块 是指由大括号围起来的一个语句序列,比方 for 循环语句块,if/else 判断语句块。在 语句块 外部申明的变量对外部不可见,块把申明围起来,决定了它的作用域。 一个程序能够蕴含多个雷同名称的变量,只有这些变量处在不同的 语句块 内。尽管语法上反对,然而实际中不倡议这样做。 全局变量全局变量依据 可见行 规定,决定在包内可用还是全局可用。 package mainimport "fmt"var ( number = 1024)func main() { // 间接拜访全局变量 fmt.Printf("number = %d\n", number) // 循环内拜访全局变量 for i := 0; i < 1; i++ { fmt.Printf("number = %d\n", number) }}// $ go run main.go// 输入如下 /** number = 1024 number = 1024*/局部变量不同的层级作用域外层定义的变量在内层能够应用,内层定义的变量外层不能够应用。常见的 if, for 等都是不同的代码块, 每个代码块组成了一个层级。 ...

December 22, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-goto-语句

概述关键字 goto 能够使程序跳转到指定的地位执行,那么这个地位如何示意呢?应用 标签 来示意 (能够了解为标签就是一个变量)。 语法规定标签的名称大小写敏感,能够搭配 for, switch 语句应用。 # 配合 for 应用标签名称: for 初始表达式; 条件表达式; 迭代表达式 { // do something // [goto|continue|break] 标签名称 }例子留神: 该示例会有限输入,终止循环请按 Ctrl + C。 package mainimport "fmt"func main() {LABEL1: for i := 0; i < 3; i++ { for j := 0; j < 3; j++ { if j == 2 { // 感兴趣的读者能够将这里的 goto 改为 break 或 continue, 体验下不同的用法 goto LABEL1 } fmt.Printf("i = %d, j = %d\n", i, j) } }}// $ go run main.go// 有限输入 /** i = 0, j = 0 i = 0, j = 1 ...*/分割我 ...

December 22, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-switch

概述相较于支流编程语言,Go 中的 switch 语法更加灵便,它承受任意模式的表达式。 语法规定switch 前面的表达式不须要括号case 语句块执行完会主动退出整个 switch 语句块,也就是不须要应用 break 显式申明case 语句快执行完后,如果心愿持续向下执行,能够应用关键字 fallthrough, 这样就和其余编程语言不加 break 成果一样了switch expr { // expr 能够是任意类型 case v1: ... case v2: ... case v3: ... case v4, v5, v6: // 能够同时测试多个可能符合条件的值 ... default: // 默认值 ...}例子一般表达式package mainimport "fmt"func main() { n := 1024 switch n { case 1023: fmt.Println("n = 1023") case 1024: fmt.Println("n = 1024") case 1025: fmt.Println("n = 1025") }}// $ go run main.go// 输入如下 /** n = 1024*/运算表达式package mainimport "fmt"func main() { n := 1024 switch n * 2 { case 1024: fmt.Println("n = 1024") case 2048: fmt.Println("n = 2048") case 0: fmt.Println("n = 0") }}// $ go run main.go// 输入如下 /** n = 2048*/defaultpackage mainimport "fmt"func main() { n := 1024 switch n * 2 { case 0: fmt.Println("n = 0") case 1: fmt.Println("n = 1") case 2: fmt.Println("n = 2") default: fmt.Println("n = 2048") }}// $ go run main.go// 输入如下 /** n = 2048*/省略 expr 表达式package mainimport "fmt"func main() { n := 1024 switch { case n < 1024: fmt.Println("n < 1024") case n > 1024: fmt.Println("n > 1024") case n == 1024: fmt.Println("n == 1024") default: fmt.Println("invalid n") }}// $ go run main.go// 输入如下 /** n = 1024*/同时测试多个 casepackage mainimport "fmt"func main() { n := 1024 switch n { case 1023, 1024: // 多个 case, 只有一个匹配就 OK fmt.Println("n <= 1024") case 1025: fmt.Println("n > 1024") default: fmt.Println("invalid n") }}// $ go run main.go// 输入如下 /** n <= 1024*/fallthroughpackage mainimport "fmt"func main() { n := 1024 switch { case n < 1024: fmt.Println("n < 1024") fallthrough // 持续向下执行 case n > 1024: fmt.Println("n > 1024") fallthrough // 持续向下执行 case n == 1024: fmt.Println("n == 1024") fallthrough // 持续向下执行 default: fmt.Println("invalid n") }}// $ go run main.go// 输入如下 /** n = 1024 invalid n*/类型断言switch 能够 .(type) 来实现类型断言,判断某个值是否为某个数据类型。 ...

December 22, 2022 · 2 min · jiezi

关于go:Go-快速入门指南-range

概述Go 特有的一种的遍历构造。它能够遍历任何一个 汇合(字符串、数组、切片、Map、通道等)。语法上相似支流编程语言中的 foreach 语句,但能够取得每次遍历对应的索引。 语法规定// key 和 val 也能够应用别的变量名称,比方 index, item 等for key, val := range collection { // do something} 遍历规定其中,key 和 val 都能够应用 空白标识符 省略,具体规定如下: 如果 key 和 val 都保留,那么在循环体中必须应用这 2 个变量,语法如下:for key, val := range collection { // do something}如果省略 val, 不须要 空白标识符,语法如下:for key := range collection { // do something}如果省略 key, 则 key 的地位须要 空白标识符,语法如下:for _, val := range collection { // do something}如果 key 和 val 全副省略,那么只执行循环体中的代码,语法如下:for _, _ = range collection { // do something}遍历字符串key, val 都保留package mainimport "fmt"func main() { str := "hello" for key, val := range str { fmt.Printf("key = %d, val = %c\n", key, val) }}// $ go run main.go// 输入如下 /** key = 0, val = h key = 1, val = e key = 2, val = l key = 3, val = l key = 4, val = o*/省略 keypackage mainimport "fmt"func main() { str := "hello" for _, val := range str { fmt.Printf("val = %c\n", val) }}// $ go run main.go// 输入如下 /** val = h val = e val = l val = l val = o*/省略 valpackage mainimport "fmt"func main() { str := "hello" for key := range str { fmt.Printf("key = %d\n", key) }}// $ go run main.go// 输入如下 /** key = 0 key = 1 key = 2 key = 3 key = 4*/key, val 都省略package mainfunc main() { str := "hello" for _, _ = range str { println("do something") }}// $ go run main.go// 输入如下 /** do something do something do something do something do something*/小结本大节只应用 字符串 作为演示,对于其余的数据结构 (数组、切片、Map、通道等), range 语法都差不多。前面介绍其余数据结构时,会附加应用 range 遍历操作的内容。 ...

December 22, 2022 · 2 min · jiezi

关于go:Go-快速入门指南-for

概述Go 仅提供了关键字 for 来示意循环,并没有提供 while 和 do-while 语句,这一点和支流编程语言不同。 语法规定for 初始表达式; 条件表达式; 迭代表达式 { // do something}留神: 迭代表达式中,不反对 ++i, --i 这种模式,详情见 自增/自减。 例子单个计数器package mainfunc main() { for i := 0; i < 5; i++ { println(i) }}// $ go run main.go// 输入如下 /** 0 1 2 3 4*/多个计数器package mainfunc main() { for i, j := 1, 5; i <= 5; i, j = i+1, j-1 { println("i = ", i, " j = ", j) }}// $ go run main.go// 输入如下 /** i = 1 j = 5 i = 2 j = 4 i = 3 j = 3 i = 4 j = 2 i = 5 j = 1*/模拟 whilepackage mainfunc main() { i := 0 for i < 5 { println(i) i++ }}// $ go run main.go// 输入如下 /** 0 1 2 3 4*/模拟 do-whilepackage mainfunc main() { i := 0 for { println(i) i++ if i >= 5 { break } }}// $ go run main.go// 输入如下 /** 0 1 2 3 4*/有限循环package mainfunc main() { i := 0 for { println(i) i++ if i >= 5 { break // 删除这行代码,将会进入有限循环 } }}// $ go run main.go// 输入如下 /** 0 1 2 3 4*/// 如果删除 `break` 语句,程序进入有限循环后能够应用 `Ctrl + C` 退出。分割我 ...

December 22, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-自增自减

自增和支流编程语言的自增语法不同,Go 只反对 i++ 形式,不反对 ++i 形式。 正确 package mainfunc main() { i := 1 i++ println(i) // 输入 2}谬误 package mainfunc main() { i := 1 ++i // 报错: '--' unexpected println(i)}自减和支流编程语言的自减语法不同,Go 只反对 i-- 形式,不反对 --i 形式。 正确 package mainfunc main() { i := 1 i-- println(i) // 输入 0}谬误 package mainfunc main() { i := 1 --i // 报错: '--' unexpected println(i)}分割我

December 22, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-ifelse

概述和其余编程语言中 if/else 规定统一,除了语法上略有差别。 语法规定if 和 else if 前面的条件表达式是不须要括号的。 单个 ifif condition { // do something }例子package mainfunc main() { n := 1024 if n > 0 { println("n > 0") }}// $ go run main.go// 输入如下 /** n > 0*/单个 if/elseif condition { // do something } else { // do something }例子package mainfunc main() { n := 1024 if n > 0 { println("n > 0") } else { println("n <= 0") }}// $ go run main.go// 输入如下 /** n > 0*/多个分支if condition1 { // do something } else if condition2 { // do something else } else { // default}例子package mainfunc main() { n := 0 if n > 0 { println("n > 0") } else if n < 0 { println("n < 0") } else { println("n = 0") }}// $ go run main.go// 输入如下 /** n == 0*/分割我 ...

December 22, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-指针

概述Go 提供了指针操作,然而没有指针运算。也就是说,不像 C 语言中那般弱小,毕竟 指针是 C 语言的灵魂。即使如此,指针仍然是十分重要的,在一些 性能敏感 的场景中,指针的身影随处可见。如果是零碎编程、操作系统或者网络应用,指针更是不可或缺的一部分。 指针的值是一个变量的地址。当然了,指针也是变量的一种,然而个别称其为 指针变量。 取地址关键字 & 示意取地址符。 程序运行时,数据通常存储在内存中,每个内存块都有一个地址, 通常应用 十六进制 示意,比方 0xc0000160a0。 # 将 & 放到一个变量前,就会取得该变量对应的内存地址, 例如x := 1024# p 变量是一个指针变量,值对应着变量 x 的地址p := &a例子package mainimport "fmt"func main() { pi := 3.1415 fmt.Printf("%p\n", &pi) // 间接取地址, 输入的是变量 pi 的地址 var p *float64 // 浮点型指针变量 p = &pi // 通过变量取地址 fmt.Printf("%p\n", p) // 输入的是指针的地址, 输入的是指针 p 的地址}// $ go run main.go// 输入如下 /** 0xc0000160a0 // 这个是我电脑的内存地址,你的输出可能和这个不一样 0xc0000b2000*/扭转值在方才的例子中,获取到了变量的地址后,间接进行了输入。 那么,应该如何输入指针对应的变量的值呢? ...

December 22, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-保留小数位

概述在大多数解决浮点数的场景中,为了进步可读性,往往只须要准确到 2 位或 3 位,一般来说,罕用的办法有两种。 fmt.Sprintf()package mainimport "fmt"func main() { pi := 3.1415926 s1 := fmt.Sprintf("%.2f", pi) // 保留 2 位小数 fmt.Printf("%T %v\n", s1, s1) s2 := fmt.Sprintf("%.1f", pi) // 保留 1 位小数 fmt.Printf("%T %v\n", s2, s2)}// $ go run main.go// 输入如下 /** string 3.14 string 3.1*/通过调用 fmt.Sprintf() 办法转换非常简单,然而不足之处在于返回值是一个字符串,如果须要保留精度的值仍然要求为 浮点型,可能须要应用二次 类型转换,不太敌对。 math 包实质上是通过两个 浮点型 数字进行计算,最初依据须要的精度,进行四舍五入。 例如 12345 / 100 = 123.45, 保留 1 位小数等于 123.5。 package mainimport ( "fmt" "math")func main() { pi := 3.1415926 var ratio float64 // 应用一个变量作为精度范畴, 比方 2 位小数时,精度范畴应该为 100 ratio = math.Pow(10, 2) // 计算精度范畴,2 位小数 = 100 s1 := math.Round(pi*ratio) / ratio // 保留 2 位小数 fmt.Printf("%T %v\n", s1, s1) ratio = math.Pow(10, 1) // 计算精度范畴,1 位小数 = 10 s2 := math.Round(pi*ratio) / ratio // 保留 1 位小数 fmt.Printf("%T %v\n", s2, s2)}通过调用 math 包 办法转换,除了能够保留精度外,还能够保障转换后的值仍然是一个 浮点型。然而该办法在一些 边缘场景 中,可能会报错,例如须要转化的数值曾经是一个 很大的浮点型数字,这时候再乘以精度范畴值,可能会产生 溢出。 ...

December 22, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-类型转换

概述Go 是强类型语言,因而不会进行隐式类型转换 (例如不能间接将一个 浮点型 转换为 整型)。任何不同类型之间的转换都必须显式阐明。 在类型转换时,要留神两边的值类型大小,能够将一个较小的值类型转换为一个较大的值类型,然而反过来,却有可能报错。例如:将一个 int64 转换为 int32 时,可能会产生 溢出。 间接转换间接转换 实用于两边值类型雷同的状况,比方 int 和 float, 都是数字类型。 语法规定变量名称 = 数据类型(变量值) 例子n := 123floatN := float64(n) // 将 n 转换为 float64 类型 ## 例子### 整数转换为浮点数package main import "fmt" func main() { n := 100fmt.Println(float64(n))} // $ go run main.go// 输入如下 /** 100*/ ### 浮点数转换为整数package main import "fmt" func main() { pi := 3.14fmt.Println(int64(pi))} // $ go run main.go// 输入如下 /** 3*/ ...

December 22, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-字符

概述Go 中示意字符的关键字为 rune, 也就是 int32 的别名。 ASCII 码只须要 7 bit 就能够残缺地示意,但只能示意英文字母在内的128个字符。 为了示意世界上大部分的文字零碎,创造了 Unicode, 它是 ASCII 的超集,蕴含世界上书写零碎中存在的所有字符,并为每个代码调配一个规范编号(称为Unicode CodePoint),在 Go 中称之为 rune。 语法规定由单引号 ' 括起来,只能蕴含一个字符 字符串长度对于字符串不同编码对长度的计算形式,感兴趣的读者能够参考扩大浏览。 例子package mainimport ( "fmt" "unicode/utf8")func main() { s := 'a' fmt.Printf("s type = %T, len = %d\n", s, utf8.RuneLen(s)) s2 := '我' fmt.Printf("s2 type = %T, len = %d\n", s, utf8.RuneLen(s2))}// $ go run main.go// 输入如下/** s type = int32, len = 1 s2 type = int32, len = 3*/扩大浏览十分钟搞清字符集和字符编码分割我 ...

December 22, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-字符串

概述Go 中 字符串 语义和其余编程语言中的字符串中一样,有一点不同的中央在于: Go 中字符串值无奈扭转,能够了解为:一旦实现定义之后,字符串就是一个 常量。 解释型字符串应用双引号 " 括起来,其中的本义符将被替换,本义符包含: \n: 换行符\r: 回车符\t: tab 键\u: 或 \U:Unicode 字符\\: 反斜杠本身其余本义符示例package mainfunc main() { s := "hello\nworld" println(s)}// $ go run main.go// 输入如下/** hello world*/非解释型字符串应用反引号 ` 括起来,其中的本义符会被原样输入。 示例package mainfunc main() { s := `hello world \n` println(s)}// $ go run main.go// 输入如下/** hello world \n*/字符串不可变的语义字符串的 不可变 是指: 字符串定义之后,每个索引地位的字符不可被批改,如下所示: package mainfunc main() { s := "hello world" s[0] = 'a' s[1] = 'b'}// $ go run main.go// 输入如下/** ./main.go:5:2: cannot assign to s[0] (value of type byte) ./main.go:6:2: cannot assign to s[1] (value of type byte)*/然而,能够通过为字符串从新赋值,扭转原有字符串,如下所示: ...

December 22, 2022 · 2 min · jiezi

关于go:Go-快速入门指南-调试打印

一般打印长处:内置函数,不须要引入额定的包,简略不便。有余:无奈进行格式化打印,无奈残缺打印复合数据结构 (如数组, Map 等)。println 函数打印多个传入的参数,并主动加一个换行。 例子package mainfunc main() { println(1024, "hello world", true)}// $ go run main.go// 输入如下 /** 1024 hello world true*/print 函数和 println 性能一样,然而不会主动加换行。 格式化打印这里先介绍 2 个办法,别离是 fmt 包外面的 Println() 和 Printf(), 大多数场景下都实用。 fmt.Println()性能上和 println 函数 相似,然而能够打印复合数据结构 (如数组, Map 等)。 例子package mainimport "fmt"func main() { fmt.Println(1024, "hello world", true)}// $ go run main.go// 输入如下 /** 1024 hello world true*/fmt.Printf()最重要的格式化打印函数之一,能够针对不同数据类型和数据结构进行打印,十分弱小。 格式化规定和 C 系列 编程语言的 printf() 格式化规定差不多。 通用%v 默认格局%+v 针对构造体,在 %v 的根底上输入构造体的键名%#v Go 语言语法格局的值%T Go 语言语法格局的类型和值%% 输入 %, 相当于本义整型%b 二进制格局%c 对应的 Unicode 码%d 十进制%o 八进制%O 八进制,加上 0o 前缀%q Go 语言语法本义后的单引号字符 (很少应用) 例如 97 会输入 'a'%x 十六进制 (小写), 例如 0xaf%X 十六进制 (大写), 例如 0xAF%U Unicode 例如 "U+%04X"Bool%t true 或 false浮点型%b 指数为 2 的幂的无小数迷信计数法,例如 -123456p-78%e 迷信计数法, 例如 -1.234456e+78%E 迷信计数法, 例如 -1.234456E+78%f 惯例小数点表示法 (个别应用这个), 例如 123.456%F 和 %f 性能一样字符串%s 字符串%q 将双引号 " 本义后的字符串%x 将字符串作为小写的十六进制%X 将字符串作为大写的十六进制指针%p 地址的十六进制,前缀为 0x例子package mainimport "fmt"func main() { n := 1024 fmt.Printf("n = %d\n", n) // 输入整型 pi := 3.1415 fmt.Printf("pi = %f\n", pi) // 输入浮点数 str := "hello world" fmt.Printf("str = %s\n", str) // 输入字符串 yes := true fmt.Printf("yes = %t\n", yes) // 输入布尔型 x := 17 fmt.Printf("yes = %b\n", x) // 输入二进制}// $ go run main.go// 输入如下/** n = 1024 pi = 3.141500 str = hello world yes = true x = 10001*/fmt.Printf() 技巧在打印中,如果一个变量打印屡次,能够通过 [1] 来示意后续变量全副以第一个为准。 ...

December 22, 2022 · 2 min · jiezi

关于go:Go-快速入门指南-包的导入

导入包关键字 import 语法规定单个导入import "包名"多个导入import ( "包名1" "包名2" "包名3" ...)导入包应用别名import 别名 "包名"例子导入 打印包package mainimport "fmt"func main() { fmt.Println("hello world")}导入 打印包 和 字符串包package mainimport ( "fmt" "strings")func main() { fmt.Println("hello world") fmt.Println(strings.Repeat("hello ", 3)) // 字符串反复}// $ go run main.go// 输入如下/** hello world hello hello hello*/导入包应用别名package mainimport ( "fmt" myStr "strings")func main() { fmt.Println(myStr.Repeat("hello ", 3))}// $ go run main.go// 输入如下/** hello hello hello*/分割我

December 22, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-运算优先级

运算符优先级从高到低||&&<- (通道操作符,前面会讲到,临时先疏忽)== != < <= > >=+ - | ^* / % << >> & &^^ !最佳实际通过应用括号来晋升某个表达式的运算优先级,进步代码可读性。 例子package mainfunc main() { println(((1+2)*3+3)/4 + 5)}// $ go run main.go// 输入如下 /** 8*/

December 22, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-常量生成器

常量生成器关键字 iota, 创立一系列相干的值,省略一一定义。 语法规定const ( 常量1 [常量类型] = iota 常量2 常量3 常量4 常量5 ...)例子package mainconst ( Sunday int = iota Monday // 1 Tuesday // 2 Wednesday // 3 Thursday // 4 Friday // 5 Saturday // 6)// $ go run main.go// 输入如下 /** 0 1 2 3 4 5 6*/在下面的申明中,Sunday 的值为 0, Monday 的值为 1, 以此类推。 分割我

December 22, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-自定义类型

自定义类型关键字 type, 次要用来对同一种类型进行形象。 语法规定type 自定义类型名称 具体类型# 例子type Number int同时定义多个自定义类型package maintype ( Number int Name string Has bool)嵌套定义能够基于已有的自定义类型,定义一个新的自定义类型。 package maintype ( Number int Name string Has bool)type Number2 Number应用规定和变量应用规定一样。 package maintype ( Number int Name string Has bool)func main() { var x Number = 1024 var n Name = "abc" var h Has = true println(x) println(n) println(h)}// $ go run main.go// 输入如下 /** 1024 abc true*/分割我

December 22, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-常量

常量关键字 const, 和其余编程语言中常量的语义一样,定义后无奈批改。 语法规定const 常量名称 [常量类型] = 常量值# 例子const Pi float = 3.14159其中,常量类型为可选,因为编译器能够依据值来推断其类型 (倡议指定类型,能够加强语义性)。 同时定义多个常量const ( 常量名称 [常量类型] = 常量值 常量名称 [常量类型] = 常量值 常量名称 [常量类型] = 常量值 ...)例子package mainconst ( Sunday = 0 Monday = 1 Tuesday = 2 Wednesday = 3 Thursday = 4 Friday = 5 Saturday = 6)func main() { println(Sunday) println(Monday) println(Tuesday) println(Wednesday) println(Thursday) println(Friday) println(Saturday)}// $ go run main.go// 输入如下 /** 0 1 2 3 4 5 6*/显式/隐式浮点类型 ...

December 22, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-空白标识符

概述_ 是一个非凡的标识符,被称为空白标识符。它能够像其余标识符那样用于变量的申明或赋值(任何类型都能够赋值给它),但任何赋给这个标识符的值都将被抛弃,因而这些值不能在后续的代码中应用,也不能够应用这个标识符作为变量对其它变量进行赋值或运算。 示例防止 "变量未应用" 谬误当定义一个变量后,始终未应用 (或者因为某些逻辑分支执行不到),为了防止运行报错,能够将其赋值给空白标识符。 package mainfunc main() { n := 1024 _ = n // 如果没有这行代码,就会报错 Unused variable 'n'}疏忽谬误package mainimport "strconv"func main() { n, _ := strconv.Atoi("1024") // 函数的第 2 个返回值是一个谬误类型值 println(n)}// $ go run main.go// 输入如下 /** 1024 */备注上述代码只是为了演示 _ 的作用,在理论开发过程中,最佳实际应该是解决每一个返回的 谬误。 分割我

December 22, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-变量

变量关键字 var, 定义后能够批改。 语法规定var 关键字var 变量名称 [变量类型] = 变量值 # 例子 var pi float = 3.14159其中,变量类型为可选,因为编译器能够依据值来推断其类型 (倡议指定类型,能够加强语义性)。 间接定义变量名称 := 变量值# 例子pi := 3.14159其中,不须要变量类型,因为编译器能够依据值来推断其类型。 注意事项在函数中定义的变量,定义后必须应用,否则编译时会报错: Unused variable 'pi' 同时定义多个变量var ( 变量名称 [变量类型] = 变量值 变量名称 [变量类型] = 变量值 变量名称 [变量类型] = 变量值 ...)例子package mainfunc main() { var ( pi float64 = 3.14159 page int = 1 name = "abc" ) println(pi) println(page) println(name)}// $ go run main.go// 输入如下 /** +3.141590e+000 1 abc*/显式/隐式浮点类型 ...

December 22, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-基本代码格式关键字-预定义标识符

文件名Go 的文件以 .go 为后缀,文件名称必须以字母结尾 (任何 UTF-8 编码的字符或 _),前面追随 0 个或多个字符或者 Unicode 数字。 正确的命名形式: filenamefileNamefile_namefilename2_filename谬误的命名形式: 1filename (以数字结尾)switch (Go 关键字)x+y (运算符)根本代码格局不须要在语句或申明前面应用分号,除非多个语句和申明呈现在同一行,比方前面要讲到的 for 循环{ 必须和判断语句、循环语句、函数表达式等在同一行,不能单独成行关键字Go 一共 25 个关键字,简洁到了极点。 大部分关键字其余编程语言中也都有,比拟非凡的几个是: chan, defer, go, select, 不过这里无需记忆,前面章节都会讲到。 breakdefaultfuncinterfaceselectcasedefergomapstructchanelsegotopackageswitchconstfallthroughifrangetypecontinueforimportreturnvar预约义标识符Go 一共 37 个预约义标识符 和关键字一样,大部分关键字其余编程语言中也都有,可能名称有所区别,比方 int64 为 long, float64 为 double, 这里无需记忆,前面章节都会讲到。 常量truefalseiotanil类型整型intint8int16int32int64uintuint8uint16uint32uint64uintptr浮点型float32float64复数型complex64complex128布尔型bool字节byte非凡的整型rune (其实就是 int32, 次要用来辨别字符值和整数值)字符串类型string谬误类型error函数makelencapnewappendcopyclosedeletecomplexrealimagpanicrecover分割我

December 22, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-关键字和运行方式

示例代码package main // 包名,必须申明func main() { println("hello world")}Go 代码组织形式Go 代码是应用包来组织的,相似于其余编程语言中的库、模块、命名空间。 包一个包由一个或多个 .go 文件组成,放在一个文件夹中。比方字符串相干解决代码全副放在 string 包中。每个 .go 文件的开始必须应用 package 申明,比方字符串包申明为 package string。 main 包一个非凡的包,用来定义具体的执行程序 (比如说咱们的业务程序)。 main 函数如果以后包是 main 包, 那么 main 函数 就是执行程序的入口。如果以后包不是 main 包, 那么 main 函数 就是一个一般的函数。Go 程序的运行形式编译并运行 (一步实现) 命令行运行 go run 文件名.go, 比方 go run main.go,先编译为可执行文件,而后运行 (两步实现) 命令行运行 go run 文件名.go, 比方 go run main.go生成可执行文件,比方 main执行可执行文件,./main备注理论性的货色就介绍到这里,笔者认为新学一门编程语言时,能疾速地通过编写代码,来理解语法以及程序结构比纯看实践更高效。

December 22, 2022 · 1 min · jiezi

关于go:Go-快速入门指南-环境安装

概述为了节俭篇幅,笔者将罕用的 3 种操作系统对应的装置教程汇总到了一起,读者能够间接抉择对应内容浏览。 Windows 环境搭建下载1. 关上 Go 官网下载地址(https://go.dev/dl/),抉择 Microsoft Windows 2. 点击对应的版本开始下载,比方 go1.19.1.windows-amd64.msi 装置双击下载好的 .msi 文件,而后下一步 -> 下一步 -> 最终实现 测试关上命令行,输出go version,回车,失常状况下,会输入相似上面的内容 go version go1.19.1 windows/amd64输出 go,回车,失常状况下,会输入相似上面的内容 Go is a tool for managing Go source code.Usage:  go <command> [arguments]The commands are:  .........Use "go help <topic>" for more information about that topic.Hello World和学习其余编程语言一样,写一个经典例子。 • 关上一个目录,比方 D:\Code\Go-Examples • 新建一个文件 main.go,输出如下代码 package mainfunc main() {    println("hello world")}• 保留文件 • 在命令行输出 go run D:\Code\Go-Examples\main.go, 回车,(当然,也能够切换到D:\Code\Go-Examples, 而后输出 go run main.go) • 失常状况下,会输入如下内容     hello world祝贺你,实现了 Go 的第一个程序。 备注在前面的例子中,为了简化代码,对立默认代码门路为 D:\Code\Go-Examples,并且目录曾经切换实现。 Mac 装置下载1. 关上 Go 官网下载地址(https://go.dev/dl/) 2. 依据硬件架构抉择 Apple macOS (ARM64) 或 Apple macOS (x86-64) 3. 点击对应的版本开始下载,比方 go1.19.1.darwin-arm64.pkg 装置双击下载好的 .pkg 文件,后续过程和装置其余 Mac App 一样 测试关上命令行,输出go version,回车,失常状况下,会输入相似上面的内容 go version go1.19.1 darwin/arm64• 输出go,回车,失常状况下,会输入相似上面的内容Go is a tool for managing Go source code.Usage:  go <command> [arguments]The commands are:.........Use "go help <topic>" for more information about that topic.Hello World和学习其余编程语言一样,写一个经典例子。 • 关上一个目录,比方 /Users/codes/Go-Examples • 新建一个文件 main.go,输出如下代码 package mainfunc main() {    println("hello world")}• 保留文件 • 在命令行输出 go run /Users/codes/Go-Examples/main.go, 回车, (当然,也能够切换到/Users/codes/Go-Examples, 而后输出 go run main.go) • 失常状况下,会输入如下内容 hello world祝贺你,实现了 Go 的第一个程序。 备注在前面的例子中,为了简化代码,对立默认代码门路为 /Users/codes/Go-Examples,并且目录曾经切换实现。 Linux 装置下载1. 关上 Go 官网下载地址(https://go.dev/dl/) ...

December 22, 2022 · 1 min · jiezi

关于go:一文读懂Go函数调用

导读|Go的函数调用时参数是通过栈传递还是寄存器传递?应用哪个版本的Go语言能让程序运行性能晋升5%?腾讯后盾开发工程师涂明光将带你由浅入深理解函数调用,并联合不同版本Go进行实操解答。 函数调用基本概念 1)调用者caller与被调用者callee 如果一个函数调用另外一个函数,那么该函数被称为调用者函数,也叫做caller,而被调用的函数称为被调用者函数,也叫做callee。比方函数main中调用sum函数,那么main就是caller,而sum函数就是callee。 2)函数栈和函数栈帧 函数执行时须要有足够的内存空间,供它寄存局部变量、参数等数据,这段空间对应到虚拟地址空间的栈,也即函数栈。在古代支流机器架构上(例如x86)中,栈都是向下成长的。栈的增长方向是从高位地址到位置地址向下进行增长。 调配给一个个函数的栈空间被称为“函数栈帧”。Go语言中函数栈帧布局是这样的:先是调用者caller栈基地址,而后是调用者函数caller的局部变量、接着是被调用函数callee的返回值和参数。而后是被调用者callee的栈帧。 留神,栈和栈帧是不一样的。在一个函数调用链中,比方函数A调用B,B调用C,则在函数栈上,A的栈帧在下面,上面顺次是B、C的函数栈帧。Go1.17以前的版本,函数栈空间布局如下: 函数调用剖析 通过在centos8上装置gvm,能够不便切换多个Go版本测试不同版本的个性。 gvm地址:https://github.com/moovweb/gvm 执行: gvm list显示gvm装置的go版本列表: go1.14.2 go1.15.14 go1.15.7 go1.16.1 go1.16.13 go1.17.1 go1.18 go1.18.1 system1)Go15版本函数调用剖析执行 gvm use go1.15.14切换到 go1.15.14版本,咱们定义一个函数调用: package mainfunc main() { var r1, r2, r3, r4, r5, r6, r7 int64 = 1, 2, 3, 4, 5, 6, 7 A(r1, r2, r3, r4, r5, r6, r7)}func A(p1, p2, p3, p4, p5, p6, p7 int64) int64 { return p1 + p2 + p3 + p4 + p5 + p6 + p7}应用命令打印出main.go汇编: ...

December 22, 2022 · 8 min · jiezi

关于go:Golang-Ubuntu系统环境搭建

1.https://go.dev/下载golang包 2.解压到/home/hong/Downloads/go1.19.4.linux-amd64/go目录下GOROOT就是解压的目录GOPATH就是代码目录 vi ~/.bashrc export GOROOT=/home/hong/Downloads/go1.19.4.linux-amd64/go export GOPATH=/home/bruce/goProject export GOBIN=$GOPATH/bin export PATH=$PATH:$GOROOT/bin export PATH=$PATH:$GOPATH/binsource ~/.bashrc 3.go代理 go env -w GOPROXY=https://goproxy.io,directgo env -w GO111MODULE=on

December 21, 2022 · 1 min · jiezi

关于go:OpenTelemetry系列-三|-神秘的采集器-Opentelemetry-Collector

前言上个篇章中咱们次要介绍了OpenTelemetry的客户端的一些数据生成形式,然而客户端的数据最终还是要发送到服务端来进行对立的采集整合,这样能力看到残缺的调用链,metrics等信息。因而在这个篇章中会次要介绍服务端的采集能力。 客户端数据上报客户端会依据肯定的规定生成调用链,metrics,logs等信息,而后就会将其推送到服务器远端。一般来说OpenTelemetry的服务端客户端传递数据的申请协定规范是Http和Grpc协定,在各语言的sdk以及服务端实现中都应该蕴含这两个数据协定的的实现。 依照常理来说调用链等数据的数据量极大,因而在客户端就会有一些相似于Batch的操作选项,此选项会将多个Span信息整合到一起一并发送,以减小网络端的损耗。 客户端的这种数据上报咱们会统称为export,同时,实现这些上报的组件咱们对立称作exporters。exporters会蕴含不同种的数据协定和格局,默认的格局为OTLP。 OTLPOTLP是指OpenTelemetry Protocol,即OpenTelemetry数据协定。OTLP标准规定了客户端和服务采集端之间的遥测数据的编码,传输和投送。 OTLP在实现上分为OTLP/gRPC和OTLP/HTTP。 OTLP/HTTPOTLP/HTTP在数据传输的时候反对两种模式:二进制和json 二进制应用proto3编码标准,且必须在申请头中标注Content-Type: application/x-protobuf JSON格局应用proto3规范定义的JSON Mapping来解决Protobuf和JSON之间的映射关系。 OTLP/gRPC一般申请:在客户端和服务端建设连贯后,客户端能够继续一直的发送申请到服务端,服务端会一一回应。并发申请:客户端能够在服务端未回应前发送下一个申请,以此进步并发量。 CollectorCollector简介OpenTelemetry提供了开源的Collector来进行客户端数据的上报采集,解决和输入。otel collector是一个反对了多种协定,多种数据源的“万能”采集器。能够说是你能想到的很多数据源他都可能间接反对。 otel collector应用golang实现,到文章目前编写的时候曾经公布了1.0.0的rc版本。Collector辨别为了两个我的项目opentelemetry-collector,opentelemetry-collector-contrib。opentelemetry-collector是外围我的项目,实现了collector的根本机制以及一些根底的组件,而opentelemetry-collector-contrib则会有大量的组件,而这些组件因为不同起因不便被间接集成到外围的collector中,因而独自构建了一个我的项目来集成这些组件。咱们后续的collector性能介绍和验证都会基于opentelemetry-collector-contrib来进行。 Collector应用otel collector的组成是很清晰的,分为: ReceiverProcessorExporterExtensionService整个配置文件的样例如下: receivers: otlp: protocols: grpc: http:exporters: jaeger: endpoint: localhost:14250 tls: insecure: true logging: loglevel: debugprocessors: batch:extensions: health_check: pprof: zpages:service: extensions: [pprof, zpages, health_check] pipelines: traces: receivers: [otlp] exporters: [jaeger, logging] processors: [batch]这个配置是我本地测试时应用的一个配置,这个配置很简略,接管otlp http/grpc的上报数据,进行batch解决,而后输入到控制台日志和jaeger中。咱们将各项数据源和插件配置实现后,在流水线中配置应用的数据源和插件。 ReceiverReceiver是指的接收器,即collector接管的数据源的模式。Receiver能够反对多个数据源,也能反对pull和push两种模式。 receivers: # Data sources: logs fluentforward: endpoint: 0.0.0.0:8006 # Data sources: metrics hostmetrics: scrapers: cpu: disk: filesystem: load: memory: network: process: processes: swap: # Data sources: traces jaeger: protocols: grpc: thrift_binary: thrift_compact: thrift_http: # Data sources: traces kafka: protocol_version: 2.0.0 # Data sources: traces, metrics opencensus: # Data sources: traces, metrics, logs otlp: protocols: grpc: http: # Data sources: metrics prometheus: config: scrape_configs: - job_name: "otel-collector" scrape_interval: 5s static_configs: - targets: ["localhost:8888"] # Data sources: traces zipkin:上述是一个receiver的样例,外面展现了多种不同的接收数据源的配置。 ...

December 18, 2022 · 2 min · jiezi

关于go:Go-120要来了看看都有哪些变化第2篇

前言Go官网团队在2022.12.08公布了Go 1.20 rc1(release candidate)版本,Go 1.20的正式release版本预计会在2023年2月份公布。 让咱们先睹为快,看看Go 1.20给咱们带来了哪些变动。 装置办法: $ go install golang.org/dl/go1.20rc1@latest$ go1.20rc1 download这是Go 1.20版本更新内容详解的第2篇,欢送大家关注公众号,及时获取本系列最新更新。 第1篇次要波及Go 1.20在语言、可移植性方面的优化,原文链接:Go 1.20版本升级内容第1篇。 Go 1.20公布清单和Go 1.19相比,改变内容适中,次要波及语言(Language)、可移植性(Ports)、工具链(Go Tools)、运行时(Runtime)、编译器(Compiler)、汇编器(Assembler)、链接器(Linker)和外围库(Core library)等方面的优化。 本文重点介绍Go 1.20在Go工具链方面的优化。 Go command$GOROOT/pkg门路不再存储规范库源代码编译后生成的文件,包含以下几点: go install不再往$GOROOT/pkg目录写文件。go build不再查看$GOROOT/pkg下的文件。Go公布包不再带有这些编译文件。以macOS环境来演示:在Go 1.16版本里,$GOROOT/pkg目录下的内容如下: $ go versiongo version go1.16.5 darwin/amd64$ go env GOROOT/usr/local/opt/go/libexec$ ls /usr/local/opt/go/libexec/pkgdarwin_amd64 darwin_amd64_race include tool然而在Go 1.20rc1版本里,$GOROOT/pkg目录下的内容如下: $ go1.20rc1 versiongo version go1.20rc1 darwin/amd64$ go1.20rc1 env GOROOT/Users/xxx/sdk/go1.20rc1$ ls /Users/xxx/sdk/go1.20rc1/pkginclude tool少了darwin_amd64和darwin_amd64_race这2个文件夹: $ ls /usr/local/opt/go/libexec/pkg/darwin_amd64archive database go io net.a runtime testing.abufio.a debug hash io.a os runtime.a textbytes.a embed.a hash.a log os.a sort.a timecmd encoding html log.a path strconv.a time.acompress encoding.a html.a math path.a strings.a unicodecontainer errors.a image math.a plugin.a sync unicode.acontext.a expvar.a image.a mime reflect.a sync.a vendorcrypto flag.a index mime.a regexp syscall.acrypto.a fmt.a internal net regexp.a testing$ ls /usr/local/opt/go/libexec/pkg/darwin_amd64_race/archive debug hash io.a os runtime.a textbufio.a embed.a hash.a log os.a sort.a timebytes.a encoding html log.a path strconv.a time.acompress encoding.a html.a math path.a strings.a unicodecontainer errors.a image math.a plugin.a sync unicode.acontext.a expvar.a image.a mime reflect.a sync.a vendorcrypto flag.a index mime.a regexp syscall.acrypto.a fmt.a internal net regexp.a testingdatabase go io net.a runtime testing.a从Go 1.20开始,规范库的package会按需编译,编译后生成的文件会缓存在编译缓存(build cache)里,就像非GOROOT下的package一样。这个批改减小了Go安装包的大小。 ...

December 17, 2022 · 2 min · jiezi

关于go:使用gomicroconfig-读取配置文件

一、前言很多人入门的go应用的第一个框架有可能就是go-micro,特地是go-micro/config库,曾经成为了,很多其余框架写config配置的默认参考构造,明天咱们就来学习一下,如何应用go-micro/config 读取配置文件

December 17, 2022 · 1 min · jiezi

关于go:带读-Go-in-Action中文Go语言实战数组切片映射

数组零值数组申明var arr [5]int 字面量数组申明arr := [5]int{1,2,3,4,5} 三个点主动计算数组长度arr := [...]int{1,2,3,4,5} 切片申明长度和容量相等的切片slice := make([]string, 6) 申明长度和容量不等的切片slice := make([]string, 3,6) 字面量申明切片slice := []int {1, 2, 3) 索引申明切片slice := []string{99,""} nil切片var slice []int 空切片slice := make([]int, 0)或者slice := []int{}未完待续。。 参考:Kennedy W , Ketelsen B , Martin E S . Go in action. 2016.

December 16, 2022 · 1 min · jiezi

关于go:带读-Go-in-Action中文Go语言实战打包和工具链

包包名给包及其目录命名时,应该应用简洁、清晰且全小写的名字 包的默认导入优先从go的装置目录查找,而后去go path找,找到既停,没找到报错 工具go build执行编译操作,有main包的状况下生成可执行文件 go clean删除可执行文件 go run=go build +执行可执行文件 go vet捕捉四类谬误:1.Printf类函数调用时,类型匹配谬误的参数。2.定义罕用的办法时,办法签名的谬误。3.谬误的构造标签。4.没有指定字段名的构造字面量。 go fmt代码疾速格式化,举荐保留文件or提交代码前用。 go doc查看go文档的两种形式: 形式一 go doc tar形式二 go doc -http =: 6060go mod书上没介绍 可参考博客 参考:Kennedy W , Ketelsen B , Martin E S . Go in action. 2016.

December 16, 2022 · 1 min · jiezi

关于go:带读-Go-in-Action中文Go语言实战-三

initinit内容本书举荐将map映射(或者叫注册)之类的筹备工作放在init外面,Golang会保障init中的函数在main函数调用之前执行结束。例如: // init registers the default matcher with the program.func init() { var matcher defaultMatcher Register("default", matcher)}// Register is called to register a matcher for use by the program.func Register(feedType string, matcher Matcher) { if _, exists := matchers[feedType]; exists { log.Fatalln(feedType, "Matcher already registered") } log.Println("Register", feedType, "matcher") matchers[feedType] = matcher}如何应用init咱们应用下划线标识符作为别名导入包,实现了这个调用。这种办法能够让编译器在导入未被援用的包时不报错,而且依旧会定位到包内的init函数。代码如下: import ( _ "December15/sample/matchers" "December15/sample/search")构造类型golang中没有类的概念,这里的构造类型的作用就相当于java外面的类,例如这里定义了4个类: type ( // item defines the fields associated with the item tag // in the rss document. item struct { XMLName xml.Name `xml:"item"` PubDate string `xml:"pubDate"` Title string `xml:"title"` Description string `xml:"description"` Link string `xml:"link"` GUID string `xml:"guid"` GeoRssPoint string `xml:"georss:point"` } // image defines the fields associated with the image tag // in the rss document. image struct { XMLName xml.Name `xml:"image"` URL string `xml:"url"` Title string `xml:"title"` Link string `xml:"link"` } // channel defines the fields associated with the channel tag // in the rss document. channel struct { XMLName xml.Name `xml:"channel"` Title string `xml:"title"` Description string `xml:"description"` Link string `xml:"link"` PubDate string `xml:"pubDate"` LastBuildDate string `xml:"lastBuildDate"` TTL string `xml:"ttl"` Language string `xml:"language"` ManagingEditor string `xml:"managingEditor"` WebMaster string `xml:"webMaster"` Image image `xml:"image"` Item []item `xml:"item"` } // rssDocument defines the fields associated with the rss document. rssDocument struct { XMLName xml.Name `xml:"rss"` Channel channel `xml:"channel"` })空构造实现接口本书举荐在不须要保护任何状态的时候,用空构造体来实现接口,比方: ...

December 16, 2022 · 2 min · jiezi

关于go:godongle-022-版本发布一个轻量级语义化的-golang-编码解码加密解密库

dongle 是一个轻量级、语义化、对开发者敌对的 Golang 编码解码和加密解密库 Dongle 已被 awesome-go 收录, 如果您感觉不错,请给个 star 吧 github.com/golang-module/dongle gitee.com/golang-module/dongle 更新日志 Add support for blowfish encryption and decryptionAdd support for AnsiX923 and ISO97971 padding modesRename encode.go to encoder.go, decode.go to decoder.go, encrypt.go to encrypter.go, sign.go to signer.go, verify.go to verifier.goRename isSupportedHash() to (receiver).isRsaSupported() in rsa.goRename carbon.RAW to carbon.Raw, carbon.HEX to carbon.Hex, carbon.BASE64 to carbon.Base64性能清单 [x] Hex 编码、解码[x] Base16 编码、解码[x] Base32 编码、解码[x] Base58 编码、解码[x] Base62 编码、解码[x] Base64 编码、解码[x] Base64URL 编码、解码[x] SafeURL 编码、解码[x] Base85 编码、解码[x] Base91 编码、解码[x] Base100 编码、解码[x] Morse(摩斯) 编码、解码[x] Md2 加密[x] Md4 加密[x] Md5 加密[x] Sha1 加密[x] Sha3-224 加密[x] Sha3-256 加密[x] Sha3-384 加密[x] Sha3-512 加密[x] Sha224 加密[x] Sha256 加密[x] Sha384 加密[x] Sha512 加密[x] Sha512-224 加密[x] Sha512-256 加密[x] Ripemd160 加密[x] Hmac-md2 加密[x] Hmac-md4 加密[x] Hmac-md5 加密[x] Hmac-sha1 加密[x] Hmac-sha3-224 加密[x] Hmac-sha3-256 加密[x] Hmac-sha3-384 加密[x] Hmac-sha3-512 加密[x] Hmac-sha224 加密[x] Hmac-sha256 加密[x] Hmac-sha384 加密[x] Hmac-sha512 加密[x] Hmac-sha512-224 加密[x] Hmac-sha512-256 加密[x] Hmac-ripemd160 加密[x] Hmac-sm3 加密[ ] Rc2 加密、解密[x] Rc4 加密、解密[ ] Rc5 加密、解密[ ] Rc6 加密、解密[x] Tea 加密、解密[ ] Xtea 加密、解密[x] Aes 加密、解密[x] Blowfish 加密、解密[x] Des 加密、解密[x] 3Des 加密、解密[x] Rsa 加密、解密[ ] Ecc 加密、解密[ ] Sm2 加密、解密[x] Sm3 加密[ ] Sm4 加密、解密[ ] Sm7 加密、解密[ ] Sm9 加密、解密[x] Bcrypt 签名、验签[x] Ed25519 签名、验签[x] Rsa 签名、验签[ ] Dsa 签名、验签

December 16, 2022 · 1 min · jiezi

关于go:给hash表分片降低锁粒度提高锁性能

锁就像漏斗,将并发解决的多个线程变成串行化的模式,咱们能够构建一个反对成千上万并发的零碎,然而如果锁解决的不好会重大影响零碎的性能,就像领有多条车道的高速公路变成了单行道。 举个例子,如果咱们应用go的map来实现一个简略的缓存,因为map不是并发平安,所以咱们还要借助sync包的锁来保障并发平安,于是咱们很容易写出上面这样的代码: package simple_cacheimport ( "sync")type Cache struct { items map[string][]byte lock *sync.RWMutex}func New() *Cache { return &Cache{ items: make(map[string][]byte, 2000), lock: new(sync.RWMutex), }}func (c *Cache) Get(key string) []byte { // 取数据只有加读锁 c.lock.RLock() defer c.lock.RUnlock() return c.items[key]}func (c *Cache) Set(key string, data []byte) { c.lock.Lock() defer c.lock.Unlock() c.items[key] = data}这段代码思考到了锁其实曾经算是不错了,然而每次调用set()办法去设置缓存值的时候不仅将并发读写变成了串行化的模式,就连get()办法也会被阻塞住。在理论生产中应用这段代码作为缓存的时候,map中会缓存大量数据,set()调用可能会很频繁,而且在set()内还须要判断缓存的容量是否足够,这些都会使锁的工夫变长。 而后咱们不得不思考如何优化一下锁的性能。下面代码的问题是每次set()都锁住了整个map,于是咱们就想到能不能只锁住一部分,这样就能升高锁对性能的耗费。咱们能够把原先这个大的缓存分成若干个小的分片,每个分片就是原先的一个Cache,而后再将这些分片放入一个大的map中,依据缓存key值通过hash计算后的值找到对应的分片。对下面代码革新如下: package simple_cacheimport ( "crypto/sha1" "fmt" "sync")type Cache map[string]*ShardCachetype ShardCache struct { items map[string][]byte lock *sync.RWMutex}func NewCache() *Cache { cache := make(Cache, 256) for i := 0; i < 256; i++ { cache[fmt.Sprintf("%02x", i)] = &ShardCache{ items: make(map[string][]byte, 2000), lock: new(sync.RWMutex), } } return &cache}func (c Cache) getShard(key string) *ShardCache { hasher := sha1.New() hasher.Write([]byte(key)) // 转16进制后取前两位 shardKey := fmt.Sprintf("%x", hasher.Sum(nil))[0:2] return c[shardKey]}func (c Cache) Get(key string) []byte { // 取数据只有加读锁 shard := c.getShard(key) shard.lock.RLock() defer shard.lock.RUnlock() return shard.items[key]}func (c Cache) Set(key string, data []byte) { shard := c.getShard(key) shard.lock.Lock() shard.lock.Unlock() shard.items[key] = data}这里咱们一共给缓存设置了256(16^2)个分片,对于任意的一个缓存key值通过hash后通过fmt.Sprintf("%x", hasher.Sum(nil))[0:2]转16进制后取前两位后都能在缓存中找到对应的分片 ...

December 15, 2022 · 1 min · jiezi

关于go:带读-Go-in-Action中文Go语言实战二接口

接口接口的定义这里用一个例子阐明了golang中接口的含意与用法,先看代码 // Launch the goroutine to perform the search. go func(matcher Matcher, feed *Feed) { Match(matcher, feed, searchTerm, results) waitGroup.Done() }(matcher, feed)下面这段代码是search.go中调用Match函数的代码,其中Match的第一个参数“matcher”就是接口定义如下: // Matcher defines the behavior required by types that want// to implement a new search type.type Matcher interface { Search(feed *Feed, searchTerm string) ([]*Result, error)}通过这个接口,咱们的匹配器就能够用统一且通用的办法解决不同类型的匹配值,是不是很优雅 接口的命名命名接口的时候,也须要恪守Golang的命名常规。如果接口类型只蕴含一个办法,那么这个类型的名字以er结尾。咱们的例子里就是这么做的,所以这个接口的名字叫作Matcher。如果接口类型外部申明了多个办法,其名字须要与其行为关联。类实现接口这里提供一个最简略的这个接口的类的实现 package searchtype defaultMatcher struct {}func (m defaultMatcher) Search(feed *Feed, searchTerm string) ([]*Result, error) { return nil, nil}下面的代码有两点须要阐明:1.这个代码创立了一个类(defaultMatcher),这是一个空构造体,空构造体创立的时候零碎不会调配任何内存,不须要保护状态,只须要实现接口即可,故,空构造体很适宜创立没有任何状态的类型。2.func 前面 Search后面的这个括号的内容是指定接收者,说白了就是把接下来要写的函数绑定在指定的类上,相似Java的成员办法。 接口办法调用受限因为大部分办法在被调用后都须要保护接收者的值的状态,所以,一个最佳实际是,将办法的接收者申明为指针。对于defaultMatcher类型来说,应用值作为接收者是因为创立一个defaultMatcher类型的值不须要分配内存。因为defaultMatcher不须要保护状态,所以不须要指针模式的接收者。与间接通过值或者指针调用办法不同,如果通过接口类型的值调用办法,规定有很大不同, 如代码清单2-38所示。应用指针作为接收者申明的办法,只能在接口类型的值是一个指针的时候被调用。应用值作为接收者申明的办法,在接口类型的值为值或者指针时,都能够被调用。 ...

December 15, 2022 · 1 min · jiezi

关于go:Go语言性能剖析利器pprof实战

作者:耿宗杰 前言对于pprof的文章在网上已是车载斗量,却是千篇一律的命令介绍,鲜有真正实操的,本文将参考Go社区材料,联合本人的教训,实战Go程序的性能剖析与优化过程。 优化思路首先说一下性能优化的个别思路。零碎性能的剖析优化,肯定是从大到小的步骤来进行的,即从业务架构的优化,到零碎架构的优化,再到零碎模块间的优化,最初到代码编写层面的优化。业务架构的优化是最具性价比的,技术难度绝对较小,却能够带来大幅的性能晋升。比方通过和共事或外部门沟通,缩小了一些接口调用或者去掉了不必要的简单的业务逻辑,能够轻松晋升整个零碎的性能。 零碎架构的优化,比方退出缓存,由http改良为rpc等,也能够在大量投入下带来较大的性能晋升。最初是程序代码级别的性能优化,这又分为两方面,一是合格的数据结构与应用,二才是在此基础上的性能分析。比方在Go语言中应用slice这种不便的数据结构时,尽可能提前申请足够的内存避免append超过容量时的内存申请和数据拷贝;应用并发爱护时尽量由RWMutex 代替mutex,甚至在极高并发场景下应用更细粒度的原子操作代替锁等等。 优化实际上面进入注释,待优化程序是社区中一个例子,代码有点长,实现的算法是驰名的计算机科学家Tarjan的求图的强连通重量算法,对于这个算法的思维请自行google(就别自行百度了~)。以下为实操过程(会有那么一丢丢长。。。): 初始版本代码 havlak1.go: // Go from multi-language-benchmark/src/havlak/go_pro// Copyright 2011 Google Inc.//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.// Test Program for the Havlak loop finder.//// This program constructs a fairly large control flow// graph and performs loop recognition. This is the Go// version.//package mainimport ( "flag" "fmt" "log" "os" "runtime/pprof")type BasicBlock struct { Name int InEdges []*BasicBlock OutEdges []*BasicBlock}func NewBasicBlock(name int) *BasicBlock { return &BasicBlock{Name: name}}func (bb *BasicBlock) Dump() { fmt.Printf("BB#%06d:", bb.Name) if len(bb.InEdges) > 0 { fmt.Printf(" in :") for _, iter := range bb.InEdges { fmt.Printf(" BB#%06d", iter.Name) } } if len(bb.OutEdges) > 0 { fmt.Print(" out:") for _, iter := range bb.OutEdges { fmt.Printf(" BB#%06d", iter.Name) } } fmt.Printf("\n")}func (bb *BasicBlock) NumPred() int { return len(bb.InEdges)}func (bb *BasicBlock) NumSucc() int { return len(bb.OutEdges)}func (bb *BasicBlock) AddInEdge(from *BasicBlock) { bb.InEdges = append(bb.InEdges, from)}func (bb *BasicBlock) AddOutEdge(to *BasicBlock) { bb.OutEdges = append(bb.OutEdges, to)}//-----------------------------------------------------------type CFG struct { Blocks []*BasicBlock Start *BasicBlock}func NewCFG() *CFG { return &CFG{}}func (cfg *CFG) NumNodes() int { return len(cfg.Blocks)}func (cfg *CFG) CreateNode(node int) *BasicBlock { if node < len(cfg.Blocks) { return cfg.Blocks[node] } if node != len(cfg.Blocks) { println("oops", node, len(cfg.Blocks)) panic("wtf") } bblock := NewBasicBlock(node) cfg.Blocks = append(cfg.Blocks, bblock) if len(cfg.Blocks) == 1 { cfg.Start = bblock } return bblock}func (cfg *CFG) Dump() { for _, n := range cfg.Blocks { n.Dump() }}//-----------------------------------------------------------type BasicBlockEdge struct { Dst *BasicBlock Src *BasicBlock}func NewBasicBlockEdge(cfg *CFG, from int, to int) *BasicBlockEdge { self := new(BasicBlockEdge) self.Src = cfg.CreateNode(from) self.Dst = cfg.CreateNode(to) self.Src.AddOutEdge(self.Dst) self.Dst.AddInEdge(self.Src) return self}//-----------------------------------------------------------// Basic Blocks and Loops are being classified as regular, irreducible,// and so on. This enum contains a symbolic name for all these classifications//const ( _ = iota // Go has an interesting iota concept bbTop // uninitialized bbNonHeader // a regular BB bbReducible // reducible loop bbSelf // single BB loop bbIrreducible // irreducible loop bbDead // a dead BB bbLast // sentinel)// UnionFindNode is used in the Union/Find algorithm to collapse// complete loops into a single node. These nodes and the// corresponding functionality are implemented with this class//type UnionFindNode struct { parent *UnionFindNode bb *BasicBlock loop *SimpleLoop dfsNumber int}// Init explicitly initializes UnionFind nodes.//func (u *UnionFindNode) Init(bb *BasicBlock, dfsNumber int) { u.parent = u u.bb = bb u.dfsNumber = dfsNumber u.loop = nil}// FindSet implements the Find part of the Union/Find Algorithm//// Implemented with Path Compression (inner loops are only// visited and collapsed once, however, deep nests would still// result in significant traversals).//func (u *UnionFindNode) FindSet() *UnionFindNode { var nodeList []*UnionFindNode node := u for ; node != node.parent; node = node.parent { if node.parent != node.parent.parent { nodeList = append(nodeList, node) } } // Path Compression, all nodes' parents point to the 1st level parent. for _, ll := range nodeList { ll.parent = node.parent } return node}// Union relies on path compression.//func (u *UnionFindNode) Union(B *UnionFindNode) { u.parent = B}// Constants//// Marker for uninitialized nodes.const unvisited = -1// Safeguard against pathological algorithm behavior.const maxNonBackPreds = 32 * 1024// IsAncestor//// As described in the paper, determine whether a node 'w' is a// "true" ancestor for node 'v'.//// Dominance can be tested quickly using a pre-order trick// for depth-first spanning trees. This is why DFS is the first// thing we run below.//// Go comment: Parameters can be written as w,v int, inlike in C, where// each parameter needs its own type.//func isAncestor(w, v int, last []int) bool { return ((w <= v) && (v <= last[w]))}// listContainsNode//// Check whether a list contains a specific element. //func listContainsNode(l []*UnionFindNode, u *UnionFindNode) bool { for _, ll := range l { if ll == u { return true } } return false}// DFS - Depth-First-Search and node numbering.//func DFS(currentNode *BasicBlock, nodes []*UnionFindNode, number map[*BasicBlock]int, last []int, current int) int { nodes[current].Init(currentNode, current) number[currentNode] = current lastid := current for _, target := range currentNode.OutEdges { if number[target] == unvisited { lastid = DFS(target, nodes, number, last, lastid+1) } } last[number[currentNode]] = lastid return lastid}// FindLoops//// Find loops and build loop forest using Havlak's algorithm, which// is derived from Tarjan. Variable names and step numbering has// been chosen to be identical to the nomenclature in Havlak's// paper (which, in turn, is similar to the one used by Tarjan).//func FindLoops(cfgraph *CFG, lsgraph *LSG) { if cfgraph.Start == nil { return } size := cfgraph.NumNodes() nonBackPreds := make([]map[int]bool, size) backPreds := make([][]int, size) number := make(map[*BasicBlock]int) header := make([]int, size, size) types := make([]int, size, size) last := make([]int, size, size) nodes := make([]*UnionFindNode, size, size) for i := 0; i < size; i++ { nodes[i] = new(UnionFindNode) } // Step a: // - initialize all nodes as unvisited. // - depth-first traversal and numbering. // - unreached BB's are marked as dead. // for i, bb := range cfgraph.Blocks { number[bb] = unvisited nonBackPreds[i] = make(map[int]bool) } DFS(cfgraph.Start, nodes, number, last, 0) // Step b: // - iterate over all nodes. // // A backedge comes from a descendant in the DFS tree, and non-backedges // from non-descendants (following Tarjan). // // - check incoming edges 'v' and add them to either // - the list of backedges (backPreds) or // - the list of non-backedges (nonBackPreds) // for w := 0; w < size; w++ { header[w] = 0 types[w] = bbNonHeader nodeW := nodes[w].bb if nodeW == nil { types[w] = bbDead continue // dead BB } if nodeW.NumPred() > 0 { for _, nodeV := range nodeW.InEdges { v := number[nodeV] if v == unvisited { continue // dead node } if isAncestor(w, v, last) { backPreds[w] = append(backPreds[w], v) } else { nonBackPreds[w][v] = true } } } } // Start node is root of all other loops. header[0] = 0 // Step c: // // The outer loop, unchanged from Tarjan. It does nothing except // for those nodes which are the destinations of backedges. // For a header node w, we chase backward from the sources of the // backedges adding nodes to the set P, representing the body of // the loop headed by w. // // By running through the nodes in reverse of the DFST preorder, // we ensure that inner loop headers will be processed before the // headers for surrounding loops. // for w := size - 1; w >= 0; w-- { // this is 'P' in Havlak's paper var nodePool []*UnionFindNode nodeW := nodes[w].bb if nodeW == nil { continue // dead BB } // Step d: for _, v := range backPreds[w] { if v != w { nodePool = append(nodePool, nodes[v].FindSet()) } else { types[w] = bbSelf } } // Copy nodePool to workList. // workList := append([]*UnionFindNode(nil), nodePool...) if len(nodePool) != 0 { types[w] = bbReducible } // work the list... // for len(workList) > 0 { x := workList[0] workList = workList[1:] // Step e: // // Step e represents the main difference from Tarjan's method. // Chasing upwards from the sources of a node w's backedges. If // there is a node y' that is not a descendant of w, w is marked // the header of an irreducible loop, there is another entry // into this loop that avoids w. // // The algorithm has degenerated. Break and // return in this case. // nonBackSize := len(nonBackPreds[x.dfsNumber]) if nonBackSize > maxNonBackPreds { return } for iter := range nonBackPreds[x.dfsNumber] { y := nodes[iter] ydash := y.FindSet() if !isAncestor(w, ydash.dfsNumber, last) { types[w] = bbIrreducible nonBackPreds[w][ydash.dfsNumber] = true } else { if ydash.dfsNumber != w { if !listContainsNode(nodePool, ydash) { workList = append(workList, ydash) nodePool = append(nodePool, ydash) } } } } } // Collapse/Unionize nodes in a SCC to a single node // For every SCC found, create a loop descriptor and link it in. // if (len(nodePool) > 0) || (types[w] == bbSelf) { loop := lsgraph.NewLoop() loop.SetHeader(nodeW) if types[w] != bbIrreducible { loop.IsReducible = true } // At this point, one can set attributes to the loop, such as: // // the bottom node: // iter = backPreds[w].begin(); // loop bottom is: nodes[iter].node); // // the number of backedges: // backPreds[w].size() // // whether this loop is reducible: // type[w] != BasicBlockClass.bbIrreducible // nodes[w].loop = loop for _, node := range nodePool { // Add nodes to loop descriptor. header[node.dfsNumber] = w node.Union(nodes[w]) // Nested loops are not added, but linked together. if node.loop != nil { node.loop.Parent = loop } else { loop.AddNode(node.bb) } } lsgraph.AddLoop(loop) } // nodePool.size } // Step c}// External entry point.func FindHavlakLoops(cfgraph *CFG, lsgraph *LSG) int { FindLoops(cfgraph, lsgraph) return lsgraph.NumLoops()}//======================================================// Scaffold Code//======================================================// Basic representation of loops, a loop has an entry point,// one or more exit edges, a set of basic blocks, and potentially// an outer loop - a "parent" loop.//// Furthermore, it can have any set of properties, e.g.,// it can be an irreducible loop, have control flow, be// a candidate for transformations, and what not.//type SimpleLoop struct { // No set, use map to bool basicBlocks map[*BasicBlock]bool Children map[*SimpleLoop]bool Parent *SimpleLoop header *BasicBlock IsRoot bool IsReducible bool Counter int NestingLevel int DepthLevel int}func (loop *SimpleLoop) AddNode(bb *BasicBlock) { loop.basicBlocks[bb] = true}func (loop *SimpleLoop) AddChildLoop(child *SimpleLoop) { loop.Children[child] = true}func (loop *SimpleLoop) Dump(indent int) { for i := 0; i < indent; i++ { fmt.Printf(" ") } // No ? operator ? fmt.Printf("loop-%d nest: %d depth %d ", loop.Counter, loop.NestingLevel, loop.DepthLevel) if !loop.IsReducible { fmt.Printf("(Irreducible) ") } // must have > 0 if len(loop.Children) > 0 { fmt.Printf("Children: ") for ll := range loop.Children { fmt.Printf("loop-%d", ll.Counter) } } if len(loop.basicBlocks) > 0 { fmt.Printf("(") for bb := range loop.basicBlocks { fmt.Printf("BB#%06d ", bb.Name) if loop.header == bb { fmt.Printf("*") } } fmt.Printf("\b)") } fmt.Printf("\n")}func (loop *SimpleLoop) SetParent(parent *SimpleLoop) { loop.Parent = parent loop.Parent.AddChildLoop(loop)}func (loop *SimpleLoop) SetHeader(bb *BasicBlock) { loop.AddNode(bb) loop.header = bb}//------------------------------------// Helper (No templates or such)//func max(x, y int) int { if x > y { return x } return y}// LoopStructureGraph//// Maintain loop structure for a given CFG.//// Two values are maintained for this loop graph, depth, and nesting level.// For example://// loop nesting level depth//----------------------------------------// loop-0 2 0// loop-1 1 1// loop-3 1 1// loop-2 0 2//var loopCounter = 0type LSG struct { root *SimpleLoop loops []*SimpleLoop}func NewLSG() *LSG { lsg := new(LSG) lsg.root = lsg.NewLoop() lsg.root.NestingLevel = 0 return lsg}func (lsg *LSG) NewLoop() *SimpleLoop { loop := new(SimpleLoop) loop.basicBlocks = make(map[*BasicBlock]bool) loop.Children = make(map[*SimpleLoop]bool) loop.Parent = nil loop.header = nil loop.Counter = loopCounter loopCounter++ return loop}func (lsg *LSG) AddLoop(loop *SimpleLoop) { lsg.loops = append(lsg.loops, loop)}func (lsg *LSG) Dump() { lsg.dump(lsg.root, 0)}func (lsg *LSG) dump(loop *SimpleLoop, indent int) { loop.Dump(indent) for ll := range loop.Children { lsg.dump(ll, indent+1) }}func (lsg *LSG) CalculateNestingLevel() { for _, sl := range lsg.loops { if sl.IsRoot { continue } if sl.Parent == nil { sl.SetParent(lsg.root) } } lsg.calculateNestingLevel(lsg.root, 0)}func (lsg *LSG) calculateNestingLevel(loop *SimpleLoop, depth int) { loop.DepthLevel = depth for ll := range loop.Children { lsg.calculateNestingLevel(ll, depth+1) ll.NestingLevel = max(loop.NestingLevel, ll.NestingLevel+1) }}func (lsg *LSG) NumLoops() int { return len(lsg.loops)}func (lsg *LSG) Root() *SimpleLoop { return lsg.root}//======================================================// Testing Code//======================================================func buildDiamond(cfgraph *CFG, start int) int { bb0 := start NewBasicBlockEdge(cfgraph, bb0, bb0+1) NewBasicBlockEdge(cfgraph, bb0, bb0+2) NewBasicBlockEdge(cfgraph, bb0+1, bb0+3) NewBasicBlockEdge(cfgraph, bb0+2, bb0+3) return bb0 + 3}func buildConnect(cfgraph *CFG, start int, end int) { NewBasicBlockEdge(cfgraph, start, end)}func buildStraight(cfgraph *CFG, start int, n int) int { for i := 0; i < n; i++ { buildConnect(cfgraph, start+i, start+i+1) } return start + n}func buildBaseLoop(cfgraph *CFG, from int) int { header := buildStraight(cfgraph, from, 1) diamond1 := buildDiamond(cfgraph, header) d11 := buildStraight(cfgraph, diamond1, 1) diamond2 := buildDiamond(cfgraph, d11) footer := buildStraight(cfgraph, diamond2, 1) buildConnect(cfgraph, diamond2, d11) buildConnect(cfgraph, diamond1, header) buildConnect(cfgraph, footer, from) footer = buildStraight(cfgraph, footer, 1) return footer}var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to this file")func main() { flag.Parse() if *cpuprofile != "" { f, err := os.Create(*cpuprofile) if err != nil { log.Fatal(err) } pprof.StartCPUProfile(f) defer pprof.StopCPUProfile() } lsgraph := NewLSG() cfgraph := NewCFG() cfgraph.CreateNode(0) // top cfgraph.CreateNode(1) // bottom NewBasicBlockEdge(cfgraph, 0, 2) for dummyloop := 0; dummyloop < 15000; dummyloop++ { FindHavlakLoops(cfgraph, NewLSG()) } n := 2 for parlooptrees := 0; parlooptrees < 10; parlooptrees++ { cfgraph.CreateNode(n + 1) buildConnect(cfgraph, 2, n+1) n = n + 1 for i := 0; i < 100; i++ { top := n n = buildStraight(cfgraph, n, 1) for j := 0; j < 25; j++ { n = buildBaseLoop(cfgraph, n) } bottom := buildStraight(cfgraph, n, 1) buildConnect(cfgraph, n, top) n = bottom } buildConnect(cfgraph, n, 1) } FindHavlakLoops(cfgraph, lsgraph) for i := 0; i < 50; i++ { FindHavlakLoops(cfgraph, NewLSG()) } fmt.Printf("# of loops: %d (including 1 artificial root node)\n", lsgraph.NumLoops()) lsgraph.CalculateNestingLevel()}咱们借助macbook零碎上的time命令来打印程序运行的工夫(内核态、用户态、总工夫): ...

December 15, 2022 · 11 min · jiezi

关于go:带读-Go-in-Action中文Go语言实战一

集体感觉《Go in Action》(中文:Go语言实战)相比于《The Go Programming Language》(中文:Go语言圣经)要接地气一些,后者很像苏俄的教材,一板一眼,自底向上 。前者更像美式教材,一上来就给你一个残缺的“搜寻数据”的我的项目,而后自顶向下,耳濡目染中教你想要的常识。(没有孰优孰劣,相比于大黑书 集体更偏爱 in action 系列,要想学扎实当然2本都要看啦) 第一章Go语言的介绍介绍了Go的产生的起因,及特点,这方面的文章帖子太多,这里不再赘述。 第二章如何疾速开始一个Go程序这里给出咱们要学习的第一个程序架构:这一章次要通过对程序的逐行解说,实现了对json格局的指标数据进行搜寻的性能。 上面列出一些集体认为值得关注的点: 对于拜访标识符:和Java的Public/Private 不同 Go应用首字母大小写标识是否能被其余包中的代码间接拜访。 对于初始化:0值的初始化用var,非零值用:= 对于援用传递or值传递golang没有援用传递,全是值传递,就算是指针也是值传递(存的是变量的内存地址罢了) 对于闭包:以上面的代码为例for _, feed := range feeds { // Retrieve a matcher for the search. matcher, exists := matchers[feed.Type] if !exists { matcher = matchers["default"] } // Launch the goroutine to perform the search. go func(matcher Matcher, feed *Feed) { Match(matcher, feed, searchTerm, results) waitGroup.Done() }(matcher, feed) }留神到闭包外面并没有传入'waitGroup',但却胜利拜访到了,并不是因为这个匿名函数拿到了waitGroup的正本,而是通过闭包间接拜访到了外层作用域中waitGroup变量自身。 对于数据转化:var feeds = [] * feederr = json.NewDecoder(file).Decode(&feeds)return feeds, err这种性能代码片段其实能够积攒一些,工作中用的挺多的,当然间接应用unmarshal也可,因为这里Decode就是用unmarshal封装的。 ...

December 15, 2022 · 1 min · jiezi

关于go:Golang都有哪些值得推荐的学习资料

后面几篇文章,给大家总结了一些对于Golang中不错的开源框架、开源库等相干的内容。明天接着给分享一些不错的学习资源内容。同时也会分享一些优质的教学视频、高质量的电子书籍。想获取该文档、视频,能够通过上面的文章链接,进入公众号菜单,分割号主。 你应该晓得的17个Golang包Go开发的linux、数据库、redis、mongo对立治理操作平台应用Go开源的一款性能监控软件应用 Go + HTML + CSS + JS 构建丑陋的跨平台桌面利用初学Go 值得深研的7大开源我的项目Go开发微信小程序SDK举荐视频资源B站最深度的Golang学习到实战 up主强力举荐Go语言外围编程Golang深刻了解GPM模型Golang框架Gin入门实战教程Golang网络编程实战文档资源人人学Go-Go语言学习最全材料地鼠文档-通过收集整理go语言相干的学习文档,为大家提供一个学习平台李文周的博客-总结Go语言学习之路,提供收费的Go语言学习教程面向信奉编程-一个偏差于Go相干的技术博客站点Geekr-由学院君集体保护的技术博客chai2010的博客-Go语言代码的贡献者搭建的技术博客极客兔兔-致力于分享一些技术教程和乏味的技术实际go-zh-Go中文网站小林coding-图解计算机网络、操作系统、计算机组成、数据库相干博客Java 全栈常识体系-涵盖Java全栈技术、微服务、数据库、容器、开发工具等相干技术美团技术博客Golang示例博客-提供收费的Golang编程基础知识、架构系列课程以及Golang高级课程内容。Go官网库中文文档-Go官网包中文文档。优质站点chinaunix-中国最大的Linux/Unix技术社区网站,波及程序开发,数据库,存储备份,服务器技术,网络安全等技术九章算法-波及算法、大数据、人工智能、零碎设计、简历晋升、模仿面试等方面的课程和服务leetcode-专做算法、数据结构、数据库、编程等相干畛域的刷题网站慕课网-波及JAVA、前端、Python、大数据等60类支流技术语言设计模式-多语言设计模式学习函数库go.uuid-uuid生成器lancet-全面、高效、可复用的go语言工具函数库图形处理go-echarts-基于Go开发的charts图形生成库调试工具delve-tracker调试工具零碎服务manba-Api服务网关cron-cron服务组件dtm-分布式事务管理服务cronsun-分布式工作零碎frp-专一于内网穿透的高性能的反向代理利用pyroscope-渐进式服务性能监控组件SDK组件powerwechat-微信领取、微信小程序、微信企业号和微信公众号开发SDKuptrace-反对集群、哨兵、pipeline等性能的Redis客户端robotgo-开源跨平台的GUI工具gocache-多驱动缓存SDK组件开源框架gfast-基于GF(Go Frame)欠缺的后盾管理系统go-admin-基于Gin + Vue + Element UI的前后端拆散开源零碎go-gin-api-基于 Gin 进行模块化设计的 API 框架,封装了罕用性能,应用简略,致力于进行疾速的业务研发GIN-VUE-ADMIN-基于vue和gin开发的全栈前后端拆散的开发根底平台YaoApp-一款反对疾速创立Web服务和治理后盾的开源低代码利用引擎

December 14, 2022 · 1 min · jiezi

关于go:godongle-014-版本发布了一个轻量级语义化的-golang-编码解码加密解密库

dongle 是一个轻量级、语义化、对开发者敌对的 Golang 编码解码和加密解密库 Dongle 已被 awesome-go 收录, 如果您感觉不错,请给个 star 吧 github.com/golang-module/donglegitee.com/golang-module/dongle 更新日志 减少对 tea 加密、解密的反对减少对 bcrypt 签名、验签的反对减少 cipherMode、cipherPadding 和 pkcsVersion 类型别名修复已知 bug将单元测试覆盖率晋升到 100%性能清单 [x] Hex 编码、解码[x] Base16 编码、解码[x] Base32 编码、解码[x] Base58 编码、解码[x] Base62 编码、解码[x] Base64 编码、解码[x] Base64URL 编码、解码[x] SafeURL 编码、解码[x] Base85 编码、解码[x] Base91 编码、解码[x] Base100 编码、解码[x] Md4 加密[x] Hmac-md4 加密[x] Md5 加密[x] Hmac-md5 加密[x] Sha1 加密[x] Hmac-sha1 加密[x] Sha224 加密[x] Hmac-sha224 加密[x] Sha256 加密[x] Hmac-sha256 加密[x] Sha384 加密[x] Hmac-sha384 加密[x] Sha512 加密[x] Hmac-sha512 加密[ ] Rc2 加密[x] Rc4 加密[ ] Rc5 加密[ ] Rc6 加密[x] Tea 加密、解密[ ] Xtea 加密、解密[x] Aes-ECB/CBC/CTR/CFB/OFB-NoPadding/ZeroPadding/PKCS5Padding/PKCS7Padding 加密、解密[x] Dde-ECB/CBC/CTR/CFB/OFB-NoPadding/ZeroPadding/PKCS5Padding/PKCS7Padding 加密、解密[x] 3des-ECB/CBC/CTR/CFB/OFB-NoPadding/ZeroPadding/PKCS5Padding/PKCS7Padding 加密、解密[x] Rsa-PKCS1Pem/PKCS8Pem 加密、解密[ ] Ecc 加密、解密[ ] Sm2 加密、解密[x] Sm3 加密[ ] Sm4 加密、解密[ ] Sm7 加密、解密[ ] Sm9 加密、解密[x] Bcrypt 签名、验签[ ] Rsa 签名、验签[ ] Dsa 签名、验签

November 30, 2022 · 1 min · jiezi

关于go:Goravel-上新用户授权模块让你简单的对非法用户-Say-No

首先,让咱们定义一个规定:用户只能拜访本人创立的文章。 facades.Gate.Define("update-post", func(ctx context.Context, arguments map[string]any) *access.Response { user := ctx.Value("user").(models.User) post := arguments["post"].(models.Post) if user.ID == post.UserID { return access.NewAllowResponse() } else { return access.NewDenyResponse("error") }})而后判断单个权限: if facades.Gate.Allows("update-post", map[string]any{ "post": post,}) { // todo}你也能够同时判断多个权限: if facades.Gate.Any([]string{"update-post", "delete-post"}, map[string]any{ "post": post,}) { // 用户能够提交update或delete...}if facades.Gate.None([]string{"update-post", "delete-post"}, map[string]any{ "post": post,}) { // 用户不能够提交update和delete...}你甚至能够定义 Before 与 After 进行受权前后的拦挡,详见文档。Over, 就是如此简略! 对于 GoravelGoravel 是一个性能齐备、具备良好扩大能力的 Web 应用程序框架。作为一个起始脚手架帮忙 Golang 开发者疾速构建本人的利用。 框架格调与 Laravel 保持一致,让 PHPer 不必学习新的框架,也能够欢快的玩转 Golang!致敬 Laravel! ...

November 29, 2022 · 1 min · jiezi

关于go:从鹅厂实例出发分析Go-Channel底层原理

本文是基于Go1.18.1源码的学习笔记。Channel的底层源码从Go1.14到当初的Go1.19之间简直没有变动,这也是Go最早引入的组件之一,体现了Go并发思维: Do not communicate by sharing memory; instead, share memory by communicating.不要通过共享内存来通信,⽽应通过通信来共享内存。 论断 还是先给出论断,没工夫看剖析过程的同学至多能够看一眼论断: 1. Channel实质上是由三个FIFO(First In FirstOut,先进先出)队列组成的用于协程之间传输数据的协程平安的通道;FIFO的设计是为了保障偏心,让事件变得简略,准则是让等待时间最长的协程最有资格先从channel发送或接收数据; 2. 三个FIFO队列顺次是buf循环队列,sendq待发送者队列,recvq待接收者队列。buf循环队列是大小固定的用来寄存channel接管的数据的队列;sendq待发送者队列,用来寄存期待发送数据到channel的goroutine的双向链表,recvq待接收者队列,用来寄存期待从channel读取数据的goroutine的双向链表;sendq和recvq能够认为不限大小; 3. 跟函数调用传参实质都是传值一样,channel传递数据的实质就是值拷贝,援用类型数据的传递也是地址拷贝;有从缓冲区buf地址拷贝数据到接收者receiver栈内存地址,也有从发送者sender栈内存地址拷贝数据到缓冲区buf; 4. Channel外面参数的批改不是并发平安的,包含对三个队列及其他参数的拜访,因而须要加锁,实质上,channel就是一个有锁队列; 5. Channel 的性能跟 sync.Mutex 差不多,没有谁比谁强。Go官网之所以举荐应用Channel进行并发协程的数据交互,是因为channel的设计理念能让程序变得简略,在大型程序、高并发简单的运行状况中也是如此。 从一个线上的内存透露问题谈起去年底,团队有个线上服务产生了一个故障,该服务部署在K8S集群的容器里,通过Prometheus监控界面看到本服务的Pod的内存使用量呈锯齿状增长,达到服务设置的内存下限16G后,就会产生容器重启,看景象是产生了内存透露。 线上服务的代码通过简化,根本逻辑如下: package mainimport ( "errors" "fmt")func accessMultiService() (data string, err error) { respAChan := make(chan string) //无缓冲channel go func() { serviceAResp, _ := accessServiceA() respAChan <- serviceAResp }() _, serviceBErr := accessServiceB() if serviceBErr != nil { return "", errors.New("service B response error") } _, serviceCErr := accessServiceC() if serviceCErr != nil { return "", errors.New("service C response error") } respA := <- respAChan fmt.Printf("service A resp is: %s\n", respA) return "success", nil}func accessServiceA() (string, error) { return "service A result", nil}func accessServiceB() (string, error) { return "service B result", errors.New("service B error")}func accessServiceC() (string, error) { return "service C result", nil}通过排查,是在起的一个goroutine拜访 A 服务时,应用了一个无缓冲的channel  respAChan,在后续的拜访服务B,C时,产生了异样导致父协程返回,A服务的子协程里的无缓冲channel respAChan始终没有goroutine去读它,导致它始终被阻塞,无奈被开释,随着申请数的增多,它所在的goroutine会始终占用内存,直到达到容器内存下限,使容器解体重启。 ...

November 29, 2022 · 9 min · jiezi

关于go:SegmentFault-思否技术周刊-Vol71-解读-Go-语言

刚刚完结的对于「Go」语言的技术征文活动,参加流动的创作者们,撰写了很多优质技术内容,本期周刊做了汇总,欢送大家一起浏览~ 本篇文章较长,倡议珍藏后缓缓浏览~ 文章举荐【Go 微服务】开发 gRPC 总共分三步 // 王中阳 Go 通过这篇文章咱们曾经把握了 gRPC 相干的知识点,能够独立用 Go 实现客户端和服务端的编写,并且通过服务注册对外提供服务,实现可客户端和服务端的 gRPC 通信。 【Go 微服务】一文带你玩转 ProtoBuf // 王中阳 Go 在网络通信和通用数据交换等利用场景中常常应用的技术是 JSON 或 XML,在微服务架构中通常应用另外一个数据交换的协定的工具 ProtoBuf。ProtoBuf 也是咱们做微服务开发,进行 Go 进阶实战中,必知必会的晓得点。 PHP 转 GO 必看:为什么我感觉 GoFrame 的 garray 比 PHP 的 array 还好用? // 王中阳 Go 在学习 Go 基础知识的时候理解到 Go 的数组和 PHP 的数组并不一样;从肯定水平上讲,Go的 slice 切片类型和 PHP 的数组 array 比拟像(不固定长度、援用类型、动静扩容等),然而在开发应用中远远不像 PHP 的 array 灵便。 通过浏览源码解决我的项目难题:GToken 替换 JWT 实现 SSO 单点登录 // 王中阳 Go 明天和大家分享一下应用 GoFrame 的 gtoken 替换 jwt 实现 sso 登录的教训。期间我也踩了一些坑,最终是通过浏览源码解决了我的项目中遇到的问题。 ...

November 29, 2022 · 2 min · jiezi

关于go:快收藏最全GO语言实现设计模式下

导语| 继上篇【快珍藏!最全GO语言实现设计模式】,本文持续列出GO语言实现的经典设计模式示例,每个示例都精心设计,力求合乎模式构造,可作为日常编码参考,同时一些罕用的设计模式融入了开发实际经验总结,帮忙大家在平时工作中灵活运用。 解释器模式(一)概念解释器模式用于形容如何应用面向对象语言形成一个简略的语言解释器。在某些状况下,为了更好地形容某一些特定类型的问题,咱们能够创立一种新的语言,这种语言领有本人的表达式和构造,即文法规定,这些问题的实例将对应为该语言中的句子。此时,能够应用解释器模式来设计这种新的语言。对解释器模式的学习可能加深咱们对面向对象思维的了解,并且把握编程语言中文法规则的解释过程。 (二)示例定义一个解析特征值的语句解释器,提供是否蕴含特征值的终结表达式,并提供或表达式与且表达式,同时,生成南极洲特色判断表达式,及美国人特色判断表达式,最初测试程序依据对象特征值形容,通过表达式判断是否为真。 特征值解释器package interpreterimport "strings"// Expression 表达式接口,蕴含一个解释办法type Expression interface { Interpret(context string) bool}// terminalExpression 终结符表达式,判断表达式中是否蕴含匹配数据type terminalExpression struct { matchData string}func NewTerminalExpression(matchData string) *terminalExpression { return &terminalExpression{matchData: matchData}}// Interpret 判断是否蕴含匹配字符func (t *terminalExpression) Interpret(context string) bool { if strings.Contains(context, t.matchData) { return true } return false}// orExpression 或表达式type orExpression struct { left, right Expression}func NewOrExpression(left, right Expression) *orExpression { return &orExpression{ left: left, right: right, }}func (o *orExpression) Interpret(context string) bool { return o.left.Interpret(context) || o.right.Interpret(context)}// andExpression 与表达式type andExpression struct { left, right Expression}func NewAndExpression(left, right Expression) *andExpression { return &andExpression{ left: left, right: right, }}func (o *andExpression) Interpret(context string) bool { return o.left.Interpret(context) && o.right.Interpret(context)}测试程序package interpreterimport ( "fmt" "testing")func TestInterpreter(t *testing.T) { isAntarcticaExpression := generateCheckAntarcticaExpression() // 大洲形容1 continentDescription1 := "此大洲生存着大量企鹅,全年高温,并且随同着有暴风雪" fmt.Printf("%s,是否是南极洲?%t\n", continentDescription1, isAntarcticaExpression.Interpret(continentDescription1)) // 大洲形容2 continentDescription2 := "此大洲生存着狮子,全年高温多雨" fmt.Printf("%s,是否是南极洲?%t\n", continentDescription2, isAntarcticaExpression.Interpret(continentDescription2)) isAmericanExpression := generateCheckAmericanExpression() peopleDescription1 := "此人生存在北美洲的黑人,说着英语,持有美国绿卡" fmt.Printf("%s,是否是美国人?%t\n", peopleDescription1, isAmericanExpression.Interpret(peopleDescription1)) peopleDescription2 := "此人生存在欧洲,说着英语,是欧洲议会议员" fmt.Printf("%s,是否是南极洲?%t\n", peopleDescription2, isAmericanExpression.Interpret(peopleDescription2))}// generateCheckAntarcticaExpression 生成校验是否是南极洲表达式func generateCheckAntarcticaExpression() Expression { // 判断南极洲的动物,或关系 animalExpression := NewOrExpression(NewTerminalExpression("企鹅"), NewTerminalExpression("蓝鲸")) // 判断南极洲的天气,与关系 weatherExpression := NewAndExpression(NewTerminalExpression("高温"), NewTerminalExpression("暴风雪")) // 最终返回动物与天气的与关系 return NewAndExpression(animalExpression, weatherExpression)}// generateCheckAmericanExpression 生成查看美国人表达式func generateCheckAmericanExpression() Expression { // 人种判断,或关系 raceExpression := NewOrExpression(NewTerminalExpression("白人"), NewTerminalExpression("黑人")) // 生存形式,与关系 lifeStyleExpression := NewAndExpression(NewTerminalExpression("英语"), NewTerminalExpression("北美洲")) // 身份,与关系 identityExpression := NewAndExpression(lifeStyleExpression, NewTerminalExpression("美国绿卡")) return NewAndExpression(raceExpression, identityExpression)}运行后果=== RUN TestInterpreter此大洲生存着大量企鹅,全年高温,并且随同着有暴风雪,是否是南极洲?true此大洲生存着狮子,全年高温多雨,是否是南极洲?false此人生存在北美洲的黑人,说着英语,持有美国绿卡,是否是美国人?true此人生存在欧洲,说着英语,是欧洲议会议员,是否是美国人?false--- PASS: TestInterpreter (0.00s)PASS ...

November 28, 2022 · 14 min · jiezi

关于go:go语言goredis库使用入门

更不便的在微信公众号阅读文章能够关注公众号:海生的go花园 在go语言中应用redis库,次要有 go-redis以及redigo。咱们这里举荐应用go-redis,次要有两点起因, go-redis反对集群客户端go-redis反对类型平安而且在 github上,go-redis的星数,也是redisgo的近1.5倍。

November 28, 2022 · 1 min · jiezi

关于go:Google重磅发布Go语言编码规范

本文参加了思否技术征文,欢送正在浏览的你也退出。 前言Google官网在2022.11.23重磅公布了Go语言编码标准。 这个编码标准源自于Google外部的Go我的项目,是Google的开发人员要恪守的代码标准。 在Go语言诞生后,全世界的Go开发者其实始终期盼着能有官网的编码标准,但迟迟未能如愿。 有些技术团队依据本人原来的编程语言背景,间接照搬过去,用于公司外部的Go语言编码标准。 尤其是写Java的,把Java的编程语言标准用于Go语言是十分不适合的。 为了让Go开发者能够晓得如何写出更简洁、更纯粹的Go代码,官网也做出了一些致力,推出了Effective Go和Go Code Review Comments。 Go官网的Effective Go: https://go.dev/doc/effective_go。 Note added January, 2022: This document was written for Go's release in 2009, and has not been updated significantly since. Although it is a good guide to understand how to use the language itself, thanks to the stability of the language, it says little about the libraries and nothing about significant changes to the Go ecosystem since it was written, such as the build system, testing, modules, and polymorphism. There are no plans to update it, as so much has happened and a large and growing set of documents, blogs, and books do a fine job of describing modern Go usage. Effective Go continues to be useful, but the reader should understand it is far from a complete guide. See issue 28782 for context.Go官网的Code Review Comments: https://github.com/golang/go/...Effective Go次要解说的是Go语言的语法细节以及一些最佳实际。Code Review Comments蕴含了一些Code Review过程中经常出现的问题。这2个指引能够拿来作为参考,但不足以成为一个十分欠缺齐全的Go语言编码标准。 ...

November 27, 2022 · 1 min · jiezi

关于go:Y-分钟速成-Go

源代码下载: learngo-cn.go 创造Go语言是出于更好地实现工作的须要。Go不是计算机科学的最新倒退潮流,但它却提供了解决事实问题的最新最快的办法。 Go领有命令式语言的动态类型,编译很快,执行也很快,同时退出了对于目前多核CPU的并发计算反对,也有相应的个性来实现大规模编程。 Go语言有十分棒的规范库,还有一个充满热情的社区。 // 单行正文/* 多行 正文 */// 导入包的子句在每个源文件的结尾。// Main比拟非凡,它用来申明可执行文件,而不是一个库。package main// Import语句申明了以后文件援用的包。import ( "fmt" // Go语言规范库中的包 "io/ioutil" // 蕴含一些输入输出函数 m "math" // 数学规范库,在此文件中别名为m "net/http" // 一个web服务器包 "os" // 零碎底层函数,如文件读写 "strconv" // 字符串转换)// 函数申明:main是程序执行的入口。// 不论你喜爱还是不喜爱,反正Go就用了花括号来包住函数体。func main() { // 往规范输入打印一行。 // 用包名fmt限度打印函数。 fmt.Println("你好世界") // 调用以后包的另一个函数。 beyondHello()}// 函数能够在括号里加参数。// 如果没有参数的话,也须要一个空括号。func beyondHello() { var x int // 变量申明,变量必须在应用之前申明。 x = 3 // 变量赋值。 // 能够用:=来偷懒,它主动把变量类型、申明和赋值都搞定了。 y := 4 sum, prod := learnMultiple(x, y) // 返回多个变量的函数 fmt.Println("sum:", sum, "prod:", prod) // 简略输入 learnTypes() // 少于y分钟,学的更多!}/* <- 快看快看我是跨行正文_(:」∠)_Go语言的函数能够有多个参数和 *多个* 返回值。在这个函数中, `x`、`y` 是参数,`sum`、`prod` 是返回值的标识符(能够了解为名字)且类型为int*/func learnMultiple(x, y int) (sum, prod int) { return x + y, x * y // 返回两个值}// 内置变量类型和关键词func learnTypes() { // 短申明给你所想。 str := "少谈话多读书!" // String类型 s2 := `这是一个能够换行的字符串` // 同样是String类型 // 非ascii字符。Go应用UTF-8编码。 g := '' // rune类型,int32的别名,应用UTF-8编码 f := 3.14195 // float64类型,IEEE-754 64位浮点数 c := 3 + 4i // complex128类型,外部应用两个float64示意 // var变量能够间接初始化。 var u uint = 7 // unsigned 无符号变量,然而实现依赖int型变量的长度 var pi float32 = 22. / 7 // 字符转换 n := byte('\n') // byte是uint8的别名 // 数组(Array)类型的大小在编译时即确定 var a4 [4] int // 有4个int变量的数组,初始为0 a3 := [...]int{3, 1, 5} // 有3个int变量的数组,同时进行了初始化 // Array和slice各有千秋,然而slice能够动静的增删,所以更多时候还是应用slice。 s3 := []int{4, 5, 9} // 回去看看 a3 ,是不是这里没有省略号? s4 := make([]int, 4) // 调配4个int大小的内存并初始化为0 var d2 [][]float64 // 这里只是申明,并未分配内存空间 bs := []byte("a slice") // 进行类型转换 // 切片(Slice)的大小是动静的,它的长度能够按需增长 // 用内置函数 append() 向切片开端增加元素 // 要削减到的指标是 append 函数第一个参数, // 少数时候数组在原内存处依次增长,如 s := []int{1, 2, 3} // 这是个长度3的slice s = append(s, 4, 5, 6) // 再加仨元素,长度变为6了 fmt.Println(s) // 更新后的数组是 [1 2 3 4 5 6] // 除了向append()提供一组原子元素(写死在代码里的)以外,咱们 // 还能够用如下办法传递一个slice常量或变量,并在前面加上省略号, // 用以示意咱们将援用一个slice、解包其中的元素并将其增加到s数组开端。 s = append(s, []int{7, 8, 9}...) // 第二个参数是一个slice常量 fmt.Println(s) // 更新后的数组是 [1 2 3 4 5 6 7 8 9] p, q := learnMemory() // 申明p,q为int型变量的指针 fmt.Println(*p, *q) // * 取值 // Map是动静可增长关联数组,和其余语言中的hash或者字典类似。 m := map[string]int{"three": 3, "four": 4} m["one"] = 1 // 在Go语言中未应用的变量在编译的时候会报错,而不是warning。 // 下划线 _ 能够使你“应用”一个变量,然而抛弃它的值。 _, _, _, _, _, _, _, _, _, _ = str, s2, g, f, u, pi, n, a3, s4, bs // 通常的用法是,在调用领有多个返回值的函数时, // 用下划线摈弃其中的一个参数。上面的例子就是一个脏套路, // 调用os.Create并用下划线变量扔掉它的错误代码。 // 因为咱们感觉这个文件肯定会胜利创立。 file, _ := os.Create("output.txt") fmt.Fprint(file, "这句代码还示范了如何写入文件呢") file.Close() // 输入变量 fmt.Println(s, c, a4, s3, d2, m) learnFlowControl() // 回到流程管制}// 和其余编程语言不同的是,go反对有名称的变量返回值。// 申明返回值时带上一个名字容许咱们在函数内的不同地位// 只用写return一个词就能将函数内指定名称的变量返回func learnNamedReturns(x, y int) (z int) { z = x * y return // 隐式返回z,因为后面指定了它。}// Go全面反对垃圾回收。Go有指针,然而不反对指针运算。// 你会因为空指针而犯错,然而不会因为减少指针而犯错。func learnMemory() (p, q *int) { // 返回int型变量指针p和q p = new(int) // 内置函数new分配内存 // 主动将调配的int赋值0,p不再是空的了。 s := make([]int, 20) // 给20个int变量调配一块内存 s[3] = 7 // 赋值 r := -2 // 申明另一个局部变量 return &s[3], &r // & 取地址}func expensiveComputation() int { return 1e6}func learnFlowControl() { // if须要花括号,括号就免了 if true { fmt.Println("这句话必定被执行") } // 用go fmt 命令能够帮你格式化代码,所以不必怕被人吐槽代码格调了, // 也不必容忍他人的代码格调。 if false { // pout } else { // gloat } // 如果太多嵌套的if语句,举荐应用switch x := 1 switch x { case 0: case 1: // 隐式调用break语句,匹配上一个即进行 case 2: // 不会运行 } // 和if一样,for也不必括号 for x := 0; x < 3; x++ { // ++ 自增 fmt.Println("遍历", x) } // x在这里还是1。为什么? // for 是go里惟一的循环关键字,不过它有很多变种 for { // 死循环 break // 骗你的 continue // 不会运行的 } // 用range能够枚举 array、slice、string、map、channel等不同类型 // 对于channel,range返回一个值, // array、slice、string、map等其余类型返回一对儿 for key, value := range map[string]int{"one": 1, "two": 2, "three": 3} { // 打印map中的每一个键值对 fmt.Printf("索引:%s, 值为:%d\n", key, value) } // 如果你只想要值,那就用后面讲的下划线扔掉没用的 for _, name := range []string{"Bob", "Bill", "Joe"} { fmt.Printf("你是。。 %s\n", name) } // 和for一样,if中的:=先给y赋值,而后再和x作比拟。 if y := expensiveComputation(); y > x { x = y } // 闭包函数 xBig := func() bool { return x > 100 // x是下面申明的变量援用 } fmt.Println("xBig:", xBig()) // true (下面把y赋给x了) x /= 1e5 // x变成10 fmt.Println("xBig:", xBig()) // 当初是false // 除此之外,函数体能够在其余函数中定义并调用, // 满足下列条件时,也能够作为参数传递给其余函数: // a) 定义的函数被立刻调用 // b) 函数返回值合乎调用者对类型的要求 fmt.Println("两数相加乘二: ", func(a, b int) int { return (a + b) * 2 }(10, 2)) // Called with args 10 and 2 // => Add + double two numbers: 24 // 当你须要goto的时候,你会爱死它的! goto lovelove: learnFunctionFactory() // 返回函数的函数多棒啊 learnDefer() // 对defer关键字的简略介绍 learnInterfaces() // 好货色来了!}func learnFunctionFactory() { // 空行宰割的两个写法是雷同的,不过第二个写法比拟实用 fmt.Println(sentenceFactory("原谅")("当然抉择", "她!")) d := sentenceFactory("原谅") fmt.Println(d("当然抉择", "她!")) fmt.Println(d("你怎么能够", "她?"))}// Decorator在一些语言中很常见,在go语言中,// 承受参数作为其定义的一部分的函数是修饰符的替代品func sentenceFactory(mystring string) func(before, after string) string { return func(before, after string) string { return fmt.Sprintf("%s %s %s", before, mystring, after) // new string }}func learnDefer() (ok bool) { // defer表达式在函数返回的前一刻执行 defer fmt.Println("defer表达式执行程序为后进先出(LIFO)") defer fmt.Println("\n这句话比上句话先输入,因为") // 对于defer的用法,例如用defer敞开一个文件, // 就能够让敞开操作与关上操作的代码更近一些 return true}// 定义Stringer为一个接口类型,有一个办法Stringtype Stringer interface { String() string}// 定义pair为一个构造体,有x和y两个int型变量。type pair struct { x, y int}// 定义pair类型的办法,实现Stringer接口。func (p pair) String() string { // p被叫做“接收器” // Sprintf是fmt包中的另一个私有函数。 // 用 . 调用p中的元素。 return fmt.Sprintf("(%d, %d)", p.x, p.y)}func learnInterfaces() { // 花括号用来定义构造体变量,:=在这里将一个构造体变量赋值给p。 p := pair{3, 4} fmt.Println(p.String()) // 调用pair类型p的String办法 var i Stringer // 申明i为Stringer接口类型 i = p // 无效!因为p实现了Stringer接口(相似java中的塑型) // 调用i的String办法,输入和下面一样 fmt.Println(i.String()) // fmt包中的Println函数向对象要它们的string输入,实现了String办法就能够这样应用了。 // (相似java中的序列化) fmt.Println(p) // 输入和下面一样,主动调用String函数。 fmt.Println(i) // 输入和下面一样。 learnVariadicParams("great", "learning", "here!")}// 有变长参数列表的函数func learnVariadicParams(myStrings ...interface{}) { // 枚举变长参数列表的每个参数值 // 下划线在这里用来摈弃枚举时返回的数组索引值 for _, param := range myStrings { fmt.Println("param:", param) } // 将可变参数列表作为其余函数的参数列表 fmt.Println("params:", fmt.Sprintln(myStrings...)) learnErrorHandling()}func learnErrorHandling() { // ", ok"用来判断有没有失常工作 m := map[int]string{3: "three", 4: "four"} if x, ok := m[1]; !ok { // ok 为false,因为m中没有1 fmt.Println("别找了真没有") } else { fmt.Print(x) // 如果x在map中的话,x就是那个值喽。 } // 谬误可不只是ok,它还能够给出对于问题的更多细节。 if _, err := strconv.Atoi("non-int"); err != nil { // _ discards value // 输入"strconv.ParseInt: parsing "non-int": invalid syntax" fmt.Println(err) } // 待会再说接口吧。同时, learnConcurrency()}// c是channel类型,一个并发平安的通信对象。func inc(i int, c chan int) { c <- i + 1 // <-把左边的发送到右边的channel。}// 咱们将用inc函数来并发地减少一些数字。func learnConcurrency() { // 用make来申明一个slice,make会调配和初始化slice,map和channel。 c := make(chan int) // 用go关键字开始三个并发的goroutine,如果机器反对的话,还可能是并行执行。 // 三个都被发送到同一个channel。 go inc(0, c) // go is a statement that starts a new goroutine. go inc(10, c) go inc(-805, c) // 从channel中读取后果并打印。 // 打印出什么货色是不可预知的。 fmt.Println(<-c, <-c, <-c) // channel在左边的时候,<-是读操作。 cs := make(chan string) // 操作string的channel cc := make(chan chan string) // 操作channel的channel go func() { c <- 84 }() // 开始一个goroutine来发送一个新的数字 go func() { cs <- "wordy" }() // 发送给cs // Select相似于switch,然而每个case包含一个channel操作。 // 它随机抉择一个筹备好通信的case。 select { case i := <-c: // 从channel接管的值能够赋给其余变量 fmt.Println("这是……", i) case <-cs: // 或者间接抛弃 fmt.Println("这是个字符串!") case <-cc: // 空的,还没作好通信的筹备 fmt.Println("别瞎想") } // 下面c或者cs的值被取到,其中一个goroutine完结,另外一个始终阻塞。 learnWebProgramming() // Go很适宜web编程,我晓得你也想学!}// http包中的一个简略的函数就能够开启web服务器。func learnWebProgramming() { // ListenAndServe第一个参数指定了监听端口,第二个参数是一个接口,特定是http.Handler。 go func() { err := http.ListenAndServe(":8080", pair{}) fmt.Println(err) // 不要忽视谬误。 }() requestServer()}// 使pair实现http.Handler接口的ServeHTTP办法。func (p pair) ServeHTTP(w http.ResponseWriter, r *http.Request) { // 应用http.ResponseWriter返回数据 w.Write([]byte("Y分钟golang速成!"))}func requestServer() { resp, err := http.Get("http://localhost:8080") fmt.Println(err) defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) fmt.Printf("\n服务器音讯: `%s`", string(body))}更进一步对于Go的所有你都能够在Go官方网站找到。 在那里你能够取得教程参考,在线试用,和更多的材料。 在简略的尝试过后,在官网文档那里你会失去你所须要的所有材料、对于编写代码的标准、库和命令行工具的文档与Go的版本历史。 ...

November 27, 2022 · 5 min · jiezi

关于go:三分钟学会go语言的变量定义

本文参加了思否技术征文,欢送正在浏览的你也退出。前言特地阐明: 本文只适宜老手学习这篇文章带咱们入门go语言的定义变量的形式,其实和javascript很类似,所以特意总结在此。 在go语言中,也有变量和常量两种,首先咱们来看变量的定义,定义变量咱们分为定义单个变量和多个变量。 定义单个变量在定义单个变量中,咱们通过var关键字来定义一个变量,其后跟变量名和变量类型,其中变量类型能够省略,语法结构如下: var <变量名> <变量类型>例如: var name string以上代码示意定义一个变量名为name,变量类型为字符串的变量,留神go语言定义变量的类型始终是在最初。 能够看到,和js定义变量区别不大,只不过是多了一个类型申明,其中这个类型申明还能够省略。定义多个变量在go语言中,咱们通过,操作符来定义多个变量,这里定义多个变量也分为两种状况,一种是多个变量都是同一类型,另外一种则是不同类型的多个变量,咱们先来看第一种。 定义雷同类型的多个变量和定义单个变量一样,也是应用var关键字来定义,并且通过,来分隔,语法结构如下: var <变量名1>,<变量名2>,<变量名3>,... <变量类型>如: var name1,name2,name3 string以上定义了name1,name2,name3 3个变量,变量的类型都是string。 能够看到以上是定义雷同类型的多个变量,那么咱们应该如何定义不同类型的多个变量呢? 很简略,用()包裹起来,而后也是应用,分隔,在每个变量名前面紧跟变量类型即可,语法如下: var (<变量名1> <变量1类型>,<变量名2> <变量2类型>...)例如以下代码: var (name string,age int)是不是很简略? 变量的初始化在go语言中,定义了变量,同样也会有初始化的操作,也就是说给变量初始化值,也是通过=操作符后跟值即可。语法结构如下: var <变量名> <变量类型>? = <变量值>例如: var name string = "eveningwater"变量类型的省略其实咱们在初始化变量的时候能够省略变量类型,而后go编译器会在编译的时候帮咱们主动推导变量类型,这几乎就是在写javascript啊,这也是我在以上语法变量类型那一块中标注?的起因,就示意能够写能够不写。例如以上示例代码就能够写成: var name = "eveningwater"如此看来,咱们定义多个变量同样也能够省略变量类型,如: var (name,age) = "eveningwater",26又或者是: var name1,name2 = "eveningwater","xxx"var关键字的省略(简短申明)go语言的定义变量名的关键字var也能够省略,这也是我没有想到的,如下所示: name1,name2 := "eveningwater","xxx"感觉挺神奇的,是吧!go把这种省略了var和type的申明叫做简短申明。这样一来咱们就能够在定义变量的时候用这个":="来定义变量了,不,你想多了,应用这种形式来定义变量是有限度的,那就是这种形式只可能作用在函数外部,如果咱们要定义全局变量的话,还是要老老实实的写var关键字。 全局变量与局部变量通过以上的阐明,咱们晓得了定义全局变量和定义局部变量的形式,如下: var a string = "hello" //全局变量func test() { b := " world" //局部变量 c := a + b fmt.Printf("%s \n", c)}特地的变量名go语言有个很非凡的变量名,那就是下划线"_",为什么说它非凡呢,因为定义它的值都会被抛弃,没错,就是抛弃,例如: ...

November 26, 2022 · 1 min · jiezi

关于go:Golang-GC-从原理到优化

写在后面这篇文章与笔者之前所写几篇不同,是一篇偏综述型的文章,心愿从 GC 的原理、在 Golang 中的利用、以及如何去做优化,这三个角度逐次进行论述,文章中对于一些技术点会援用到多篇文章,心愿读者也都能进行浏览,这有助于更全面的理解 Golang GC。 同时,特地鸣谢 @王德宇 同学对这篇文章的斧正,以及撰写过程中的诸多帮忙与解答。 实践GC 和内存调配形式是强相干的两个技术,因而在剖析两者的设计原理之时,要联合起来一起看。 GC 算法标记-革除标记-整顿标记-复制分代收集对于以上算法的简略介绍 内存调配形式线性调配线性调配(Bump Allocator)是一种高效的内存调配办法,然而有较大的局限性。当咱们应用线性分配器时,只须要在内存中保护一个指向内存特定地位的指针,如果用户程序向分配器申请内存,分配器只须要查看残余的闲暇内存、返回调配的内存区域并批改指针在内存中的地位,即挪动下图中的指针:线性分配器尽管线性分配器实现为它带来了较快的执行速度以及较低的实现复杂度,然而线性分配器无奈在内存被开释时重用内存。如下图所示,如果曾经调配的内存被回收,线性分配器无奈从新利用红色的内存:线性分配器回收内存因为线性分配器具备上述个性,所以须要与适合的垃圾回收算法配合应用,例如:标记压缩(Mark-Compact)、复制回收(Copying GC)和分代回收(Generational GC)等算法,它们能够通过拷贝的形式整顿存活对象的碎片,将闲暇内存定期合并,这样就能利用线性分配器的效率晋升内存分配器的性能了。因为线性分配器须要与具备拷贝个性的垃圾回收算法配合,所以 C 和 C++ 等须要间接对外裸露指针的语言就无奈应用该策略,咱们会在下一节具体介绍常见垃圾回收算法的设计原理。 援用自:https://draveness.me/golang/d... 利用代表:Java(如果应用 Serial, ParNew 等带有 Compact 过程的收集器时,采纳调配的形式为线性调配)问题:内存碎片解决形式:GC 算法中退出「复制/整顿」阶段 闲暇链表调配闲暇链表分配器(Free-List Allocator)能够重用曾经被开释的内存,它在外部会保护一个相似链表的数据结构。当用户程序申请内存时,闲暇链表分配器会顺次遍历闲暇的内存块,找到足够大的内存,而后申请新的资源并批改链表:闲暇链表分配器因为不同的内存块通过指针形成了链表,所以应用这种形式的分配器能够从新利用回收的资源,然而因为分配内存时须要遍历链表,所以它的工夫复杂度是 ()O(n)。闲暇链表分配器能够抉择不同的策略在链表中的内存块中进行抉择,最常见的是以下四种: 首次适应(First-Fit)— 从链表头开始遍历,抉择第一个大小大于申请内存的内存块;循环首次适应(Next-Fit)— 从上次遍历的完结地位开始遍历,抉择第一个大小大于申请内存的内存块;最优适应(Best-Fit)— 从链表头遍历整个链表,抉择最合适的内存块;隔离适应(Segregated-Fit)— 将内存宰割成多个链表,每个链表中的内存块大小雷同,申请内存时先找到满足条件的链表,再从链表中抉择适合的内存块;援用自:https://draveness.me/golang/d... 利用代表:GO、Java(如果应用 CMS 这种基于标记-革除,采纳调配的形式为闲暇链表调配)问题:相比线性调配形式的 bump-pointer 调配操作(top += size),闲暇链表的调配操作过重,例如在 GO 程序的 pprof 图中常常能够看到 mallocgc() 占用了比拟多的 CPU; 在 Golang 里的利用残缺解说:https://time.geekbang.org/col... 内存调配形式Golang 采纳了基于闲暇链表调配形式的 TCMalloc 算法。 对于 TCMalloc官网文档 Front-end:它是一个内存缓存,提供了疾速调配和重分配内存给利用的性能。它次要有2局部组成:Per-thread cache 和 Per-CPU cache。Middle-end:职责是给Front-end提供缓存。也就是说当Front-end缓存内存不够用时,从Middle-end申请内存。它次要是 Central free list 这部分内容。Back-end:这一块是负责从操作系统获取内存,并给Middle-end提供缓存应用。它次要波及 Page Heap 内容。TCMalloc将整个虚拟内存空间划分为n个等同大小的Page。将n个间断的page连贯在一起组成一个Span。PageHeap向OS申请内存,申请的span可能只有一个page,也可能有n个page。ThreadCache内存不够用会向CentralCache申请,CentralCache内存不够用时会向PageHeap申请,PageHeap不够用就会向OS操作系统申请。 ...

November 26, 2022 · 2 min · jiezi

关于go:protocolbuffers-namespace-conflict

在运行grpc服务,加载*.pb.go时可能会报抵触谬误,如文件名命名抵触:其实针对文件名抵触的错误处理开发者有移除过: https://github.com/protocolbu...起初发现有问题又加上了文件冲突检测了:https://github.com/protocolbu...protocol-buffers命名空间抵触的几种状况:留神package和go_package没有必然联系,上面说的package时proto外面的package不是go的引入包file抵触, 注册的时候会查看文件(即source)抵触<pkg name>.<entity name>不能抵触,<pkg name>.<entity name>也不能和<pkg name>抵触。如package叫"A.B", 如果此时另外一个package叫"A",外面有个"B"的message,那么这个message的fullName叫做“A.B”就会和后面的package "A.B"抵触。package的名字能够雷同,比方能够有两个package都叫"A"protocol buffer抵触查看相干的代码:https://github.com/protocolbu... filesByPath次要查看文件抵触, descsByName来查看各类型之间fullName有没有互相抵触如果切实须要更改抵触解决策略,能够运行时指定命令或者配置环境变量: 参考:https://developers.google.com...

November 26, 2022 · 1 min · jiezi

关于go:protobuf-使用其他消息类型message的三种方式

更不便的在微信公众号阅读文章能够关注公众号:海生的go花园 形式一:应用其余音讯类型您能够应用其余音讯类型作为字段类型。例如,假如您想Result在每条SearchResponse音讯中蕴含音讯——为此,您能够在其中定义一个Result音讯类型,.proto而后在 中指定一个类型Result字段SearchResponse message SearchResponse { repeated Result results = 1;}message Result { string url = 1; string title = 2; repeated string snippets = 3;}形式二:导入在下面的例子中,Result音讯类型在同一个文件中定义 SearchResponse——如果你想用作字段类型的音讯类型曾经在另一个.proto文件中定义了怎么办? 您能够通过导入其余.proto文件来应用它们的定义。要导入另一个的定义,您能够在文件顶部增加一个导入语句:.protoimport "myproject/other_protos.proto"; 形式三:嵌套类型 message SearchResponse { message Result { string url = 1; string title = 2; repeated string snippets = 3; } repeated Result results = 1;}如果要在其父音讯类型之外重用此音讯类型,请将其称为_Parent_._Type_: message SomeOtherMessage { SearchResponse.Result result = 1;}

November 25, 2022 · 1 min · jiezi

关于go:万字长文从实践到原理说透Golang-defer

本从以go-1.16版本源码为根底,介绍了defer关键字的应用规定、实现原理和优化路线,最初介绍了几种将近的应用场景。试图对 go defer 关键字利用到实现原理有一个全面的理解。defer 概述Go 提供关键字defer 解决提早调用问题。在语法上,defer与一般的函数调用没有什么区别。正如官网文档形容的那样: A "defer" statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a return statement, reached the end of its function body, or because the corresponding goroutine is panicking. DeferStmt = "defer" Expression .The expression must be a function or method call; it cannot be parenthesized. Calls of built-in functions are restricted as for expression statements.简略了解一下: ...

November 24, 2022 · 19 min · jiezi

关于go:Goravel-上新同时支持-Local-OSS-COS-S3的文件储存模块还支持自定义想存哪里存哪里

对于一些同学相熟的滋味扑面而来,对于另一些同学应用体验相当丝滑。 // 贮存文件err := facades.Storage.Put("file.jpg", contents)// 切换驱动err := facades.Storage.Disk("oss").Put("file.jpg", contents)// 检索文件contents := facades.Storage.Get("file.jpg")// 判断文件是否存在exists := facades.Storage.Disk("s3").Exists("file.jpg")// 文件地址url := facades.Storage.Url("file.jpg")// 长期地址url, err := facades.Storage.TemporaryUrl("file.jpg", time.Now().Add(5*time.Minute))// 文件门路path := facades.Storage.Path("file.jpg")// 复制 / 挪动文件err := facades.Storage.Copy("old/file.jpg", "new/file.jpg")err := facades.Storage.Move("old/file.jpg", "new/file.jpg")// 删除文件err := facades.Storage.Delete("file.jpg")err := facades.Storage.Delete("file.jpg", "file2.jpg")// 创立一个目录err := facades.Storage.MakeDirectory(directory)// 删除一个目录err := facades.Storage.DeleteDirectory(directory)对于 GoravelGoravel 是一个性能齐备、具备良好扩大能力的 Web 应用程序框架。作为一个起始脚手架帮忙 Golang 开发者疾速构建本人的利用。 框架格调与 Laravel 保持一致,让 PHPer 不必学习新的框架,也能够欢快的玩转 Golang!致敬 Laravel! Welcome star, PR and issues!

November 24, 2022 · 1 min · jiezi

关于go:Go-泛型

在最新的go1.18版中减少了期盼已久的泛型反对 什么是泛型泛型是程序设计语言的一种格调或范式。泛型容许程序员在强类型程序设计语言中编写代码时应用一些当前才指定的类型,在实例化时作为参数指明这些类型。 为什么应用泛型如果没有泛型,对于golang语言这种强类型语言,针对不同类型的函数解决形式: 每个类型别离实现一遍,代码简短,浏览性差。通过interface{},须要反射类型判断及类型强转,这容易裸露谬误。以int64和int类型为例: func CompareInt64(a, b int64) bool { if a >= b { return true } return false}func CompareInt(a, b int) bool { if a >= b { return true } return false}func Compare(a, b interface{}) bool { switch a.(type) { case int64: a1 := a.(int64) b1 := b.(int64) if a1 >= b1 { return true } return false case int: a1 := a.(int) b1 := b.(int) if a1 >= b1 { return true } return false default: return false }}应用泛型golang反对泛型函数和泛型类型 ...

November 24, 2022 · 3 min · jiezi

关于go:Gin-GORM-入门到实战

本文参加了思否技术征文,欢送正在浏览的你也退出。 Hello,大家好,我是海军,最近始终在学习Go,目前在做我的项目相熟Go 阶段。 本文来分享一下 Gin + GORM 的一些 开发体验,有喜爱Go 方向的敌人,欢送一起交流学习呀!后续会更新实战我的项目,目前在实现一个 技术论坛我的项目,结尾有效果图,前端局部实现了,当初在欠缺后端接口和前端联调的过程,没多久就会公布了。 后续,会再写一篇我的项目文章。 干就完了 导读目录Gin Gin 学习路线Gin 入门装置Gin 中间件应用Gin 获取申请参数Gin 获取JSON数据Gin CookieGin SessionGin 上传文件GORM 什么是GORM如何建ModelGin学习路线 入门装置装置Gin下载依赖$ go get -u github.com/gin-gonic/gin将 gin 引入到代码中:import "github.com/gin-gonic/gin"(可选)如果应用诸如 http.StatusOK 之类的常量,则须要引入 net/http 包:import "net/http"demo package mainimport ( "fmt" "github.com/gin-gonic/gin")func main() { fmt.Println("Gin 入门学习") // 创立一个默认的路由引擎 r := gin.Default() // GET:申请形式;/hello:申请的门路 // 当客户端以GET办法申请/hello门路时,会执行前面的匿名函数 r.GET("/hello", func(c *gin.Context) { // c.JSON:返回JSON格局的数据 c.JSON(200, gin.H{ "message": "Hello world!", }) }) // 启动HTTP服务,默认在0.0.0.0:8080启动服务 r.Run(":8098")}Gin 中间件应用目录什么是 中间件?中间件作用域中间件数据共享中间件留神什么是中间件?中间件是在当客户端拜访服务端接口之前和之后会做一些事件。 作用: 登陆认证, 权限校验 , 记录日志, 耗时统计 ..... ...

November 23, 2022 · 5 min · jiezi

关于go:PGO-是啥咋就让-Go-更快更猛了

大家好,我是煎鱼。 Go1.20 行将公布,近期很多大佬提到一个关键词 PGO,说是有很大的进步,很猛...让我一愣一愣,不禁思考是什么? 明天就由煎鱼和大家一起学习。 疾速理解PGO 是什么Profile-guided optimization (PGO),翻译过去是应用配置文件疏导的优化。也被称为: profile-directed feedback(PDF)feedback-directed optimization(FDO)PGO 是计算机编程中的一种编译器优化技术,应用剖析来进步程序运行时性能。也就是能够进步 Go 运行时的性能。 该项优化是一个通用技术,不局限于某一门语言。像是: 罕用的 Chrome 浏览器,在 64 位版本的 Chrome 中从 53 版开始启用 PGO, 32 位版在 54 版中启用。Microsoft Visual C++ 也同样有所应用。AutoFDO 进行了 PGO 的优化,间接将某数据中心中的 C/C++ 程序的性能进步了 5-15%(不必改业务代码)。这个优化问题,一听就很振奋人心。 PGO 怎么优化《Intel Developer Guide and Reference》 中对 PGO 的优化和流程有一个根本介绍,如下内容,分享给大家。 PGO 通过放大代码大小、缩小分支谬误预测和从新组织代码布局以缩小指令缓存问题来进步应用程序性能。并向编译器提供无关应用程序中最常执行的区域的信息。通过理解这些畛域,编译器可能在优化应用程序时更具选择性和针对性。 PGO 由三个阶段组成。如下图: 检测程序。编译器从您的源代码和编译器的非凡代码创立并链接一个检测程序。运行检测的可执行文件。每次执行插桩代码时,插桩程序都会生成一个动静信息文件,用于最终编译。最终编译。当您第二次编译时,动静信息文件将合并到一个摘要文件中。应用此文件中的概要信息摘要,编译器尝试优化程序中最频繁的运行门路去执行。这就是 PGO 这项优化的根本过程了。 新提案背景提案作者(Cherry Mui、Austin Clements、Michael Pratt)倡议向 Go GC 工具链减少对配置文件疏导优化 (PGO) 的反对,能够使得工具链能依据运行时信息执行特定于应用程序和工作负载的优化。 阐明了就是想进步性能,不改业务代码。 用什么来做PGO 须要用户参加来收集配置文件并将其反馈到构建过程中能力优化。这是一个大问题。 最合乎这个要求的,就是 pprof。最终敲定Go 团队将基于 runtime/pprof 来失去所需 profile,以此来实现 PGO。因为它合乎:采集样本开销低、多零碎兼容性强、Go 规范且被宽泛应用的基准。 ...

November 22, 2022 · 2 min · jiezi

关于go:快收藏最全GO语言实现设计模式

导语| 设计模式是针对软件设计中常见问题的工具箱,其中的工具就是各种通过实际验证的解决方案。即便你从未遇到过这些问题,理解模式依然十分有用,因为它能领导你如何应用面向对象的设计准则来解决各种问题,进步开发效率,升高开发成本;本文囊括了GO语言实现的经典设计模式示例,每个示例都精心设计,力求合乎模式构造,可作为日常编码参考,同时一些罕用的设计模式融入了开发实际经验总结,帮忙大家在平时工作中灵活运用。 责任链模式 (一)概念责任链模式是一种行为设计模式, 容许你将申请沿着解决者链进行发送。收到申请后,每个解决者均可对申请进行解决,或将其传递给链上的下个解决者。 该模式容许多个对象来对申请进行解决,而无需让发送者类与具体接收者类相耦合。链可在运行时由遵循规范解决者接口的任意解决者动静生成。 个别意义上的责任链模式是说,申请在链上流转时任何一个满足条件的节点解决完申请后就会进行流转并返回,不过还能够依据不同的业务状况做一些改良: 申请能够流经解决链的所有节点,不同节点会对申请做不同职责的解决;能够通过上下文参数保留申请对象及上游节点的处理结果,供上游节点依赖,并进一步解决;解决链可反对节点的异步解决,通过实现特定接口判断,是否须要异步解决;责任链对于申请解决节点能够设置进行标记位,不是异样,是一种满足业务流转的中断;责任链的拼接形式存在两种,一种是节点遍历,一个节点一个节点程序执行;另一种是节点嵌套,内层节点嵌入在外层节点执行逻辑中,相似递归,或者“回”行构造;责任链的节点嵌套拼接形式多被称为拦截器链或者过滤器链,更易于实现业务流程的切面,比方监控业务执行时长,日志输入,权限校验等;(二)示例本示例模仿实现机场登机过程,第一步办理登机牌,第二步如果有行李,就办理托运,第三步核实身份,第四步安全检查,第五步实现登机;其中行李托运是可选的,其余步骤必选,必选步骤有任何不满足就终止登机;旅客对象作为申请参数上下文,每个步骤会依据旅客对象状态判断是否解决或流转下一个节点; (三)登机过程package chainofresponsibilityimport "fmt"// BoardingProcessor 登机过程中,各节点对立解决接口type BoardingProcessor interface { SetNextProcessor(processor BoardingProcessor) ProcessFor(passenger *Passenger)}// Passenger 旅客type Passenger struct { name string // 姓名 hasBoardingPass bool // 是否办理登机牌 hasLuggage bool // 是否有行李须要托运 isPassIdentityCheck bool // 是否通过身份校验 isPassSecurityCheck bool // 是否通过安检 isCompleteForBoarding bool // 是否实现登机}// baseBoardingProcessor 登机流程处理器基类type baseBoardingProcessor struct { // nextProcessor 下一个登机解决流程 nextProcessor BoardingProcessor}// SetNextProcessor 基类中对立实现设置下一个处理器办法func (b *baseBoardingProcessor) SetNextProcessor(processor BoardingProcessor) { b.nextProcessor = processor}// ProcessFor 基类中对立实现下一个处理器流转func (b *baseBoardingProcessor) ProcessFor(passenger *Passenger) { if b.nextProcessor != nil { b.nextProcessor.ProcessFor(passenger) }}// boardingPassProcessor 办理登机牌处理器type boardingPassProcessor struct { baseBoardingProcessor // 援用基类}func (b *boardingPassProcessor) ProcessFor(passenger *Passenger) { if !passenger.hasBoardingPass { fmt.Printf("为旅客%s办理登机牌;\n", passenger.name) passenger.hasBoardingPass = true } // 胜利办理登机牌后,进入下一个流程解决 b.baseBoardingProcessor.ProcessFor(passenger)}// luggageCheckInProcessor 托运行李处理器type luggageCheckInProcessor struct { baseBoardingProcessor}func (l *luggageCheckInProcessor) ProcessFor(passenger *Passenger) { if !passenger.hasBoardingPass { fmt.Printf("旅客%s未办理登机牌,不能托运行李;\n", passenger.name) return } if passenger.hasLuggage { fmt.Printf("为旅客%s办理行李托运;\n", passenger.name) } l.baseBoardingProcessor.ProcessFor(passenger)}// identityCheckProcessor 校验身份处理器type identityCheckProcessor struct { baseBoardingProcessor}func (i *identityCheckProcessor) ProcessFor(passenger *Passenger) { if !passenger.hasBoardingPass { fmt.Printf("旅客%s未办理登机牌,不能办理身份校验;\n", passenger.name) return } if !passenger.isPassIdentityCheck { fmt.Printf("为旅客%s核实身份信息;\n", passenger.name) passenger.isPassIdentityCheck = true } i.baseBoardingProcessor.ProcessFor(passenger)}// securityCheckProcessor 安检处理器type securityCheckProcessor struct { baseBoardingProcessor}func (s *securityCheckProcessor) ProcessFor(passenger *Passenger) { if !passenger.hasBoardingPass { fmt.Printf("旅客%s未办理登机牌,不能进行安检;\n", passenger.name) return } if !passenger.isPassSecurityCheck { fmt.Printf("为旅客%s进行安检;\n", passenger.name) passenger.isPassSecurityCheck = true } s.baseBoardingProcessor.ProcessFor(passenger)}// completeBoardingProcessor 实现登机处理器type completeBoardingProcessor struct { baseBoardingProcessor}func (c *completeBoardingProcessor) ProcessFor(passenger *Passenger) { if !passenger.hasBoardingPass || !passenger.isPassIdentityCheck || !passenger.isPassSecurityCheck { fmt.Printf("旅客%s登机查看过程未实现,不能登机;\n", passenger.name) return } passenger.isCompleteForBoarding = true fmt.Printf("旅客%s胜利登机;\n", passenger.name)}(四)测试程序package chainofresponsibilityimport "testing"func TestChainOfResponsibility(t *testing.T) { boardingProcessor := BuildBoardingProcessorChain() passenger := &Passenger{ name: "李四", hasBoardingPass: false, hasLuggage: true, isPassIdentityCheck: false, isPassSecurityCheck: false, isCompleteForBoarding: false, } boardingProcessor.ProcessFor(passenger)}// BuildBoardingProcessorChain 构建登机流程解决链func BuildBoardingProcessorChain() BoardingProcessor { completeBoardingNode := &completeBoardingProcessor{} securityCheckNode := &securityCheckProcessor{} securityCheckNode.SetNextProcessor(completeBoardingNode) identityCheckNode := &identityCheckProcessor{} identityCheckNode.SetNextProcessor(securityCheckNode) luggageCheckInNode := &luggageCheckInProcessor{} luggageCheckInNode.SetNextProcessor(identityCheckNode) boardingPassNode := &boardingPassProcessor{} boardingPassNode.SetNextProcessor(luggageCheckInNode) return boardingPassNode}(五)运行后果=== RUN TestChainOfResponsibility为旅客李四办理登机牌;为旅客李四办理行李托运;为旅客李四核实身份信息;为旅客李四进行安检;旅客李四胜利登机;--- PASS: TestChainOfResponsibility (0.00s)PASS ...

November 22, 2022 · 13 min · jiezi

关于go:Go常见错误第15篇interface使用的常见错误和最佳实践

本文参加了思否技术征文,欢送正在浏览的你也退出。 前言这是Go常见谬误系列的第15篇:interface应用的常见谬误和最佳实际。 素材来源于Go布道者,现Docker公司资深工程师Teiva Harsanyi。 本文波及的源代码全副开源在:Go常见谬误源代码,欢送大家关注公众号,及时获取本系列最新更新。 常见谬误和最佳实际interface是Go语言里的外围性能,然而在日常开发中,常常会呈现interface被乱用的状况,代码适度形象,或者形象不合理,导致代码艰涩难懂。 本文先带大家回顾下interface的重要概念,而后解说应用interface的常见谬误和最佳实际。 interface重要概念回顾interface外面蕴含了若干个办法,大家能够了解为一个interface代表了一类群体的独特行为。 构造体要实现interface不须要相似implement的关键字,只有该构造体实现了interface里的所有办法即可。 咱们拿Go语言里的io规范库来阐明interface的弱小之处。io规范库蕴含了2个interface: io.Reader:示意从某个数据源读数据io.Writer:示意写数据到指标地位,比方写到指定文件或者数据库Figure 2.3 io.Reader reads from a data source and fills a byte slice, whereas io.Writer writes to a target from a byte slice. io.Reader这个interface里只有一个Read办法: type Reader interface { Read(p []byte) (n int, err error)}Read reads up to len(p) bytes into p. It returns the number of bytes read (0 <= n <= len(p)) and any error encountered. Even if Read returns n < len(p), it may use all of p as scratch space during the call. If some data is available but not len(p) bytes, Read conventionally returns what is available instead of waiting for more.如果某个构造体要实现io.Reader,须要实现Read办法。这个办法要蕴含以下逻辑: ...

November 21, 2022 · 2 min · jiezi

关于go:聊聊Go里面的闭包

以前写 Java 的时候,听到前端同学议论闭包,感觉甚是离奇,前面本人写了一小段时间 JS,虽只学到皮毛,也大略理解到闭包的概念,当初工作罕用语言是 Go,很多优雅的代码中总是有闭包的身影,看来不理解个透是不可能的了,本文让我来科普(依照本人程度轻易瞎扯)一下: 1、什么是闭包?在真正讲述闭包之前,咱们先铺垫一点知识点: 函数式编程函数作用域作用域的继承关系 ## 1.1 前提常识铺垫 1.2.1 函数式编程函数式编程是一种编程范式,对待问题的一种形式,每一个函数都是为了用小函数组织成为更大的函数,函数的参数也是函数,函数返回的也是函数。咱们常见的编程范式有: 命令式编程: 次要思维为:关注计算机执行的步骤,也就是一步一步通知计算机先做什么再做什么。先把解决问题步骤规范化,形象为某种算法,而后编写具体的算法去实现,个别只有反对过程化编程范式的语言,咱们都能够称为过程化编程语言,比方 BASIC,C 等。申明式编程: 次要思维为:通知计算机应该做什么,然而不指定具体要怎么做,比方 SQL,网页编程的 HTML,CSS。函数式编程: 只关注做什么而不关注怎么做,有一丝丝申明式编程的影子,然而更加侧重于”函数是第一位“的准则,也就是函数能够呈现在任何中央,参数、变量、返回值等等。函数式编程能够认为是面向对象编程的对立面,个别只有一些编程语言会强调一种特定的编程形式,大多数的语言都是多范式语言,能够反对多种不同的编程形式,比方 JavaScript ,Go 等。 函数式编程是一种思维形式,将电脑运算视为函数的计算,是一种写代码的方法论,其实我应该聊函数式编程,而后再聊到闭包,因为闭包自身就是函数式编程外面的一个特点之一。 在函数式编程中,函数是头等对象,意思是说一个函数,既能够作为其它函数的输出参数值,也能够从函数中返回值,被批改或者被调配给一个变量。(维基百科)个别纯函数编程语言是不容许间接应用程序状态以及可变对象的,函数式编程自身就是要防止应用 共享状态,可变状态,尽可能防止产生 副作用。 函数式编程个别具备以下特点: 函数是第一等公民:函数的位置放在第一位,能够作为参数,能够赋值,能够传递,能够当做返回值。没有副作用:函数要放弃纯正独立,不能批改内部变量的值,不批改内部状态。援用通明:函数运行不依赖内部变量或者状态,雷同的输出参数,任何状况,所失去的返回值都应该是一样的。1.2.2 函数作用域作用域(scope),程序设计概念,通常来说,一段程序代码中所用到的名字并不总是无效/可用的,而限定这个名字的可用性的代码范畴就是这个名字的作用域。 通俗易懂的说,函数作用域是指函数能够起作用的范畴。函数有点像盒子,一层套一层,作用域咱们能够了解为是个关闭的盒子,也就是函数的局部变量,只能在盒子外部应用,成为独立作用域。 函数内的局部变量,出了函数就跳出了作用域,找不到该变量。(里层函数能够应用外层函数的局部变量,因为外层函数的作用域包含了里层函数),比方上面的 innerTmep 出了函数作用域就找不到该变量,然而 outerTemp 在内层函数外面还是能够应用。 不论是任何语言,根本存在肯定的内存回收机制,也就是回收用不到的内存空间,回收的机制个别和下面说的函数的作用域是相干的,局部变量出了其作用域,就有可能被回收,如果还被援用着,那么就不会被回收。 1.2.3 作用域的继承关系所谓作用域继承,就是后面说的小盒子能够继承外层大盒子的作用域,在小盒子能够间接取出大盒子的货色,然而大盒子不能取出小盒子的货色,除非产生了逃逸(逃逸能够了解为小盒子的货色给出了援用,大盒子拿到就能够应用)。一般而言,变量的作用域有以下两种: 全局作用域:作用于任何中央部分作用域:个别是代码块,函数、包内,函数外部申明/定义的变量叫局部变量,作用域仅限于函数外部1.2 闭包的定义“少数状况下咱们并不是先了解后定义,而是先定义后了解“,先下定义,读不懂没关系: 闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的援用的组合。 换而言之,闭包让开发者能够从外部函数拜访内部函数的作用域。 闭包会随着函数的创立而被同时创立。一句话表述: $$闭包 = 函数 + 援用环境$$ 以上定义找不到 Go语言 这几个字眼,聪慧的同学必定晓得,闭包是和语言无关的,不是 JavaScript 特有的,也不是 Go 特有的,而是函数式编程语言的特有的,是的,你没有看错,任何反对函数式编程的语言都反对闭包,Go 和 JavaScript 就是其中之二, 目前 Java 目前版本也是反对闭包的,然而有些人可能认为不是完满的闭包,详细情况文中探讨。 1.3 闭包的写法1.3.1 初看闭包上面是一段闭包的代码: import "fmt"func main() { sumFunc := lazySum([]int{1, 2, 3, 4, 5}) fmt.Println("期待一会") fmt.Println("后果:", sumFunc())}func lazySum(arr []int) func() int { fmt.Println("先获取函数,不求后果") var sum = func() int { fmt.Println("求后果...") result := 0 for _, v := range arr { result = result + v } return result } return sum}输入的后果: ...

November 21, 2022 · 7 min · jiezi

关于go:通过阅读源码解决项目难题GToken替换JWT实现SSO单点登录

明天和大家分享一下应用GoFrame的gtoken替换jwt实现sso登录的教训。期间我也踩了一些坑,最终是通过浏览源码解决了我的项目中遇到的问题。感觉这个经验比拟有意思,整顿一篇文章分享给大家。 jwt的问题首先阐明一个jwt存在的问题,也就是要替换jwt的起因: jwt无奈在服务端被动退出的问题jwt无奈作废已颁布的令牌,只能等到令牌过期问题jwt携带大量用户扩大信息导致升高传输效率问题jwt的申请流程图 gtoken的劣势gtoken的申请流程和jwt的基本一致。 gtoken的劣势就是能帮忙咱们解决jwt的问题,另外还提供好用的个性,比方: gtoken反对单点利用应用内存存储,反对集体我的项目文件存储,也反对企业集群应用redis存储,齐全实用于集体和企业生产级应用;无效的防止了jwt服务端无奈退出问题;解决jwt无奈作废已颁布的令牌,只能等到令牌过期问题;通过用户扩大信息存储在服务端,无效躲避了jwt携带大量用户扩大信息导致升高传输效率问题;无效防止jwt须要客户端实现续签性能,减少客户端复杂度;反对服务端主动续期,客户端不须要关怀续签逻辑;留神问题反对服务端缓存主动续期性能,不须要通过refresh_token刷新token,简化了客户端的操作版本问题千万留神:在gtoken v1.5.0全面适配GoFrame v2.0.0 ; GoFrame v1.X.X 请应用GfToken v1.4.X相干版本TIPS:上面我的演示demo和源码浏览都是基于v1.4.x版本的。 前面会更新视频教程,带大家实现最新版goframe和gtoken的教程,有须要的小伙伴能够关注我一下。 演示demo上面的演示demo能够复制到本地main.go文件中执行,更新依赖的时候千万留神版本。 重点说一下踩的坑,Login办法会要求咱们返回两个值: 第一个值对应userKey,后续咱们能够依据userKey取得token第二个值对应data,是interface{}类型,咱们能够在这里定义例如userid、username等数据。先有这个概念即可,为了让大家更好的了解,文章最初会带大家读源码。 入门示例代码段的要害逻辑,曾经增加了正文。 package mainimport ( "github.com/goflyfox/gtoken/gtoken" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/net/ghttp" "github.com/gogf/gf/os/glog")var TestServerName string//var TestServerName string = "gtoken"func main() { glog.Info("########service start...") g.Cfg().SetPath("example/sample") s := g.Server(TestServerName) initRouter(s) glog.Info("########service finish.") s.Run()}var gfToken *gtoken.GfToken/*对立路由注册*/func initRouter(s *ghttp.Server) { // 不认证接口 s.Group("/", func(group *ghttp.RouterGroup) { group.Middleware(CORS) // 调试路由 group.ALL("/hello", func(r *ghttp.Request) { r.Response.WriteJson(gtoken.Succ("hello")) }) }) // 认证接口 loginFunc := Login // 启动gtoken gfToken := &gtoken.GfToken{ ServerName: TestServerName, LoginPath: "/login", LoginBeforeFunc: loginFunc, LogoutPath: "/user/logout", AuthExcludePaths: g.SliceStr{"/user/info", "/system/user/info"}, // 不拦挡门路 /user/info,/system/user/info,/system/user, MultiLogin: g.Config().GetBool("gToken.MultiLogin"), } s.Group("/", func(group *ghttp.RouterGroup) { group.Middleware(CORS) gfToken.Middleware(group) group.ALL("/system/user", func(r *ghttp.Request) { r.Response.WriteJson(gtoken.Succ("system user")) }) group.ALL("/user/data", func(r *ghttp.Request) { r.Response.WriteJson(gfToken.GetTokenData(r)) }) group.ALL("/user/info", func(r *ghttp.Request) { r.Response.WriteJson(gtoken.Succ("user info")) }) group.ALL("/system/user/info", func(r *ghttp.Request) { r.Response.WriteJson(gtoken.Succ("system user info")) }) }) // 启动gtoken gfAdminToken := &gtoken.GfToken{ ServerName: TestServerName, //Timeout: 10 * 1000, LoginPath: "/login", LoginBeforeFunc: loginFunc, LogoutPath: "/user/logout", AuthExcludePaths: g.SliceStr{"/admin/user/info", "/admin/system/user/info"}, // 不拦挡门路 /user/info,/system/user/info,/system/user, MultiLogin: g.Config().GetBool("gToken.MultiLogin"), } s.Group("/admin", func(group *ghttp.RouterGroup) { group.Middleware(CORS) gfAdminToken.Middleware(group) group.ALL("/system/user", func(r *ghttp.Request) { r.Response.WriteJson(gtoken.Succ("system user")) }) group.ALL("/user/info", func(r *ghttp.Request) { r.Response.WriteJson(gtoken.Succ("user info")) }) group.ALL("/system/user/info", func(r *ghttp.Request) { r.Response.WriteJson(gtoken.Succ("system user info")) }) })}func Login(r *ghttp.Request) (string, interface{}) { username := r.GetString("username") passwd := r.GetString("passwd") if username == "" || passwd == "" { r.Response.WriteJson(gtoken.Fail("账号或明码谬误.")) r.ExitAll() } return username, "1" /** 返回的第一个参数对应:userKey 返回的第二个参数对应:data { "code": 0, "msg": "success", "data": { "createTime": 1652838582190, "data": "1", "refreshTime": 1653270582190, "userKey": "王中阳", "uuid": "ac75676efeb906f9959cf35f779a1d38" } } */}// 跨域func CORS(r *ghttp.Request) { r.Response.CORSDefault() r.Middleware.Next()}运行成果启动我的项目: ...

November 18, 2022 · 2 min · jiezi