古人学问无遗力,少壮工夫老始成。纸上得来终觉浅,绝知此事要躬行。 —— 宋.陆游 《冬夜读书示子聿》
学习资料
The Go Programming Language
Go语言中文网
菜鸟教程
GitHub-Chinese-Top-Charts
Go’s Declaration Syntax
雪花算法
Go核心
Go语言之旅
剑指Offer-Golang实现
Golang面试题搜集
go-interview
超全golang面试题合集
为什么Golang中不用this和self
新手可能会踩的50个坑
Go语言Mac下环境搭建 一、安装
通过安装包
下载 安装文件
一路双击安装就行了
配置环境变量vim ~/.bash_profile
1 2 3 4 #注意不要和GOROOT相同,否则会报warning: GOPATH set to GOROOT (/usr/local/go) has no effect export GOPATH=~/go #GOPATH是自定义的工作空间,它是日常开发的根目录。 export GOBIN=/usr/local/go/bin export PATH=$PATH:$GOBIN
通过Homebrew安装
相关命令
查看版本go version
查看环境变量go env
二、测试
vim main.go
写入以下脚本
1 2 3 4 5 6 7 package main import("fmt") func main(){ fmt.Println("It works!") }
生成可执行文件 go build main.go
运行可执行文件 ./main
三、vs code开发配置
vs code开发时自动导入包
command+shift+P
go:install/update tools
将所有16个插件都勾选上安装
vs code安装tools超时失败
自动下载:go env -w GO111MODULE=on
设置代理:go env -w GOPROXY=https://goproxy.cn,direct
Go语言env说明 一、概念
go env命令用于打印Go语言的环境变量信息,也可进行设置环境变量信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 GO111MODULE="" GOARCH="amd64" GOBIN="" GOCACHE="***" GOENV="***" GOEXE="" GOFLAGS="" GOHOSTARCH="amd64" GOHOSTOS="darwin" GONOPROXY="" GONOSUMDB="" GOOS="darwin" GOPATH="***" GOPRIVATE="" GOPROXY="https://proxy.golang.org,direct" GOROOT="/usr/local/go" GOSUMDB="sum.golang.org" GOTMPDIR="" GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64" GCCGO="gccgo" AR="ar" CC="clang" CXX="clang++" CGO_ENABLED="1" GOMOD="" CGO_CFLAGS="-g -O2" CGO_CPPFLAGS="" CGO_CXXFLAGS="-g -O2" CGO_FFLAGS="-g -O2" CGO_LDFLAGS="-g -O2" PKG_CONFIG="pkg-config" GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/14/kx973qc93bn0kpgymsb01xbh0000gn/T/go-build470845532=/tmp/go-build -gno-record-gcc-switches -fno-common"
二、常用
参数名
含义
GCCGO
构建时时候所用编译器
GOARCH
计算机处理器的架构(常见如amd64、arm等)
GOCACHE
存储编译后信息的缓存目录
GOFLAGS
go命令能够识别的标记(可以是多个,中间用空格隔开)
GOOS
编译代码的操作系统名称(如linux、windows、darwin等),用于交叉编译
GOPROXY
go module目录所在的地址
GOROOT
Go语言的安装目录的路径
GOPATH
工作区所在的路径(可存在多个工作区),用于存放源代码、归档和可执行文件
GOBIN
存放可执行文件的路径
GOTOOLDIR
Go工具目录的绝对路径
CGO_ENABLED
指明cgo工具是否可用的标识
GOEXE
可执行文件的后缀
GOHOSTARCH
程序运行环境的目标计算架构
GOHOSTOS
程序运行环境的目标操作系统
三、参考
参考一
参考二
Go语言package和import 一、概念 Go语言使用包package
来管理定义模块,使用import
导入模块。
基本原则
同一个目录下的所有源码文件的代码包声明语句要相同,如果包含命令源码文件,则其他源码文件包声明都应为main
代码包的名称与目录名称可以不一致,代码里引用时需要使用代码包名称,build时使用的是目录名称
源码文件所在的目录相对于src目录的相对路径就是导入路径,而实际使用时给定的限定符要与它声明代码包名称对应
二、使用
命名重复的包名
目录结构($GOPATH/src/pack),tree
1 2 3 4 5 6 7 8 9 . ├── go.mod ├── main.go ├── p1 │ ├── demo1.go │ └── demo2.go └── p2 ├── demo1.go └── demo2.go
1 2 3 4 5 6 package p2 import "fmt" func Test1(){ fmt.Println("p1.test1") }
1 2 3 4 5 6 package p2 import "fmt" func Test2(){ fmt.Println("p1.test2") }
1 2 3 4 5 6 package p2 import "fmt" func Test1(){ fmt.Println("test1") }
1 2 3 4 5 6 package p2 import "fmt" func Test2(){ fmt.Println("test2") }
1 2 3 4 5 6 7 8 9 10 11 12 13 package main import ( p1 "pack/p1" // p2 "pack/p2" ) func main(){ // p2.Test1() // p2.Test2() p1.Test1() p1.Test2() }
三、参考
参考一
参考二
参考三
Go语言for循环 一、基础
标准三段式
for ... range
方式
无限循环方式for{...}
二、使用
标准三段式
1 2 3 4 5 6 7 8 9 10 11 package main import "fmt" func main() { sum := 0 for i := 0; i <= 100; i++ { sum += i } fmt.Println(sum) }
for ... range
形式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package main import "fmt" func main() { array := [...]int64{1, 2, 3, 4} for k, v := range array { fmt.Println(k, v) } fmt.Println() for _, v := range array { fmt.Println(v) } fmt.Println() for i:=0; i<len(array); i++ { fmt.Println(array[i]) } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package main import "fmt" func main() { slice1 := []string{"liu", "liusir", "liuliu"} for index,value := range slice1{ fmt.Printf("index:%d, value:%s\n", index, value) } for _,value := range slice1{ fmt.Printf("value:%s\n",value) } for i:=0; i<len(slice1); i++ { fmt.Printf("index:%d, value:%s\n", i, slice1[i]) } }
1 2 3 4 5 6 7 8 9 10 11 12 13 package main import "fmt" func main() { map1 := map[string]interface{}{"Name":"liu", "Age":30, "Male": true, "Hobby":[]string{"跑步", "电影"}} fmt.Printf("length of map1:%d\n", len(map1)) for key,value := range map1{ fmt.Println(key,value) } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package main import ( "fmt" "time" ) func main() { ch := make(chan int) go func() { for i := 1; i <= 5; i++ { ch <-i time.Sleep(time.Second) } close(ch) }() for value := range ch{ fmt.Println(value) } }
无限循环方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package main import ( "fmt" "time" ) func main() { i := 1 for { fmt.Println(i) time.Sleep(time.Second) i++ } }
三、参考
Go语言基本数据类型 一、概念
在Go编程语言中,数据类型用于声明函数和变量,它的出现是为了合理分配利用内存,根据需求把数据分成不同的内存大小,避免不必要的内存浪费。
Go语言数据类型包含基本类型和复合类型两大类。
基本数据类型包括:布尔型、字符串型、数值型(整型、浮点型、复数型)、错误类型。
复合数据类型包括:数组、结构体、指针、切片、字典(也称map)、通道、函数、接口。
切片、字典类型、通道类型、函数类型等属于引用类型,数组、其他基础数据类型、结构体类型等属于值类型
声明变量的几种方式
标准声明方式var str string
类型推断方式var str = "liusir"
短变量声明方式str := "liusir"
类型推断 + 语法糖
只能在函数体内部使用短变量声明
优点
提升程序的灵活性
重构代码变得更加简单
提高程序的可维护性
类型推断在代码编译时确定,不影响运行效率
动态语言是运行时确定变量类型,静态语言是编译时确定变量类型
变量的重声明
重声明的类型必须与原本的类型相同
变量的重声明只可能发生在某一个代码块中
必须要使用短变量的声明方式
声明并赋值的变量必须是多个,并且至少有一个是新的变量
可重名变量
在同一个代码块中不允许出现重名的变量
可重名变量的查找过程
优先查找当天作用域代码块
当前代码块没有则向外查找
直至最外层仍找不到则报错
默认不查找import的包,特殊情况import . "XXX"
除外
二、使用
map并发安全写
可重名变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package main import "fmt" var value = "var1" func main() { test(value) // value :="liusir" value := "var2" { value := "var3" fmt.Printf("The value is %s.\n", value) } fmt.Printf("The value is %s.\n", value) test(value) } func test(name string) { fmt.Printf("The value is %s.\n", name) }
三、参考
参考一
Go语言切片和数组 一、概念
数组:是同一类型元素的集合,Go语言中不允许混合不同类型的元素(interface{}类型数组可以包含任意类型)。
数组是值类型,意为当把数组a赋值给一个新的变量b时,变量b得到的只是一个原始数组a的副本,如果对新变量b进行更改则不会影响原始数组a。
数组的下标从0
开始.
数组类型的值长度是固定的
数组的长度在声明它的时候就必须给定,并且之后不会再改变
声明数组,如长度为4的int型数组var arr [4]int
切片:是由数组建立的一种方便、灵活且功能强大的包装,它本身不拥有任何数据,只是对现有数组的引用。
切片是引用类型,同值类型刚好相反。
切片类型的值长度是可变长的
声明切片
方式一:使用varvar s1 []int
方式二:使用makevar s2 = make([]int, len, cap)
,cap可选
使用make函数初始化切片时,如果不指明其容量,那么它就会和长度一致。
切片的容量实际上代表了它的底层数组的长度(仅限于方式一和方式二的情形)
方式三:基于数组或切片s4 := s3[3:6]或s4 := arr[3:6]
,其中[3,6)
扩容
扩容时不会改变原来的切片,而是会生成一个容量更大的切片,然后将把原有的元素和新元素一并拷贝到新切片中
扩容后的容量:<1024
则为2倍(极端情况追加后比2倍还大则以新切片为准),>=1024
则为1.25倍
缩容
切片缩容之后还是会引用底层的原数组,有时候会造成大量垃圾内容没有被回收
联系与区别
每个切片的底层数据结构中一定会包含一个数组,数组可以被叫做切片的底层数组,而切片则可以被看作是对数组的某个连续片段的引用
通过函数len可得到数组和切片的长度,通过函数cap可得到数组和切片的容量
二、使用
数组
声明方式
数组长度len(a)
遍历数组
使用for循环
使用range返回key,value
,key可省略
1 2 3 4 5 6 7 8 9 10 11 package main import "fmt" func main() { c := [...]int{1, 2} for i := 0; i < len(c); i++ { fmt.Printf("第 %d 个元素为:%d\n", i, c[i]) } for i, v := range c { fmt.Printf("第 %d 个元素为:%d\n", i, v) } }
切片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package main import ( "fmt" ) func main() { s3 := []int{0, 1, 2, 3, 4, 5, 6, 7, 8} fmt.Println(s3) s4 := s3[4:6] fmt.Println(s4) // 引用类型,s4改变会影响s3 s4 = append(s4, 98) fmt.Println(s4) fmt.Println(s3) }
三、参考
参考一
参考二
Go语言字典map 一、基础
字典类型map[keyType]valueType
,map的读取和设置也是通过下标key来操作
map的数据类型更多,slice则只能通过int型的下标操作
声明和初始化
方式一:先声明,后初始化var m1 map[string]int m1= make(map[string]int)
方式二:声明并初始化赋值var m1 map[string]int=map[sting]int{"key":0}
方式三:使用makem2:=make(map[string]int)
字典底层实现/usr/local/go/src/runtime/map.go
(go version go1.18.1 darwin/amd64)
Go语言的字典类型其实是一个哈希表(hash table)的特定实现
在Go语言的字典中,每一个键值都是由它的哈希值代表的
Go语言字典的键类型不可以是函数类型、字典类型和切片类型
Go语言规范规定在键类型的值之间必须可以施加操作符==
和!=
,即键类型的值必须要支持判等操作
如果键的类型是接口类型的,那么键值的实际类型也不能是上述三种类型,否则在程序运行过程中会引发panic
接口类型尽量不要作为键,使用不当会引发运行时panic
如果键的类型是数组类型,那么还要确保该类型的元素类型不是函数类型、字典类型或切片类型
如果键的类型是结构体类型,那么还要保证其中字段的类型的合法性
只有键的哈希值和键值都相等,才能说明查找到了匹配的键-元素对
哈希函数
哈希碰撞
装载因子(负载因子)
扩容
二、使用
基础demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package main import ( "fmt" "sort" ) func main(){ myMap := make(map[string]int) myMap["liu"] = 5 myMap["sir"] = 2 myMap["dot"] = 3 myMap["me"] = 4 fmt.Println(myMap) var mySlice []string for k := range myMap { mySlice = append(mySlice, k) } // 每次打印顺序都不一定 fmt.Println(mySlice) // 按字典序 sort.Strings(mySlice) fmt.Println(mySlice) // var arr [5]int = [5]int{1,2,3,4,5} // arr := [5]int{1,2,3,4,5} // arr := [...]int{1,2,3,4,5} arr := [5]int{1:1,4:4} // var multiMap map[int]interface{} multiMap := make(map[int]interface{}) multiMap[0] = false multiMap[4] = 1 multiMap[1] = arr multiMap[2] = "liusir" fmt.Println(multiMap) }
demo2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package main import ( "fmt" ) func main() { m := make(map[string]string) m["a"] = "a" m["b"] = "b" // fmt.Println(m) if v, ok := m["c"]; ok { fmt.Println(v) } else { fmt.Println(ok) fmt.Println("not found") } for k, v := range m { fmt.Println(k, v) } }
三、参考
Go语言信道channel 一、概念 信道是goroutine之间通信的一种方式,可以在信道两端进行操作。
二、使用 三、参考
参考一
Go语言结构体struct 一、概念 和C语言类似,结构体是一种用户自定义的可用的数据类型,它允许存储不同类型的数据项。同时结构体是一种值类型,在不包含引用类型的情况下可以进行等值==
比较。
分类
访问控制
结构体内部变量的首字母大写是公开变量,无访问限制。
结构体内部变量的首字母小写是内部变量,只有属于同一个package的代码才能直接访问。
结构体类型定义方法,表明该方法的适用对象是当前结构体,具体做法是在func关键字和方法名之间加入接收者
进阶
空结构体及其应用
struct{}
是空结构体类型,struct{}{}
是对它的实例化
空结构体也是结构体,但不占内存
结构体匿名字段
嵌套结构体
二、使用
声明结构体
1 2 3 4 5 6 7 8 9 10 type 结构体名 struct { 属性名1 类型 `tag` 属性名2 类型 `tag` } 如: type Person struct { Name string age int }
创建结构体变量
直接声明:var p1 Person
用大括号:p2 := Person{}
new
方式:var p3Point = new(Person)
&
方式:var p4Point = &Person{"王5", 22}
定义方法
1 2 3 4 5 6 7 8 9 type Person struct { Name string age int } func (p Person) say() float64 { fmt.Println("hello") return 1.1 }
三、参考
参考一
参考二
参考三
参考四
Go语言接口interface 一、基础 在面向对象编程领域里,接口一般这样定义:接口定义一个对象的行为。接口只指定了对象应该做什么,至于如何实现这个行为则由对象本身去确定。由于Go不支持类class和面向对象编程(可以以别的方式实现类似的功能),所以在Go语言中,接口就是方法签名的集合。接口指定了一个类型应该具有的方法,并由该类型决定如何实现这些方法。
对于任何数据类型,只要它的方法集合中完全包含了一个接口的全部的方法,那么它就一定是这个接口的实现类型。
接口变量
二、使用 三、参考
参考一
参考二
Go语言多返回值 一、概念 一个函数的函数名即是该函数的代表,也是一个变量。由于函数名变量通常用来把函数的处理结果数据返回给调用函数,即递归调用,所以一般把函数名变量称为返回值,函数的返回值类型是在定义函数时指定的。
Go一个非常特别的特性(对于编译语言而言)是函数和方法可以返回多个值(Python和Perl同样也可以)。这可以用于改进一大堆在C程序中糟糕的惯例用法:修改参数的方式,返回一个错误(例如遇到EOF则返回-1)。Go函数的返回值或者结果参数可以指定一个名字(名字不是强制的,但是它们可以使得代码更加健壮和清晰),并且像原始的变量那样使用,就像输入参数那样。如果对其命名,在函数开始时,它们会用其类型的零值初始化。如果函数在不加参数的情况下执行了return语句,结果参数会返回。用这个特性,允许(再一次的)用较少的代码做更多的事。
二、使用
普通例子
1 2 3 4 5 6 7 8 9 10 11 12 13 package main import "fmt" func multiReturn() (a int, b int, c int) { a, b, c = 111, 222, 333 return } func main() { a, b, c := multiReturn() fmt.Printf("a = %d, b = %d, c = %d\n", a, b, c) }
运行
方式一:go run test.go
方式二:go build test.go && ./test
方式三:在线运行
错误处理
三、参考
参考一
Go语言断言 一、基础 Golang中的所有程序都实现了interface{}
的接口,这意味着所有的类型如string,int,int64
甚至是自定义的struct
类型都就此拥有了interface{}
的接口,这种做法和Java中的Object类型比较类似。
二、使用 三、参考
参考一
Go语言反射 一、基础 编写好的程序在编译时,变量会被转换为内存地址,变量名不会被编译器写入到可执行部分,所以在运行程序时程序无法获取自身的信息。支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。简而言之,反射就是指在程序运行期对程序本身进行访问和修改的能力。
Golang中提供了一种机制在运行时更新和检查变量的值、调用变量的方法和变量支持的内在操作,但是在编译时并不知道这些变量的具体类型,这种机制被称为反射。
二、使用 三、参考
参考一
参考二
参考三
参考四
参考五
参考六
Go语言延迟调用defer 一、概念 defer是Go语言中的延迟执行语句,用来添加函数结束时执行的代码,常用于释放某些已分配的资源、关闭数据库连接、断开socket连接、解锁一个加锁的资源等。Go语言机制能够保证一定会执行defer
语句中的代码,有点类似于Java
、C#
语言里的finally
语句,C++
、PHP
语言里的析构函数等。
defer后面必须是函数调用语句,其后面的函数会当前函数执行结束后被调用。
二、使用
demo
1 2 3 4 5 6 7 8 9 10 11 12 13 package main import "fmt" func testFunc() { fmt.Println("执行结束...") } func main() { defer testFunc() fmt.Println("开始执行...") }
三、参考
参考一
Go语言异常处理panic 一、概念 panic是Go语言中用于终止程序的一种函数,一般出现在程序出现了很大的故障,如不能在提供服务了,或程序在运行阶段碰到了内存异常的操作,如空指针的取值,改写只读内存等。
Go语言交叉编译 一、概念 很多时候会有这样的场景出现,使用Mac开发或使用Windows开发,需要编译成Linux系统的执行文件,此时就需要用到交叉编译。在1.5+
版本后(通过自举),Go程序编译时可通过设置环境变量实现交叉编译,从而大大提高了开发效率。
编程语言的自举就是完全依靠自身语言(如go)写出自己的编译器、解释器来编译自身。
二、使用
可通过go tool dist list
查看当前版本支持的系统和架构
常见如下:
GOOS
GOARCH
darwin
386
darwin
amd64
darwin
arm
darwin
arm64
dragonfly
amd64
freebsd
386
freebsd
amd64
freebsd
arm
linux
386
linux
amd64
linux
arm
linux
arm64
linux
ppc64
linux
ppc64le
netbsd
386
netbsd
amd64
netbsd
arm
openbsd
386
openbsd
amd64
openbsd
arm
plan9
386
plan9
amd64
solaris
amd64
windows
386
windows
amd64
使用
1 2 3 4 5 6 7 8 9 10 package main import ( "fmt" "runtime" ) func main() { fmt.Printf("OS: %s\nArchitecture: %s\n", runtime.GOOS, runtime.GOARCH) }
windows/linux/mac
CGO_ENABLED=0 GOOS=window GOARCH=amd64 go build main.go
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build main.go
默认情况下Go环境变量CGO_ENABLED=1
(可通过go env
查看),即默认开启cgo,意为允许在Go代码中调用C代码。
要使用CGO特性,需要安装C/C++
构建工具链,在MacOS和Linux下是要安装GCC,在windows下是需要安装MinGW工具,同时需要保证环境变量CGO_ENABLED被设置为1,这表示CGO是被启用的状态。
交叉编译时CGO默认是禁止的,亦可在编译时指定CGO_ENABLED=0。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package main import ( "fmt" "runtime" ) /* #include <stdio.h> void sayHello(){ int a; printf("请输入数字:"); scanf("%d", &a); printf("a的值为:%d", a); } */ import "C" func main() { fmt.Printf("OS: %s\nArchitecture: %s\n", runtime.GOOS, runtime.GOARCH) C.sayHello() }
CGO_ENABLED=1 go run main.go
CGO_ENABLED=0 go run main.go
三、参考
参考一
Go语言go build / go install / go run / go get / go mod
go build
对于命令源码文件,执行后会在当前目录下生成可执行文件
执行go build时指定文件名如index.go,生成的可执行文件与命令源码文件名称相同如index
执行go build时不指定文件名,生成的可执行文件与命令源码文件所在目录名相同如goapp
对于库源码文件,执行后不会在当前目录生成任何文件(可通过-x
参数查看构建细节),主要用于验证和检查
go install:约等于go build + 链接
对于命令源码文件,执行后会在$GOPATH/bin/
目录下生成可执行文件
对于库源码文件,执行后会在$GOPATH/pkg/darwin_amd64(平台)/
目录下生成归档文件
go run:只能用于main包,在临时目录生成可执行文件并执行
go get:约等于git clone + go install
go mod:go1.11新增特性,用于替代老版本必须将项目放在GOPATH下,不方便管理
相关的三个参数设置GO111MODULE/GOPRIVATE/GOPROXY
go env -w GO111MODULE=off/on/auto
Go语言常见web框架 一、概念 软件框架(software framework),通常指的是为了实现某个业界标准或完成特定基本任务的软件组件规范,也指为了实现某个软件组件规范时,提供规范所要求之基础功能的软件产品。Web框架,全称web应用框架(Web application framework)是一种开发框架,用来支持动态网站、网络应用程序及网络服务的开发。
二、常见
Beego
Gin
Iris
Echo
Buffalo
Revel
三、参考
参考一
参考二
Go语言type关键字 一、用法
定义结构体
定义接口
别名类型
type MyString = string
代表MyString是string类型的别名类型
type MyString2 string
则代表MyString2和string是两个不同的类型,是对string的再定义
Go默认的别名类型:byte
是uint8
的别名类型,而rune
是int32
的别名类型
源类型和别名类型是两个对立的概念
类型重定义
type MyString2 string
代表MyString2和string是两个不同的类型,是对string的再定义,此时称string为MyString2的潜在类型
潜在类型相同的不同类型的值之间是可以进行类型转换的
两个不同类型的潜在类型相同,它们的值之间不能进行判等或比较,它们的变量之间也不能赋值
类型判断开关(type switch
)
二、例子 三、参考
Go语言sync.WaitGroup 一、基础
引入,运行100个协程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package main import ( "fmt" "sync" ) func main() { counter := 0 for i := 0; i < 100 ; i++{ go func(i int) { counter += 1 fmt.Println("Goroutine ",i) }(i) } fmt.Println(counter) }
加入sleep休眠(休眠要足够长以保证100个协程运行结束)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package main import ( "fmt" "time" ) func main() { counter := 0 for i := 0; i < 100 ; i++{ go func(i int) { counter += 1 fmt.Println("Goroutine ",i) }(i) } time.Sleep(time.Second * 5) fmt.Println(counter) }
使用管道channel(大材小用),10k+导致内存飙升
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package main import ( "fmt" "sync" ) func main() { counter := 0 ch := make(chan int) count := 100 for i := 0; i < 100 ; i++{ go func(i int) { counter += 1 fmt.Println("Goroutine ",i) ch <- 0 }(i) } for range ch { count-- if count == 0 { close(ch) } } fmt.Println(counter) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package main import ( "fmt" "sync" ) func main() { counter := 0 var wg sync.WaitGroup wg.Add(100) for i := 0; i < 100 ; i++{ go func(i int) { counter += 1 fmt.Println("Goroutine ",i) wg.Done() }(i) } wg.Wait() fmt.Println(counter) }
二、详解 WaitGroup顾名思义,就是用来等待一组操作完成的。WaitGroup内部实现了一个计数器,用来记录未完成的操作个数。它提供了三个方法
wg.Add(n)用来添加计数
wg.Done()用来在操作结束时调用,使计数减一。
wg.Wait()用来等待所有的操作结束,即计数置为为0
该函数会在计数不为0时等待,在计数为0时立即返回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package main import ( "fmt" "sync" ) func main() { counter := 0 wg := sync.WaitGroup{} wg.Add(100) for i := 0; i < 100; i++ { counter += 1 // 传参必须为&wg go print(i, &wg) } wg.Wait() fmt.Println(counter) } // 一定要通过指针传值 func print(i int, wg *sync.WaitGroup) { fmt.Println(i) wg.Done() }
三、参考
Go语言类型断言 一、基础
方式一:x.(T)
x:必须是接口类型
interface{}代表空接口,任何类型都是它的实现类型
空接口类型interface{},不包含任何方法定义、
空结构体类型struct{},不包含任何字段和方法的、
空的切片值[]string{}
空的字典值map[int]string{}
任何类型的值都可以很方便地被转换成空接口的值,即interface{}(var)
T:可以是接口类型,也可以是非接口类型
方式二:switch t := x.(type)
语句
二、使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package main import ( "fmt" ) var container = []string{"beijng", "shanghai", "guangzhou"} func main() { container := map[int]string{0: "liu", 1: "liusir", 2: "liuliu"} fmt.Printf("The element is %q.\n", container[1]) // value, ok := interface{}(container).([]string) value, ok := interface{}(container).(map[int]string) // interface{}(container)意为将container转换为空接口interface{} // .(map[int]string)用于类型断言,即interface{}(container)是否为map[int]string类型 if ok { fmt.Println(value) } else { fmt.Println(ok) } var x interface{}=10 // v,ok := x.(int) v,ok := x.(string) if ok { fmt.Println(v) } else { fmt.Println(ok) } // container := 1 var elem string var err error switch t := interface{}(container).(type) { case []string: elem = t[1] case map[int]string: elem = t[1] default: err = fmt.Errorf("unsupported container type: %T", container) fmt.Println(err) return } fmt.Printf("The element is %q. (container type: %T)\n", elem, container) }
三、参考
Go语言访问权限规则 一、分类
包级私有
包级公开
模块级私有(internal关键字)
二、使用
make和new 一、基础
make是专门用来创建slice、map、channel的,它返回的是被创建的值,并且立即可用。
new是申请一小块内存并标记它是用来存放某个值的,它返回的是指向这块内存的指针,而且这块内存并不会被初始化。
对于引用类型的值不要用new,能用make就用make,不能用make就用复合字面量来创建
二、使用 三、参考
Go语言标准库 一、基础 在Go语言的安装文件里包含了一些可以直接使用的包,即标准库。Go语言的标准库提供了清晰的构建模块和公共接口,包含I/O操作、文本处理、图像、密码学、网络和分布式应用程序等,并支持许多标准化的文件格式和编解码协议。
Go语言标准库包名
说明
bufio
带缓冲的 I/O 操作
bytes
实现字节操作
container
封装堆、列表和环形列表等容器
crypto
加密算法
database
数据库驱动和接口
debug
各种调试文件格式访问及调试功能
encoding
常见算法如 JSON、XML、Base64 等
flag
命令行解析
fmt
格式化操作
go
Go语言的词法、语法树、类型等。可通过这个包进行代码信息提取和修改
html
HTML 转义及模板系统
image
常见图形格式的访问及生成
io
实现 I/O 原始访问接口及访问封装
math
数学库
net
网络库,支持 Socket、HTTP、邮件、RPC、SMTP 等
os
操作系统平台不依赖平台操作封装
path
兼容各操作系统的路径操作实用函数
plugin
Go 1.7 加入的插件系统。支持将代码编译为插件,按需加载
reflect
语言反射支持。可以动态获得代码中的类型信息,获取和修改变量的值
regexp
正则表达式封装
runtime
运行时接口
sort
排序接口
strings
字符串转换、解析及实用函数
time
时间接口
text
文本模板及 Token 词法器
二、使用
container/list
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package main import ( "container/list" "fmt" ) func main() { l := list.New() l.PushFront("liu") l.PushBack("sir") element := l.PushBack("liuliu") l.InsertBefore("long", element) l.InsertAfter("me", element) // l.Remove(element) for i := l.Front(); i != nil; i = i.Next() { fmt.Println(i.Value) } }
三、参考
Go语言any关键字 一、基础 golang 1.18新的关键字any,它其实是空接口interface{}的别名,即type any = interface{}
。引入any关键字的好处之一就是使用泛型时简化代码的书写。
二、使用 三、参考
Go语言泛型 一、基础 二、使用 三、参考
Go语言占位符 一、基础
占位符
类型
说明
%v
普通占位符
相应值的默认格式
%+v
普通占位符
打印结构体时,会添加字段名
%#v
普通占位符
相应值的Go语法表示
%T
普通占位符
相应值的类型的Go语法表示
%%
普通占位符
字面上的百分号,并非值的占位符
%t
布尔占位符
true 或 false
%b
整数占位符
二进制表示
%c
整数占位符
相应Unicode码点所表示的字符
%d
整数占位符
十进制表示
%o
整数占位符
八进制表示
%q
整数占位符
单引号包裹的字符字面值,由Go语法安全地转义
%x
整数占位符
十六进制表示,字母形式为小写a-f
%X
整数占位符
十六进制表示,字母形式为大写A-F
%U
整数占位符
Unicode格式:U+1234,等同于 “U+%04X”
%b
浮点数和复数
无小数部分的指数为二的幂的科学计数法,与strconv.FormatFloat的’b’转换格式一致
%e
浮点数和复数
科学计数法
%E
浮点数和复数
科学计数法
%f
浮点数和复数
有小数点而无指数
%g
浮点数和复数
根据情况选择%e或%f以产生更紧凑的(无末尾的0)输出
%G
浮点数和复数
根据情况选择%E或%f以产生更紧凑的(无末尾的0)输出
%s
字符串与字节切片
输出字符串表示(string类型或[]byte)
%q
字符串与字节切片
双引号围绕的字符串,由Go语法安全地转义
%x
字符串与字节切片
十六进制,小写字母,每字节两个字符
%X
字符串与字节切片
十六进制,大写字母,每字节两个字符
%p
指针
十六进制表示,前缀 0x
+
其他
总打印数值的正负号 对于%q(%+q)保证只输出ASCII编码的字符。
-
其他
在右侧而非左侧填充空格(左对齐该区域)
#
其他
为八进制添加前导0(%#o) 为十六进制添加前导0x(%#x)或 0X(%#X) 为%p(%#p)去掉前导0x等
‘ ‘
其他
(空格)为数值中省略的正负号留出空白(% d); 以十六进制(% x, % X)打印字符串或切片时,在字节之间用空格隔开
0
其他
填充前导的0而非空格;对于数字这会将填充移到正负号之后
说明
go没有’%u’点位符,若整数为无符号类型,默认就会被打印成无符号的。
宽度与精度的控制格式以Unicode码点为单位。宽度为该数值占用区域的最小宽度;精度为小数点之后的位数。
操作数的类型为int时,宽度与精度都可用字符’*’表示。
对于%g、%G而言,精度为所有数字的总数,例如:123.45,%.4g会打印123.5,而%6.2f会打印123.45。
%e、%f的默认精度都为6
对大多数的数值类型而言,宽度为输出的最小字符数,如果必要的话会为已格式化的形式填充空格。
对于字符串类型,精度为输出的最大字符数,如果必要的话会直接截断。
二、使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 package main import "fmt" type Wife struct { Name string Age int8 } type SkillFunc func() string type Man struct { Name string Male bool Age int8 Addr *string Salary float32 Hobby []string Edu map[string]string Skill SkillFunc Wife Wife Other interface{} } func main() { addr := "beijing" var man = Man{ Name: "liusir", Male: true, Age: 18, Addr: &addr, Salary: 27800.5, Hobby: []string{"钓鱼", "跑步", "看电影"}, Edu: map[string]string{"高中":"实验一中", "大学":"北京大学"}, Skill: func() string { return "I can speak Chinese and a little English" }, Wife: Wife{Name: "na", Age:18}, Other: 1, } skill := func() string { return "I'm stronger than before" } fmt.Printf("%p\n", skill) skill1 := skill() fmt.Println(skill1) skill2 := man.Skill fmt.Println(skill2) skill3 := man.Skill() fmt.Println(skill3) fmt.Println() // 普通占位符 fmt.Println("普通占位符=====") fmt.Printf("%v\n", man) fmt.Printf("%+v\n", man) fmt.Printf("%#v\n", man) fmt.Printf("%T\n", man) fmt.Printf("%%\n\n") // 布尔占位符 fmt.Println("布尔占位符=====") fmt.Printf("%s is male? %t\n\n", man.Name, man.Male) // 整型占位符 fmt.Println("整型占位符=====") fmt.Printf("%b\n", 5) fmt.Printf("%c\n", 0x4E2D) fmt.Printf("%d\n", 0x12) fmt.Printf("%o\n", 10) fmt.Printf("%q\n", 0x4E2D) fmt.Printf("%x\n", 13) fmt.Printf("%X\n", 13) fmt.Printf("%U\n\n", 0x4E2D) // 浮点数和复数 fmt.Println("浮点数和复数=====") fmt.Printf("%b\n", 10.2) fmt.Printf("%e\n", 10.2) fmt.Printf("%E\n", 10.2) fmt.Printf("%f\n", 10.2000000) fmt.Printf("%g\n", 10.20) fmt.Printf("%G\n\n", 10.2+2i) // 字符串与字节切片 fmt.Println("字符串与字节切片=====") fmt.Printf("%s\n", []byte("Go语言")) fmt.Printf("%q\n", "Go语言") fmt.Printf("%x\n", "abc") fmt.Printf("%X\n\n", "abc") // 指针 fmt.Println("指针=====") fmt.Printf("%p\n\n", man.Addr) // 其他 fmt.Println("其他=====") fmt.Printf("%+q\n", "中文") fmt.Printf("%#o\n", 10) fmt.Printf("%#x\n", 10) fmt.Printf("%#p\n", man.Addr) fmt.Printf("%#q\n", "Go语言") fmt.Printf("%#U\n", '中') fmt.Printf("% d\n", 10) fmt.Printf("%0d\n", -10) }
三、参考
Go语言实现枚举 一、基础 二、使用 三、参考
参考一
Go语言卫述语句 一、基础 二、使用 三、参考
Nginx反向代理Go 一、安装nginx 二、安装go 三、代码
cd ~/go/src && mkdir web
vim test.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 package main import ( "fmt" "log" "net/http" "strings" ) func printRequest(r *http.Request) { fmt.Println("r.Form=", r.Form) fmt.Println("r.PostForm=", r.PostForm) fmt.Println("path=", r.URL.Path) fmt.Println("scheme=", r.URL.Scheme) fmt.Println("method=", r.Method) fmt.Println("Get参数列表") for k, v := range r.Form { fmt.Println("Http Get["+k+"]=", strings.Join(v, " ; ")) } fmt.Println("Post参数列表") for k, v := range r.PostForm { fmt.Println("Http Post["+k+"]=", strings.Join(v, " ; ")) } fmt.Println("表单参数列表") arraA := r.Form["a"] fmt.Println("r.Form['a']=", arraA) if len(arraA) > 0 { fmt.Println("r.Form['a'][0]=", arraA[0]) } } func defaultFunc(w http.ResponseWriter, r *http.Request) { r.ParseForm() //解析参数 fmt.Println("This is func default") printRequest(r) fmt.Fprintf(w, "This is a default method") } func testFunc(w http.ResponseWriter, r *http.Request) { r.ParseForm() //解析参数 fmt.Println("This is func test") printRequest(r) fmt.Fprintf(w, "Func Test") } func anotherFunc(w http.ResponseWriter, r *http.Request) { r.ParseForm() //解析参数 fmt.Println("This is func another") printRequest(r) fmt.Fprintf(w, "Another Func") } func main() { http.HandleFunc("/", defaultFunc) http.HandleFunc("/test", testFunc) http.HandleFunc("/another", anotherFunc) err := http.ListenAndServe(":9090", nil) if err != nil { log.Fatal("Error:", err) } }
运行go run test.go
,浏览器打开localhost:9090
,可看到终端输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 This is func another r.Form= map[a:[b] id:[1]] r.PostForm= map[] path= /another scheme= method= GET Get参数列表 Http Get[id]= 1 Http Get[a]= b Post参数列表 表单参数列表 r.Form['a']= [b] r.Form['a'][0]= b This is func default r.Form= map[] r.PostForm= map[] path= /favicon.ico scheme= method= GET Get参数列表 Post参数列表 表单参数列表 r.Form['a']= []
四、设置反向代理
进入nginx配置目录cd /path/nginx/conf/vhost
vim go.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 server { listen 8081; #server_name go.cn; access_log logs/go.access.log; error_log logs/go.error.log; root html; index index.html index.htm index.php; location / { proxy_pass http://localhost:9090;#go 服务器可以指定到其他的机器上,这里设定本机服务器 proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }
运行go run index.go
,浏览器打开localhost:8081
,可看到对应输出
Go语言文件读取的几种方式 一、基础
读取文件有三种方式:将文件整个读入内存、按行读取、按字节数读取。
将文件整个读入内存
按行读取
按字节读取
二、参考
参考一
参考三
Go语言实现OOP 一、基础 Golang是支持面向对象编程特性的语言,不过在Golang中没有类的概念,而是一些别的方式来实现。
struct:通过给结构体类型定义方法实现传统OOP中的方法,具体做法是在方法当中定义接收者(类型接收者或指针接收者),位于func关键字和方法名之间。
当接收者是指针类型时,调用时和类型接收者一样,Golang内部会自动转化
结构体是值类型
结构体的所有字段在内存中是连续的
结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的属性(名字、个数、类型)
结构体进行type重新定义(相当于取别名),Golang认为是新的数据类型,但是相互间可以强转
结构体的每个字段上可以写上一个tag,该tag可以通过反射机制获取,常见的使用场景就是序列化和反序列化
interface
interface是方法的集合,要实现一个接口,必须实现该接口里面的所有方法
如果一个interface没有任何方法,则称该接口为空接口,任何类型都实现了空接口,空接口可以作为任何类型数据的容器
interface是Golang中实现多态的一种形式
interface可以嵌套,可以简单理解为继承
子接口拥有父接口的所有方法,想要使用该子接口的话,则必须将父接口和子接口的所有方法都实现
interface也可以进行类型转换
interface只有方法声明,没有实现,没有数据字段
map
reflection
二、实现
封装
继承
多态
三、参考
参考一
参考二
参考三
参考四
指针操作:取地址&
和取值*
一、基础
变量名前面添加&
操作符(前缀)来获取变量的内存地址,即取地址操作
对指针使用*
操作符,即指针取值操作
二、demo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package main import ( "fmt" ) func main() { var a int = 3 var b int = 3 var c int = a * b fmt.Println(c) var house = "liusir.me" ptr := &house fmt.Printf("house address: %p\n", ptr) fmt.Printf("ptr address: %p\n", &ptr) fmt.Printf("ptr type: %T\n", ptr) fmt.Printf("ptr value: %s\n", *ptr) *ptr = "liuyulong" fmt.Printf("ptr new value: %s\n", *ptr) fmt.Println("====") value := &ptr fmt.Printf("value type: %T\n", value) fmt.Printf("value: %s\n", **value) }
三、参考
参考一
括号匹配 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 package main import "fmt" type Stack struct { top int data []interface{} } func (s *Stack) InitList(maxSize int) { s.data = make([]interface{}, maxSize) s.top = -1 } func (s Stack) isNull() bool { return s.top == -1 } func (s Stack) isFull() bool { return s.top >= cap(s.data)-1 } func (s *Stack) Push(Element interface{}) bool { if s.isFull() { return false } s.top++ s.data[s.top] = Element return true } func (s *Stack) Pop() (interface{}, bool) { if s.isNull() { return nil, false } e := s.data[s.top] s.top-- return e, true } func MatchBracket(str string) bool { s := Stack{} // s.InitList(30) s.InitList(len(str)) for i := 0; i < len(str); i++ { if str[i] == '(' && !s.isFull() { s.Push(str[i]) } if str[i] == ')' { if s.isNull(){ return false } s.Pop() } } return s.top == -1 } func main() { fmt.Println(MatchBracket("(((((((((((((((((((((((((((((())))))))))))))))))))))))))))))")) fmt.Println(MatchBracket("((((")) }
go-redis 一、基础 二、代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 package main import ( "context" "fmt" "math/rand" "time" "github.com/go-redis/redis" ) const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" var redisdb *redis.Client var ctx = context.Background() func randStr(n int) string { b := make([]byte, n) for i := range b { b[i] = letters[rand.Intn(len(letters))] } return string(b) } func randomInt(start int,end int) int{ rand.Seed(time.Now().UnixNano()) random:=rand.Intn(end-start) random = start + random return random } func initRedis()(err error){ redisdb := redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "", DB: 0, }) pong, err := redisdb.Ping(ctx).Result() fmt.Println(pong, err) key := "testZset" for i:=0;i<=1000000;i++ { member := randStr(16) score := randomInt(0, 10000) items := []redis.Z{ redis.Z{Score: float64(score),Member:member}, } // fmt.Println(items) _,err1 := redisdb.ZAdd(ctx, key, items...).Result() if err1 != nil { fmt.Printf("redis zadd failed! err:%v\n",err1) return } } return } func insertData() { key := "testZset" member := randStr(16) score := randomInt(0, 10000) items := []redis.Z{ redis.Z{Score: float64(score),Member:member}, } fmt.Println(items) _,err1 := redisdb.ZAdd(ctx, key, items...).Result() if err1 != nil { fmt.Printf("redis zadd failed! err:%v\n",err1) return } } func main() { err := initRedis() if err != nil { fmt.Printf("connect redis failed! err : %v\n",err) return } // error // insertData() }
字符串全排列 一、代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 package main import ( "fmt" "sort" ) func permutation1(s string) []string { var ( //tags保存字符串各个字符是否被使用过的标志,注意key值是s中字符的下标,不是字符,因为s可能是abbc这种 tags = make(map[int]bool, 0) //res保存需要返回的结果 res = make([]string, 0) //str将s类型转换,排序用 str = []byte(s) ) //需要先将s排序,将abcb排序为abbc这种 sort.Slice(str, func(i, j int) bool { return str[i] < str[j] }) s = string(str) //递归调用 dfs(s, tags, 0, "", &res) return res } func dfs(s string, tags map[int]bool, index int, resStr string, res *[]string) { //递归终止条件 if index >= len(s) { *res = append(*res, resStr) return } //遍历处理字符串 for i := 0; i < len(s); i++ { //如果s[i]没被使用过,将其与resStr连接,标志位标记为已使用,然后进入 //注意tags的key值为下标值不是字符串,递归返回后一定要将刚刚处理的字符状态归为 if tags[i] == false { resStr += string(s[i]) tags[i] = true dfs(s, tags, index+1, resStr, res) resStr = resStr[:len(resStr)-1] tags[i] = false //处理重复的情况,如果有重复的,只递归处理一次,避免重复 for { if i < len(s)-1 && s[i] == s[i+1] { i++ } else { break } } } } } func permutation2(str string) []string { var result []string if str == "" { return result } fmt.Println(str) str1 := []byte(str) fmt.Println(str1) permutationHandler([]byte(str), 0, &result) return result } func permutationHandler(strByte []byte, i int, result *[]string) { length := len(strByte) if i != length { //利用map排除重复字符 strMap := make(map[string]int) //固定首字符,递归剩余字符;首字符依次与后面交换,继续递归剩余字符 for j := i; j < length; j++ { _, ok := strMap[string(strByte[j])] if !ok { strMap[string(strByte[j])] = 1 if j != i { strByte[i], strByte[j] = strByte[j], strByte[i] } permutationHandler(strByte, i+1, result) if j != i { strByte[i], strByte[j] = strByte[j], strByte[i] } } } } else { *result = append(*result, string(strByte)) } } func Perm(a []rune, f func([]rune)) { perm(a, f, 0) } func perm(a []rune, f func([]rune), i int) { length := len(a) if i != length { strMap := make(map[string]int) for j := i; j < length; j++ { _, ok := strMap[string(a[j])] if !ok { strMap[string(a[j])] = 1 if j != i { a[i], a[j] = a[j], a[i] } perm(a, f, i+1) if j != i { a[i], a[j] = a[j], a[i] } } } } else { f(a) } } func main() { res1 := permutation1("ABC") fmt.Println(res1) res2 := permutation2("ABC") fmt.Println(res2) Perm([]rune("ABC"), func(a []rune) { fmt.Println(string(a)) }) }
二、参考
随机生成字符串 一、代码
main_test.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 package main import ( "fmt" "math/rand" "strings" "testing" "time" "unsafe" ) var letters1 = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") func randStr1(n int) string { b := make([]rune, n) for i := range b { b[i] = letters1[rand.Intn(len(letters1))] } return string(b) } func TestApproach1(t *testing.T) { rand.Seed(time.Now().UnixNano()) fmt.Println(randStr1(10)) } func BenchmarkApproach1(b *testing.B) { rand.Seed(time.Now().UnixNano()) for i := 0; i < b.N; i++ { _ = randStr1(10) } } const letters2 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" func randStr2(n int) string { b := make([]byte, n) for i := range b { b[i] = letters2[rand.Intn(len(letters2))] } return string(b) } func TestApproach2(t *testing.T) { rand.Seed(time.Now().UnixNano()) fmt.Println(randStr2(10)) } func BenchmarkApproach2(b *testing.B) { rand.Seed(time.Now().UnixNano()) for i := 0; i < b.N; i++ { _ = randStr2(10) } } const letters3 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" func randStr3(n int) string { b := make([]byte, n) for i := range b { b[i] = letters3[rand.Int63() % int64(len(letters3))] } return string(b) } func TestApproach3(t *testing.T) { rand.Seed(time.Now().UnixNano()) fmt.Println(randStr3(10)) } func BenchmarkApproach3(b *testing.B) { rand.Seed(time.Now().UnixNano()) for i := 0; i < b.N; i++ { _ = randStr3(10) } } const letters4 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" const ( // 6 bits to represent a letters4 index letterIdBits = 6 // All 1-bits as many as letterIdBits letterIdMask = 1 <<letterIdBits - 1 ) func randStr4(n int) string { b := make([]byte, n) for i := range b { if idx := int(rand.Int63() & letterIdMask); idx < len(letters4) { b[i] = letters4[idx] i++ } } return string(b) } func TestApproach4(t *testing.T) { rand.Seed(time.Now().UnixNano()) fmt.Println(randStr4(10)) } func BenchmarkApproach4(b *testing.B) { rand.Seed(time.Now().UnixNano()) for i := 0; i < b.N; i++ { _ = randStr4(10) } } const letters5 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" const ( // 6 bits to represent a letter index letterIdBits5 = 6 // All 1-bits as many as letterIdBits letterIdMask5 = 1<<letterIdBits5 - 1 letterIdMax5 = 63 / letterIdBits5 ) func randStr5(n int) string { b := make([]byte, n) // A rand.Int63() generates 63 random bits, enough for letterIdMax letters5! for i, cache, remain := n-1, rand.Int63(), letterIdMax5; i >= 0; { if remain == 0 { cache, remain = rand.Int63(), letterIdMax5 } if idx := int(cache & letterIdMask); idx < len(letters5) { b[i] = letters5[idx] i-- } cache >>= letterIdBits remain-- } return string(b) } func TestApproach5(t *testing.T) { rand.Seed(time.Now().UnixNano()) fmt.Println(randStr5(10)) } func BenchmarkApproach5(b *testing.B) { rand.Seed(time.Now().UnixNano()) for i := 0; i < b.N; i++ { _ = randStr5(10) } } const letters6 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" var src = rand.NewSource(time.Now().UnixNano()) const ( // 6 bits to represent a letter index letterIdBits6 = 6 // All 1-bits as many as letterIdBits6 letterIdMask6 = 1<<letterIdBits6 - 1 letterIdMax6 = 63 / letterIdBits6 ) func randStr6(n int) string { b := make([]byte, n) // A rand.Int63() generates 63 random bits, enough for letterIdMax6 letters6! for i, cache, remain := n-1, src.Int63(), letterIdMax6; i >= 0; { if remain == 0 { cache, remain = src.Int63(), letterIdMax6 } if idx := int(cache & letterIdMask6); idx < len(letters6) { b[i] = letters6[idx] i-- } cache >>= letterIdBits6 remain-- } return string(b) } func TestApproach6(t *testing.T) { fmt.Println(randStr6(10)) } func BenchmarkApproach6(b *testing.B) { for i := 0; i < b.N; i++ { _ = randStr6(10) } } const letters7 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" var src7 = rand.NewSource(time.Now().UnixNano()) const ( // 6 bits to represent a letter index letterIdBits7 = 6 // All 1-bits as many as letterIdBits7 letterIdMask7 = 1<<letterIdBits7 - 1 letterIdMax7 = 63 / letterIdBits7 ) func randStr7(n int) string { sb := strings.Builder{} sb.Grow(n) // A rand.Int63() generates 63 random bits, enough for letterIdMax7 letters7! for i, cache, remain := n-1, src7.Int63(), letterIdMax7; i >= 0; { if remain == 0 { cache, remain = src7.Int63(), letterIdMax7 } if idx := int(cache & letterIdMask7); idx < len(letters7) { sb.WriteByte(letters7[idx]) i-- } cache >>= letterIdBits7 remain-- } return sb.String() } func TestApproach7(t *testing.T) { fmt.Println(randStr7(10)) } func BenchmarkApproach7(b *testing.B) { for i := 0; i < b.N; i++ { _ = randStr7(10) } } const letters8 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" var src8 = rand.NewSource(time.Now().UnixNano()) const ( // 6 bits to represent a letter index letterIdBits8 = 6 // All 1-bits as many as letterIdBits8 letterIdMask8 = 1<<letterIdBits8 - 1 letterIdMax8 = 63 / letterIdBits8 ) func randStr8(n int) string { b := make([]byte, n) // A rand.Int63() generates 63 random bits, enough for letterIdMax8 letters8! for i, cache, remain := n-1, src8.Int63(), letterIdMax8; i >= 0; { if remain == 0 { cache, remain = src8.Int63(), letterIdMax8 } if idx := int(cache & letterIdMask8); idx < len(letters8) { b[i] = letters8[idx] i-- } cache >>= letterIdBits8 remain-- } return *(*string)(unsafe.Pointer(&b)) } func TestApproach8(t *testing.T) { fmt.Println(randStr8(10)) } func BenchmarkApproach8(b *testing.B) { for i := 0; i < b.N; i++ { _ = randStr8(10) } }
运行go test -bench=. -benchmem
二、参考
参考一
参考二