生活札记
golang学习笔记 - 基础(二)
go mod init 方式配置的vscode需要把项目放在根目录下,一个mod对应一个目录,不然vscode一直提示找不到包:
一、转义字符
//转义字符
func EscapeCharacter() {
fmt.Println("--转义字符--")
fmt.Println("双引号(\\\"):\"")
fmt.Println("斜线(\\\\)\\")
fmt.Println("警报声(\\a)\a")
fmt.Println("退格(\\b)\b退格")
fmt.Println("换页(\\f)\f换页")
fmt.Println("换行(\\n)\n换行")
fmt.Println("回车替换(\\r)\r回车替换")
fmt.Println("制表符(\\t)\t制表符")
fmt.Println("垂直制表符(\\v)\v垂直制表符")
}
二、基础数据类型
Golang中所有的值类型变量常量都会在声明时被分配内存空间并被赋予默认值
bit: 比特位,二进制中的一位,信息的最小单位
byte: 字节,1 byte = 8 bit,在Go中byte是uint8的别名
byte、rune、string的区别:
string底层是[]byte。对于ASCII字符,Golang默认用1字节;对于汉字,Golang使用UTF-8字符集存储,默认用三个字节。
字符串默认在堆上分配内存存储,是不可变的字节数组,其头部指针指向一个字节数组。
string在内存中的存储结构是长度固定的字节数组,也就是说是字符串是不可变的。当要修改字符串的时候,需要转换为[]byte,修改完成后再转换回来。但是不论怎么转换,都必须重新分配内存,并复制数据,通过加号拼接字符串,每次都必须重新分配内存。
byte约等于int8,处理汉字是不合适的。
rune,处理字符串里的UTF-8很适合,rune实际上就是int32,只不过用来表示字符。
byte 等同于int8,常用来处理ascii字符
rune 等同于int32,常用来处理unicode或utf-8字符,字符串转rune:rr := []rune(str1)
string放在堆,底层是byte[],不可修改。如果一定要改,转成[]byte。
参考:https://blog.csdn.net/972301/article/details/89523243
三、指针
取址符:&(获取当前变量的地址)
取值符:*(访问地址指向的值)
数据类型:*指向的类型,如:*int,引用已有的变量,通过内建函数new()分配内存
注意:引用类型的默认值为nil,需要分配内存空间(引用已有值类型,或通过内建函数new()/make()来分配)
四、fmt格式字符
1)、通用
2)、整数
3)、布尔
4)、字符串或者byte切片
5)、指针
6)、浮点数
%#v:实例的完整输出,包括它的字段(在程序自动生成 Go 代码时也很有用)
五、流程控制
1)、switch…case 直接替代 if…else if…else
case结尾会自动break,如果需要继续匹配下一项可以加入fallthrough
package main
import "fmt"
func main() {
LABEL1:
for i := 0; i <= 5; i++ {
for j := 0; j <= 5; j++ {
if j == 4 {
continue LABEL1
}
fmt.Printf("i is: %d, and j is: %d\n", i, j)
}
}
}
2)、label与goto
标签,通常与多重for/switch…case结合使用
goto(容易造成逻辑混乱,不推荐使用)跳转到指定标签,通常与if结合使用
注意点:
Label在continue, break中是可选的, 但是在goto中是必须的
作用范围: 定义Label的函数体内
Label可以声明在函数体的任何位置, 不管Label声明在调用点的前面还是后面
参考:Golang中Label的用法:http://t.zoukankan.com/zhangyafei-p-13938116.html
六、defer…recover
错误捕捉延迟处理,结合被延迟调用的匿名函数使用
defer func() {
err := recover()
if err != nil {
fmt.Println(err)
}
}()
Recover 是一个Go语言的内建函数,可以让进入宕机流程中的 goroutine 恢复过来,recover 仅在延迟函数 defer 中有效,在正常的执行过程中,调用 recover 会返回 nil 并且没有其他任何效果,如果当前的 goroutine 陷入恐慌,调用 recover 可以捕获到 panic 的输入值,并且恢复正常的执行
panic 和 recover 的组合有如下特性:
有 panic 没 recover,程序宕机。
有 panic 也有 recover,程序不会宕机,执行完对应的 defer 后,从宕机点退出当前函数后继续执行。
提示
虽然 panic/recover 能模拟其他语言的异常机制,但并不建议在编写普通函数时也经常性使用这种特性。
在 panic 触发的 defer 函数内,可以继续调用 panic,进一步将错误外抛,直到程序整体崩溃。
如果想在捕获错误时设置当前函数的返回值,可以对返回值使用命名返回值方式直接进行设置。
参考:宕机恢复(recover)——防止程序崩溃:http://c.biancheng.net/view/64.html
七、数组
1)、等号右侧的长度可以简写为[…]自动判断
a := [...]int{1, 2, 3}
fmt.Println(a)
2)、占位符“_”
for _, value := range a {
fmt.Println(value)
}
3)、多维数组:元素为n-1维数组的数组为n维数组
//多维数组
b := [2][3]int{
{1, 1, 1},
{2, 2, 2},
}
fmt.Println(b)
4.2.5 string与[]byte
可相互转换格式字符通用
//string与[]byte
str := "Hello 世界"
fmt.Printf("类型 = %T, 值 = %v, s= %s", str, []byte(str), []byte(str))
八、自定义类型
1)、自定义数据类型
定义方法:type 自定义数据类型 底层数据类型
属于不同类型,混用需要类型转换
2)、类型别名
定义方法:type 自定义数据类型 = 底层数据类型
属于相同类型,混用无需类型转换
//自定义数据类型
type typeA int16
var a typeA = 1000
fmt.Printf("type = %T, value = %v\n", a, a)
//类型别名
type typeB = int32
var b typeB = 100
fmt.Printf("type = %T, value = %v", b, b)
九、随机数
注意:“math/rand”包生成的随机数是容易预测的,安全等级要求较高的随机数请参考“crypto/rand”包,或等更新“Go密码学”相关的内容
1)、随机数种子
rand.Seed(int64)
Unix时间: UTC(世界时间标准)1970年1月1日0时0分0秒起至现在所经过的时间
纳秒: 十亿分之一秒
Go获取Unix纳秒: time.Now().UnixNano()
2)、随机整数
3)、随机小数
//随机数
func RandNumber() {
//随机因子
seenNum := time.Now().UnixNano()
rand.Seed(seenNum)
a := rand.Intn(10)
fmt.Println(a)
b := rand.Float64()
fmt.Println(b)
}
十、字符串类型转换/字符串/中文字符常见操作
1)、fmt包
Sprintf:其他类型转字符串
Sscanf:字符串解析为其他类型
//字符串、utf8
func StringUtf8() {
//fmt
ip := 123
port := "aikehou.com"
//Sprintf:其他类型转为字符串
a := fmt.Sprintf("%d@%s", ip, port)
fmt.Println(a)
//字符串解析为其他类型
var (
i1 int
s1 string
)
n, err := fmt.Sscanf(a, "%d@%s", &i1, &s1)
if err != nil {
panic(err.Error())
}
fmt.Printf("%d, %d, %s\n", n, i1, s1)
}
2)、strconv包
Itoa:整数转字符串
Atoi:字符串转整数
Format...:基本字符转字符串
Parse...:字符串解析为基本类型
//字符串、utf8
func StringUtf8() {
//整数转字符串、字符串转整数
c := 10
c1 := strconv.Itoa(c)
fmt.Printf("%T,%v\n", c1, c1)
c2, err := strconv.Atoi(c1)
if err != nil {
panic(err.Error())
}
fmt.Printf("%T,%v\n", c2, c2)
//基本字符转字符串、字符串解析为基本类型,进制范围是2-36
d1 := strconv.FormatInt(300, 8)
fmt.Println(d1)
e1, err := strconv.ParseInt(d1, 8, 36)
if err != nil {
panic(err.Error())
}
fmt.Printf("%T,%v\n", e1, e1)
}
3)、字符串常见操作 (strings包常见函数)
Contains:是否存在子串
Count:子串出现次数,没有返回-1
Index:子串第一次出现的index,没有返回-1
LastIndex:子串最后一次出现的index,没有返回-1
Replace:替换前N个字符串,N < 0 则替换全部
ReplaceAll:替换全部
Repeat:重复N次字符串
HasPrefix:是否存在前缀
HasSuffix:是否存在后缀
EqualFold:不区分大小写比较字符串
ToLower:转为小写
ToUpper:转为大写
Fields:按空白符拆分字符串
Split:按sep串拆分字符串
SplitAfter:按sep拆分字符串,结尾保留sep
SplitN:按sep拆分前N个字符串,N < 0拆分全部
SplitAfterN:按sep拆分前N个字符串,结尾保留sep,N < 0拆分全部
Trim:修剪字符串两端的字符
TrimLeft:修剪字符串左边的字符
TrimRight:修剪字符串右边的字符
TrimPrefix:修剪前缀
TrimSuffix:修剪后缀
TrimSpace:修剪空白
Join:拼接字符串
*空白字符包括'\t', '\n', '\v', '\f', '\r', ' ', U+0085 (NEL), U+00A0 (NBSP).
//字符串、utf8
func StringUtf8() {
//string包
//是否存在子串
ss := "Hello 世界"
f := strings.Contains(ss, "llo")
fmt.Println(f)
//子串出现次数,没有返回-1
f1 := strings.Count(ss, "l")
fmt.Println(f1)
//子串第一次出现的index,没有返回-1
index := strings.Index(ss, "l")
fmt.Println(index)
//子串最后一次出现的index,没有返回-1
lastindex := strings.LastIndex(ss, "l")
fmt.Println(lastindex)
//替换前N个字符串,N < 0 则替换全部
news := strings.Replace(ss, "l", "b", 1)
fmt.Println(news)
//替换全部
news1 := strings.ReplaceAll(ss, "l", "b")
fmt.Println(news1)
//重复N次字符串
rs := strings.Repeat(ss, 2)
fmt.Println(rs)
//是否存在前缀
hp := strings.HasPrefix(ss, "Hello")
fmt.Println(hp)
//是否存在后缀
hs := strings.HasSuffix(ss, "界")
fmt.Println(hs)
//不区分大小写比较字符串
eq := strings.EqualFold("aaa", "AAA")
fmt.Println(eq)
// ToLower:转为小写
tl := strings.ToLower(ss)
fmt.Println(tl)
// Fields:按空白符拆分字符串
fi := strings.Fields(ss)
fmt.Println(fi)
// Split:按sep串拆分字符串
sf := strings.Split(ss, "l")
fmt.Println(sf)
// Trim:修剪字符串两端的字符
tm := strings.Trim(ss, "H")
fmt.Println(tm)
// TrimSpace:修剪空白
ts := strings.TrimSpace("\t" + ss + "\t")
fmt.Println(ts)
// Join:拼接字符串
ts := strings.Join([]byte{1,2,3}, "-")
fmt.Println(ts)
//字符串截取
s := "abcdefg"
b := s[n:m]
}
4)、中文字符常见操作 (utf8包常见函数)
RuneCount:统计byte切片图文字符
RuneCountInString:统计字符串切片图文字符
Valid:判断byte切片是否由有效的符文组成
ValidString:判断字符串切片是否由有效的符文组成
FullRune:判断byte切片第一个字符是否是有效符文
FullRuneInString:判断字符串切片第一个字符是否是有效符文
//字符串、utf8
func StringUtf8() {
//utf8
// RuneCount:统计byte切片图文字符
byte := []byte{1, 2, 3, 4}
by := utf8.RuneCount(byte)
fmt.Println(by)
// RuneCountInString:统计字符串切片图文字符
runes := utf8.RuneCountInString("你是谁,666")
fmt.Println(runes)
// Valid:判断byte切片是否由有效的符文组成
byv := utf8.Valid(byte)
fmt.Println(byv)
// ValidString:判断字符串切片是否由有效的符文组成
runesV := utf8.ValidString("aaafd哈哈哈")
fmt.Println(runesV)
// FullRune:判断byte切片第一个字符是否是有效符文
fullR := utf8.FullRune(byte)
fmt.Println(fullR)
// FullRuneInString:判断字符串切片第一个字符是否是有效符文
fullS := utf8.FullRuneInString(ss)
fmt.Println(fullS)
}
十一、时间常见操作 (time包)
定义:时段:一段时间,时刻:某个时间点,时区:某个地域的时间区域
1)、时段(类型为Duration,底层为int64)、时段可以为负(方向)
//当前线程休眠时段d
time.Sleep(time.Second * 1)
fmt.Println(time.Now())
//从字符串解析时段,必须使用格式:"1h10m20s"
d1, err := time.ParseDuration("1000s")
if err != nil {
panic(err.Error())
}
fmt.Println(d1)
//格式字符串(固定):年("2006"/"06")、月("Jan"/"January"/"01"/"1")、月日("2"/"-2"/"02")、年日("__2"/"002")、时("15"/"3"/"03")、分("4"/"04")、秒("5"/"05")、给定经度小数秒("05,000"/"05.000")、AM/PM("PM")、星期("Mon"/"Monday")、时区(UTC偏移量)("-0700"/"-07:00"/"-07")、时区(IOS 8601)("Z0700"/"Z07:00"/"Z07")
//按给定字符串解析时刻,如果没有时区信息,Parse会将时区解析为UTC
t2, err := time.Parse("2006-01-02 15:04:05", "2022-05-19 13:14:20")
if err != nil {
panic(err.Error())
}
fmt.Println(t2)
//返回时刻到现在经过的时段,等效于time.Now().Sub(t)
fmt.Println(time.Since(t2))
//返回从现在到某时刻需要经过的时间段,等效于t.Sub(time.Now())
fmt.Println(time.Until(t2))
//d.Hours():返回小数形式小时数、d.Minutes():返回小数形式分钟数、d.Seconds():返回小数形式秒数、d.Milliseconds():返回整数形式毫秒数、d.Microseconds():返回整数形式微秒数、d.Nanoseconds():返回整数形式纳秒数
fmt.Println(d1.Hours())
fmt.Println(d1.Minutes())
fmt.Println(d1.Seconds())
fmt.Println(d1.Milliseconds())
fmt.Println(d1.Microseconds())
fmt.Println(d1.Nanoseconds())
//以"72h3m0.5s"这类格式返回字符串,首项不足1则采用更小时间单位
fmt.Println(d1.String())
2)、时区(类型为Location,底层为time包的一个结构体)
//返回给定名称的时区l,给定名称:""、"UTC"、"Local"、"Asia/Shanghai"
l, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
panic(err.Error())
}
fmt.Println(l)
fmt.Println(l.String())
3)、月(类型为Month,底层为int,范围为1~12)
4)、星期几(类型为Weekday,底层为int,范围为0~6)
//月(类型为Month,底层为int,范围为1~12)
fmt.Println(t2.Month().String())
fmt.Println(t2.Weekday().String())
5)、时刻(类型为Time,底层为time包的一个结构体)用于解析t的格式字符串
//返回当前时刻
fmt.Println(time.Now())
//按给定格式将时刻转为字符串
fmt.Println(t2.Format("2006-01-02 15:04:05"))
//按"2022-05-20 11:59:26.0738769 +0800 CST m=+1.035755001"返回字符串
fmt.Println(t2.String())
//返回对应值的时间
fmt.Println(time.Date(2022, 06, 05, 12, 15, 10, 10, l))
6)、周期计时器(类型为Ticker,底层为time包的一个结构体)
7)、单次计时器(类型为Timer,底层为time包的一个结构体)
//周期定时器,类型setInterval、setTimeout
intChan := make(chan int)
go func() {
time.Sleep(time.Second * 1)
intChan <- 1
}()
for {
select {
case <-intChan:
fmt.Println("intChat")
case <-time.NewTicker(time.Second * 2).C:
fmt.Println(".")
}
}
十二、文件常见操作/读写 (os, io, bufio包),参考:Go语言的IO库那么多,我该怎么选?
1)、Unix文件操作权限码
权限针对不同操作者分别设置,三位八进制数字分别对应UGO,如775,为所有者和组成员有完整权限,普通访客只能读取与运行
2)、相关类型
File:File是os包定义的一个结构体,表示一个打开的文件
FileInfo:FileInfo是io/fs包定义的一个接口,该接口要求实现Mode() FileMode,IsDir() bool等方法
FileMode:
FileMode是io/fs包定义数据类型,底层为uint32,实现了FileInfo接口
Unix文件权限(ModePerm)数据类型也为FileMode,占用uint32后几位
DirEntry:
DirEntry是io/fs包定义的一个接口,该接口要求实现Name() string,IsDir() bool,Type() FileMode,Info() (FileInfo, err)方法
3)、flag:flag数据类型为int,是指定文件操作模式的开关,以按位或进行组合
*必须指定O_RDONLY, O_WRONLY, O_RDWR中一个的flag
*可选的flag
4)、文件/目录共用操作
5)、文件/目录共用操作
6)、文件操作
7)、无缓冲区读写(适合一次性读写)
//打开文件
file1 := "filesdir/1.txt"
fs1, err := os.OpenFile(file1, os.O_RDWR|os.O_CREATE, 0775)
if err != nil {
panic(err.Error())
}
fmt.Println(fs1)
fs1.Close()
//读取文件到byte切片
files3 := "filesdir/3.txt"
byte3, err := os.ReadFile(files3)
if err != nil {
panic(err.Error())
}
fmt.Println(byte3)
fmt.Println(string(byte3))
//byte切片写入文件
files4 := "filesdir/4.txt"
a := os.WriteFile(files4, []byte("abc"), 0775)
fmt.Println(a)
8)、文件读写 (io, bufio包)
io.Reader/io.Writer:io.Reader/io.Writer是io包定义的接口
io.Reader只要求实现Read(p []byte) (n int, err error)方法
io.Writer只要求实现Write(p []byte) (n int, err error)
*os.File实现了io.Reader/io.Writer接口
我们也可以根据需要,让自定义类型实现这些接口
9)、通过缓冲区读写
bufio.Reader/bufio.Writer:bufio.Reader/bufio.Writer是bufio定义的结构体,为io.Reader/io.Writer实现缓冲
10)、基于io.Reader/io.Writer的普通读写
11)、复制
//缓冲区
f5, err := os.OpenFile("filesdir/5.txt", os.O_RDWR|os.O_CREATE, 0775)
if err != nil {
panic(err.Error())
}
defer f5.Close()
//打开写入
writer := bufio.NewWriter(f5)
for i := 0; i < 3; i++ {
writer.Write([]byte(fmt.Sprintf("%v", i))) //写入缓冲区
}
writer.Flush() //缓冲区写入文件
十三、错误/日志/单元测试
1)、错误error:error是builtin包定义的接口(本文简称err),默认值为nil,一般声明在文件顶部(import后),采用驼峰命名法
自定义错误
错误处理
2)、日志 (log包)
flag:flag数据类型为int,是指定log模式的开关,以按位或进行组合
os.Exit(int):中断程序并退出,延迟函数不会被执行,0代表正常,非0代表错误
Logger 是log包定义的结构体,用于记录日日志
记录方式
3)、单元测试,参考:https://blog.csdn.net/le_17_4_6/article/details/124015323
命名规范:文件名: xxx_test.go (通常xxx为被测试文件名)
测试函数:func TestXxx(*testing.T) (通常Xxx为被测试函数名)
性能测试函数: func BenchmarkXxx(*testing.B) (通常Xxx为被测试函数名)
testing.T与testing.B:testing.T与testing.B是testing包定义的结构体
//测试函数
func TestIsAbc(log *testing.T) {
err := errors.New("Is Abc")
if IsAbc(-1) {
log.Log("Ok")
} else {
log.Error(err)
}
if IsAbc(1) {
log.Log("Ok")
} else {
log.Error(err)
}
}
测试命令(go mod下):项目名可以简写为".",下面统一为"."
测试指定包:go test ./文件夹路径 ,将自动调用所有_test.go文件中的所有TestXxx函数
测试指定文件:go test ./文件夹路径/xxx_test.go ./go/文件夹路径/xxx.go,将自动调用xxx_test.go中的所有TestXxx函数
flag:
flag可以写在 ./... 前也可以写在 ./... 之后
也运行BenchmarkXxx函数: go test -bench ./…
详细输出(verbose): go test -v ./…
即使运行成功也输出日志
规定测试时间: go test -benchtime XhXmXs ./…
规定测试次数: go test -benchtime Nx ./…
指定测试cpu数量: go test -cpu N ./…
指定测试函数: go test -run TestXxx ./…
只测试TestXxx函数
超时限制: go test -timeout XhXmXs ./…
默认限制为10min
打印测试的内存使用统计: go test -benchmem ./…
func Test_adminLogic_GenerateJwtCode(t *testing.T) {
type args struct {
req *entity.Admin
}
tests := []struct {
name string
s *adminLogic
args args
want string
}{
{
"test admin",
&adminLogic{},
args{},
"",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.s.GenerateJwtCode(tt.args.req); got != tt.want {
t.Errorf("adminLogic.GenerateJwtCode() = %v, want %v", got, tt.want)
}
})
}
}
十四、命令行编程
1)、发送命令行参数:有flag的参数列表 无flag参数列表,参数用空格分割,如果参数带有空格需要加引号,flag前需要用 "-" 或 "--" 标识
2)、以字符串形式接收所有参数:os.Args 定义在os包的字符串切片,用于记录命令行参数(第一个参数为当前可执行文件位置)
3)、接收有flag参数:直接接收: flag.Type(flag, 默认值, 注释) *Type,使用变量接收: flag.TypeVar(&Var, flag, 默认值, 注释),*支持的类型
4)、按flag调用函数:出现flag时自动调用func(str) err: flag.Func(flag, 默认字符串, fn func(str) err)
5)、解析:flag方式的参数需要解析才能使用: flag.Parse()、flag.Args() []string 返回无flag的命令行参数
//命令行
func CmdArgs() {
fmt.Printf("接收到了%v个参数\n", len(os.Args))
for i, v := range os.Args {
fmt.Printf("参数%v:%v\n", i, v)
}
//定义版本
version := flag.Bool("v", false, "版本0.0.1")
//用户名
var userName string
flag.StringVar(&userName, "u", "用户名", "用户名描述")
//匿名函数
flag.Func("f", "", func(s string) error {
fmt.Println("s=", s)
return nil
})
//解析命令
flag.Parse()
if *version {
fmt.Printf("版本:%v\n", 1)
}
//当前用户
fmt.Println("当前用户:", userName)
}
十五、builtin包/runtime包/sync包
1)、builtin包:浅拷贝:只拷贝地址,不拷贝底层内容,深拷贝:也拷贝底层内容
2)、runtime包:运行环境相关
3)、协程控制
4)、sync包
Mutex互斥锁:sync包定义的结构体,如果已经上锁,一个协程尝试再次调用上锁,则会被阻塞到解锁为止
WaitGroup:sync包定义的结构体,通过计数器来获得阻塞能力
Cond:sync包定义的结构体,一个字段为L sync.Locker,sync.Locker为sync包定义的接口,要求实现Lock()与Unlock(),也就是说sync.Mutex实现了该接口,Cond提供了同时控制多个协程阻塞的能力
Once:sync包定义的结构体,来确保一个函数只被执行一次
Map:sync包定义的结构体,并发安全的键值对
Pool:sync包定义的结构体,一个字段为New func() interface{},并发安全、动态容量、无序的value池,适合存储临时对象
RWMutex读写互斥锁:sync包定义的结构体,如果已经上锁,一个协程尝试再次调用上锁,则会被阻塞到解锁为止,为单写多读提供更加性能
//sysnc
func SysncFun() {
fmt.Println("Muxte互斥锁 WaitGroup")
var c int
var mutex sync.Mutex
var wg sync.WaitGroup
//匿名函数
abc := func(n int) {
defer wg.Done() //计数器-1
for i := 2; i < n; i++ {
if n%i == 0 {
return
}
}
mutex.Lock() //上锁
c++
mutex.Unlock() //解锁
}
for i := 2; i < 10001; i++ {
wg.Add(1) //计数器+1
go abc(i)
}
wg.Wait() //阻塞至计数器归零
fmt.Printf("\n共找到%v个素数", c)
//nCond
fmt.Println("\nCond")
cond := sync.NewCond(&mutex) //通过互锁新建Cond
for i := 1; i < 10; i++ {
go func(n int) {
cond.L.Lock()
cond.Wait() //阻塞cond.L,等待被唤醒
fmt.Println("\n协程%v被唤醒了\n", n)
cond.L.Unlock()
}(i)
}
for i := 0; i < 15; i++ {
time.Sleep(time.Millisecond * 1)
fmt.Println(".")
if i == 4 {
fmt.Println()
cond.Signal() //唤醒一个等待被唤醒协程
}
if i == 9 {
fmt.Println()
cond.Broadcast() //唤醒所有等待被唤醒协程
}
}
//Once
fmt.Println("\nOnce")
var once sync.Once
for i := 1; i < 10; i++ {
wg.Add(1)
go func() {
once.Do(func() { //执行传入的函数
fmt.Println("\n就执行一次\n")
})
wg.Done()
}()
}
wg.Wait()
//Map
fmt.Println("\nMap")
var m sync.Map
m.Store(1, 100) //存储键值对
m.Store(2, 200)
//遍历器
m.Range(func(key, value interface{}) bool {
fmt.Printf("key=%v,valye=%v\n", key, value)
return true
})
}
十六、递归/闭包
1)、递归:一个函数自己调用自己,就实现了递归,递归所调用的函数在内存中是独立的,注意要要向结束递归的方向逼近
2)、闭包:将一个函数(内层函数)和它需要重复使用的变量封装在一起(外层函数),就形成了闭包,外层函数返回的是内层函数本身,而不是内层函数的运行结果
十七、排序/sort包
1)、对常见类型进行排序
2)、自定义排序
3)、自定义查找
4)、sort.Interface:sort定义的接口,要求实现Len() int, Less(i, j int) bool, Swap(i, j int)方法
*这三个类型额外提供了Search(x) int和Sort()方法
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
type PersonSlice []Person
//Len方法
func (ps PersonSlice) Len() int {
return len(ps)
}
//Less方法
func (ps PersonSlice) Less(i, j int) bool {
return ps[i].Age > ps[j].Age
}
//Swap方法
func (ps PersonSlice) Swap(i, j int) {
ps[i], ps[j] = ps[j], ps[i]
}
//sort
func SortFun() {
//int切片升序排序
a := []int{1, 12, 3, 4}
sort.Ints(a)
fmt.Println(a)
//int切片判断是否为升序
b := sort.IntsAreSorted(a)
fmt.Println(b)
//int切片升序切片查找给定值,找不到返回适合插入值得下标
c := sort.SearchInts(a, 12)
fmt.Println(c)
//按首字母A-Z排序
d := []string{"A", "a", "c", "C", "b", "B"}
sort.Strings(d)
fmt.Println(d)
//判断字符串是否按照首字母排序
d1 := sort.StringsAreSorted(d)
fmt.Println(d1)
//升序查找值
d2 := sort.SearchStrings(d, "a")
fmt.Println(d2)
//自定义排序
e := []Person{{"小三", 18}, {"小四", 19}, {"小五", 20}}
sort.Slice(e, func(i int, j int) bool {
return e[i].Age > e[j].Age
})
fmt.Println(e)
//判断指定切片是否已经按照指定规则进行排序
e1 := sort.SliceIsSorted(e, func(i int, j int) bool {
return e[i].Age > e[j].Age
})
fmt.Println(e1)
//按照规则排序,并对相等元素保留原始相对位置
sort.SliceStable(e, func(i int, j int) bool {
return e[i].Age > e[j].Age
})
fmt.Println(e)
//有序自定义查找值
f := sort.Search(len(a), func(i int) bool {
return a[i] >= 10
})
fmt.Println(f)
//sort.Interface 排序
sort.Sort(sort.Reverse(PersonSlice(e)))
fmt.Println(e)
}
十八、工厂模式 / JSON常见操作 / TCP编程入门
1)、工厂模式:按需设置私有字段, 私有方法,封装在公有方法之内来使用,隐藏了某一方法的具体步骤
package factory
//结构体
type Person struct {
Name string `json:"name"`
age int `json:"age"`
}
//公共外部方法
func NewPerson() *Person {
return &Person{}
}
//设置
func (this *Person) SetAge(age int) {
this.age = age
}
//获取
func (this *Person) GetAge() int {
return this.age
}
//工厂类
fac := factory.NewPerson()
fac.SetAge(25)
fmt.Println(fac.GetAge())
2)、JSON
bool将编码为JSON布尔型、float, int将编码为JSON数值型、*范围为float64、string将编码为JSON字符串
为了安全的在HTML中使用, "<", ">", "&", "U+2028", "U+2029" 默认转义为 "\u003c", "\u003e", "\u0026", "\u2028", "\u2029"
数组, 切片将编码为JSON数组
*[]byte将编码为base64编码的字符串a
结构体, map将编码为JSON对象
*map中的key即使不是字符串, 也会被编码为字符串
nil将编码为null
格式化:json.Indent()、json.MarshalIndent()
检测:json.Valid()
3)、TCP编程入门
TCP中字节流对应Go中的[]byte
客户端:
服务器端:
通用:
net.Conn 是 net 包定义的一个接口:实现了 net.Conn 的了类型也实现了 io.Reader/io.Writer
//TCP客户端
func TcpCli() {
//拨号连接服务器
conn, err := net.Dial("tcp", "127.0.0.1:8090")
if err != nil {
fmt.Println("拨号连接失败:", err)
return
}
//关闭连接
defer conn.Close()
for {
//打印消息
fmt.Println("请输入要发送的内容......")
//监听输入
var msg string
fmt.Scanln(&msg)
//验证
if msg == "" {
fmt.Println("输入内容为空")
continue
}
if msg == "exit" {
fmt.Println("退出")
return
}
//写入数据
_, err := conn.Write([]byte(msg))
if err != nil {
fmt.Println("发送失败")
return
}
fmt.Println("发送成功:", msg)
}
}
//TCP服务端
func TcpServer() {
//服务端监听端口
listener, err := net.Listen("tcp", "127.0.0.1:8090")
if err != nil {
fmt.Println("监听失败:", err)
return
}
//关闭连接
defer listener.Close()
for {
fmt.Println("主进程接收数据......")
//接收一个客户端
conn, err := listener.Accept()
if err != nil {
fmt.Println("接收失败:", err)
continue
}
go func(conn net.Conn) {
fmt.Println("协程已开启")
defer conn.Close()
for {
buf := make([]byte, 4096)
n, err := conn.Read(buf)
if err == io.EOF {
fmt.Println("客户端退出了")
return
}
if err != nil {
fmt.Println("读取失败", err)
return
}
fmt.Printf("%d\n", n)
}
}(conn)
}
}
十九、LevelDB
LevelDB是一个由谷歌公司开发的跨平台嵌入式开源键值型数据库管理系统,高性能,最快的嵌入式键值存储之一,嵌入式,和我们的程序打包在一起,无需用户专门安装
1)、安装与引入
LevelDB是基于C++开发的,可以想办法通过GO调用C++,
地址:https://pkg.go.dev/github.com/syndtr/goleveldb/leveldb
命令行中:go get github.com/syndtr/goleveldb/leveldb
代码:import "github.com/syndtr/goleveldb/leveldb"
2)、基本使用
key与value的类型均为[]byte,leveldb.Batch是一个结构体,用于批量处理数据写入,batch := new(leveldb.Batch)
//LevelDb
func LevelDbFun() {
//连接数据库
db, err := leveldb.OpenFile("leveldb", nil)
if err != nil {
panic(err)
}
//关闭连接
defer db.Close()
//写入
err1 := db.Put([]byte("user-1"), []byte("copylian"), nil)
db.Put([]byte("user-2"), []byte("copylian2"), nil)
if err1 != nil {
fmt.Println("写入失败:", err1)
return
}
//删除
err2 := db.Delete([]byte("user-1"), nil)
if err2 != nil {
fmt.Println("删除失败:", err2)
return
}
//查询
value, err := db.Get([]byte("user-2"), nil)
if err != nil {
fmt.Println("查询失败:", err)
return
}
fmt.Println("user-1:", string(value))
//查询是否存在
bool, err := db.Has([]byte("user-2"), nil)
if err != nil {
fmt.Println("查询失败:", err)
return
}
fmt.Println("user-2:", bool)
}
3)、遍历
leveldb中的数据是按key升序排列的,移动迭代器返回的bool均表示是否存在这样的key
//LevelDb:批量
func LevelDbBatchFun() {
//连接数据库
db, err := leveldb.OpenFile("leveldb", nil)
if err != nil {
panic(err)
}
//关闭连接
defer db.Close()
//实例化批量
batch := new(leveldb.Batch)
for i := 0; i < 20; i++ {
//存入
batch.Put([]byte(fmt.Sprintf("user-0%d", i)), []byte(fmt.Sprintf("name-0%d", i)))
}
//写入
err1 := db.Write(batch, nil)
if err != nil {
fmt.Println("批量写入错误:", err1)
return
}
//查询
value, _ := db.Get([]byte("user-01"), nil)
fmt.Println(string(value))
//遍历
iter := db.NewIterator(&util.Range{Start: []byte("user-05"), Limit: []byte("user-10")}, nil)
for iter.Next() {
fmt.Printf("%v=%v\n", string(iter.Value()), string(iter.Key()))
}
//释放遍历
iter.Release()
//处理遍历错误
err_i := iter.Error()
if err_i != nil {
fmt.Println(err_i)
return
}
//获取指定前缀范围
iter2 := db.NewIterator(util.BytesPrefix([]byte("user-0")), nil)
for iter2.Next() {
fmt.Printf("%v=%v\n", string(iter2.Value()), string(iter2.Key()))
}
//释放遍历
iter2.Release()
//处理遍历错误
err_i2 := iter2.Error()
if err_i2 != nil {
fmt.Println(err_i2)
return
}
}
4)、事务、快照
//LevelDb:事务与快照
func LevelDbShotFun() {
//连接数据库
db, err := leveldb.OpenFile("leveldb", nil)
if err != nil {
panic(err)
}
defer db.Close()
//获取快照
shot, err := db.GetSnapshot()
if err != nil {
panic(err)
}
fmt.Println(shot)
//快照获取
val, err := shot.Get([]byte("user-01"), nil)
if err != nil {
fmt.Println(err)
}
fmt.Println(string(val))
//释放快照
shot.Release()
//开启事务
tans, err := db.OpenTransaction()
//添加
err1 := tans.Put([]byte("abcd1"), []byte("efgh1"), nil)
if err1 != nil {
fmt.Println(err1)
return
}
//回滚事务
// tans.Discard()
//提交事务
tans.Commit()
//获取数据
val1, _ := db.Get([]byte("abcd1"), nil)
fmt.Println(string(val1))
}
二十、Go-Redis:redis的golang包:https://github.com/redis/go-redis
1)、安装与引入:
Redis基础:https://blog.csdn.net/weixin_44183721/article/details/126116049
安装:go get github.com/go-redis/redis/v8
引入:import "github.com/go-redis/redis/v8"
2)、连接Redis
redis.Options是redis包定义的结构体,用于设置redis连接
redis.Client是redis包定义的结构体,代表一个Redis客户端,底层是零至多个连接组成的redis连接池,可以安全的在并发使用
3)、通用操作
context.Background:是context包定义的接口,代表一个上下文,包括有效期限、取消标识和其他跨 API 进程的值,上下文接口定义的方法可以安全的在并发使用,上下文的作用是允许任务中断:任务中断之后,处理器保存上下文,以便之后根据上下文在任务的同一位置继续执行
redis.Cmd:是redis包定义的结构体,代表一个命令
redis.Nil:是redis包定义的一个错误常量,代表redis中的nil
4)、数据类型
适用于返回单个结果的情况
适用于返回多个结果的情况
//Redis
func RedisFun() {
//连接参数
// opt := &redis.Options{
// Addr: "127.0.0.1:6379",
// Password: "duoxiang88",
// DB: 0,
// }
//解析字符串获取redis参数返回引用地址
opt, err := redis.ParseURL("redis://:duoxiang88@127.0.0.1:6379/0")
// opt, err := redis.ParseURL("redis://localhost:duoxiang88@127.0.0.1:6379/0")
if err != nil {
panic(err)
}
// fmt.Println(opt)
//连接redis,获取redis客户端
client := redis.NewClient(opt)
// fmt.Println(client)
//关闭redis
defer client.Close()
//返回一个非空的零值上下文
ctx := context.Background()
//redis执行命令
cmd := client.Do(ctx, "set", "abc1", "efg")
res, err1 := cmd.Result()
if err1 == redis.Nil {
panic(err1)
}
fmt.Println(res)
//获取
cmd1 := client.Do(ctx, "get", "abc1")
res1, err2 := cmd1.Result()
if err2 == redis.Nil {
panic(err2)
}
fmt.Println(res1)
//设置数据类型处理
cmd3 := client.Set(ctx, "b1", "b-1", 0)
res3, err3 := cmd3.Result()
if err3 == redis.Nil {
panic(err3)
}
fmt.Println(res3)
cmd3_1 := client.Get(ctx, "b1").Val() //数据类型处理
fmt.Println(cmd3_1)
//数据类型处理
client.Set(ctx, "d1", 0, 0)
client.Set(ctx, "d2", true, 0)
cmd_d, _ := client.Do(ctx, "mget", "d1", "d2").BoolSlice()
fmt.Println(cmd_d)
cmd4 := client.MGet(ctx, "d1", "d2").Val()
fmt.Println(cmd4)
}
5)、常见命令
Redis驱动为常见命令提供了函数,函数名于Redis命令名相同,可以方便的直接使用,如db.Set/db.HSet…
redis.StringCmd:是redis包定义的结构体,代表处理单个返回值的命令,为db.Get/db.HGet等函数的返回值,可以将结果处理为string/[]byte/bool/time.Time等
redis.SliceCmd:是redis包定义的结构体,代表处理多个返回值的命令,为db.MGet/db.HMGet等函数的返回值,结果为空接口切片,需要进一步处理
6)、Redis管道(Pipeline)
Redis管道允许批量执行命令;目的是提高性能
redis.Cmder:是redis包定义的接口,代表多种命令,可以类型断言回命令本身应该是的Cmd,[]redis.Cmder是pipe.Exec/db.Pipelined等函数的返回值
redis.Pipeliner:是redis包定义的接口,用于实现管道
注意Pipeline不是事务
//Redis管道(Pipeline)
func RedisPipeline() {
//连接redis,获取redis客户端
client := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
Password: "duoxiang88",
})
//上下文
ctx := context.Background()
//开启管道
pipe := client.Pipeline()
//获取
p1 := pipe.Get(ctx, "p1")
fmt.Println("p1=", p1.Val())
//批量存入管道
for i := 0; i < 10; i++ {
pipe.Set(ctx, fmt.Sprintf("p%d", i), i, 0)
}
//执行管道
_, err := pipe.Exec(ctx)
if err != nil {
panic(err)
}
fmt.Println("p1=", p1.Val())
//开启管道并执行函数
cmds, err := client.Pipelined(ctx, func(pipe redis.Pipeliner) error {
for i := 0; i < 10; i++ {
pipe.Get(ctx, fmt.Sprintf("p%d", i))
}
return nil
})
if err != nil {
panic(err)
}
for i, v := range cmds {
fmt.Printf("i=%d,v=%s\n", i, v.(*redis.StringCmd).Val())
}
}
7)、Redis事务(Transaction)
redis.TxFailedErr是redis包定义的错误,用于表示事务失败
//redis事务Transaction
func RedisTransaction() {
//连接redis,获取redis客户端
client := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
Password: "duoxiang88",
})
//上下文
ctx := context.Background()
//监视、事务
for i := 0; i < 10; i++ {
err := client.Watch(ctx, func(tx *redis.Tx) (err error) {
//事务管道
pipe := tx.TxPipeline()
//自增100
err0 := pipe.IncrBy(ctx, "p0", 100).Err()
if err0 != nil {
return
}
//自减100
err1 := pipe.DecrBy(ctx, "p1", 100).Err()
if err1 != nil {
return
}
//执行管道
_, err2 := pipe.Exec(ctx)
if err2 != nil {
return
}
return
}, "p0")
if err == nil {
fmt.Printf("事务执行成功%d次\n", i)
} else if err == redis.TxFailedErr {
fmt.Printf("事务执行失败%d次\n", i)
} else {
panic(err)
}
}
//开启管道执行给定函数
cmds, err := client.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
for i := 0; i < 10; i++ {
pipe.Get(ctx, fmt.Sprintf("p%d", i))
}
return nil
})
if err != nil {
panic(err)
}
for i, v := range cmds {
fmt.Printf("i=%d,v=%s\n", i, v.(*redis.StringCmd).Val())
}
}
8)、Redis遍历
redis.ScanCmd:是redis包定义的结构体,代表处理一个Scan的命令,为db.Scan/SScan/HScan/ZScan等函数的返回值,可以将结果处理为redis.ScanIterator等,下面简称scanCmd
redis.ScanIterator:是redis包定义的结构体,代表用于遍历一组元素的迭代器,可以安全的在并发使用下面简称iter
//Redis遍历
func RedisIterator() {
//连接redis,获取redis客户端
client := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
Password: "duoxiang88",
})
//上下文
ctx := context.Background()
//遍历
iter := client.Scan(ctx, 0, "p*", 0).Iterator()
for iter.Next(ctx) {
fmt.Printf("key = %v, val = %v\n", iter.Val(), client.Get(ctx, iter.Val()).Val())
}
if err := iter.Err(); err != nil {
panic(err)
}
fmt.Println("\n")
//Hash
client.HMSet(ctx, "myhash", "e", "e1", "f", "f1", "g", "g1")
iterH := client.HScan(ctx, "myhash", 0, "*", 0).Iterator()
for i := 0; iterH.Next(ctx); i++ {
if i%2 == 0 {
fmt.Printf("field=%s\n", iterH.Val())
} else {
fmt.Printf("value=%s\n", iterH.Val())
}
}
if err := iterH.Err(); err != nil {
panic(err)
}
}
9)、将Redis Hash扫描至Go结构体
redis.StringStringMapCmd:是redis包定义的结构体,可以将结果处理为map[string]string,为db.HGetAll等函数的返回值
type RedisHash struct {
Name string `redis:"name"`
Sex string `redis:"sex"`
Age int `redis:"age"`
}
//Redis扫描至GO结构体
func RedisGo() {
//连接redis,获取redis客户端
client := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
Password: "duoxiang88",
})
//上下文
ctx := context.Background()
//实例化结构体
var rh1 = RedisHash{
Name: "copylian",
Sex: "男",
Age: 25,
}
//执行调用
cmds, err := client.Pipelined(ctx, func(pipe redis.Pipeliner) error {
pipe.HSet(ctx, "r1", "name", rh1.Name)
pipe.HSet(ctx, "r1", "sex", rh1.Sex)
pipe.HSet(ctx, "r1", "age", rh1.Age)
return nil
})
if err != nil {
panic(err)
}
fmt.Println(cmds)
var rh2 RedisHash
//获取
client.HGetAll(ctx, "r1").Scan(&rh2)
fmt.Printf("rh2=%+v", rh2)
}
参考资料&教程
1、go get 快速导入GitHub中的包:https://www.jianshu.com/p/16aceb6369b6
2、goland编写go语言导入自定义包出现: package xxx is not in GOROOT (/xxx/xxx) 的解决方案:https://blog.csdn.net/qq_27184497/article/details/122160400
3、Vscode中Golang引入自定义包报错 package xxx is not in GOROOT:https://blog.csdn.net/qq_40209780/article/details/123133467
4、GO111MODULE的设置(及GOPROXY):https://www.cnblogs.com/pu369/p/12068645.html
5、关于go反射中的NumMethod方法的一点发现:https://www.bilibili.com/read/cv8985976/
6、VS Code 安装go插件失败分析及解决方案:https://blog.csdn.net/qq_36564503/article/details/124509832
7、VSCode: Could not import Golang package:https://stackoverflow.com/questions/58518588/vscode-could-not-import-golang-package
8、vscode 远程开发 go 提示 You are outside of a module and outside of $GOPATH/src:https://www.jianshu.com/p/089efae0bdd3
基础教程:
视频1:https://www.bilibili.com/video/BV1gf4y1r79E
笔记1:https://www.yuque.com/aceld/mo95lb/zwukev
视频2:https://www.bilibili.com/video/BV1s341147US
笔记2:https://www.bilibili.com/read/readlist/rl496566
进阶教程:
文明上网理性发言!
已完结!