Golang学习笔记
你的 Go 安装目录($GOROOT)
的文件夹结构应该如下所示:
1 | /bin:包含可执行文件,如:编译器,Go 工具 |
数据类型
1 | 这些浮点数类型的取值范围可以从很微小到很巨大。浮点数取值范围的极限值可以在 math 包中找到: |
变量
1 | 1. 变量如果只声明没有赋值,则默认值为0,bool型是false,字符型是"" |
常量
1 | 1. 常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。 |
iota1
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 const(
a=iota //a=0
b //b=1
c //c=2
d //d=3
)
如果想忽略中间一个量则可以使用
const(
a=iota //a=0
b //b=1
c //c=2
d //d=3
_
e //e=5
)
乘法实例
const(
a=2*iota //2*0
b //2*1
c //2*2
d //2*3
e //2*4
f="test" //注意这里iota变成了5
g=2*iota //2*6
)
fmt.Println(a,b,c,d,e,g)
iota 只是在同一个 const 常量组内递增,每当有新的 const 关键字时,iota 计数会重新开始。
const (
i = iota
j = iota
x = iota
)
const xx = iota
const yy = iota
func main(){
println(i, j, x, xx, yy)
}
// 输出是 0 1 2 0 0
运算符
1 | + 相加 A + B 输出结果 30 |
条件
case1
2
3
41. 注意多个case是不能出现相同的值的,否则会报错
2. 可以在一个 case 中包含多个表达式,每个表达式用逗号分隔。
3. 没有表达式的 switch,则相当于 switch true,这种情况下会将每一个 case 的表达式的求值结果与 true 做比较,如果相等,则执行相应的代码。
4. fallthrough语句用于标明执行完当前 case 语句之后按顺序执行下一个case 语句。
类型转换
1 | 1. 在必要以及可行的情况下,一个类型的值可以被转换成另一种类型的值。由于Go语言不存在隐式类型转换,因此所有的类型转换都必须显式的声明 |
指针
1 | 1. new() 函数可以创建一个对应类型的指针,创建过程会分配内存,被创建的指针指向默认值。 |
Go语言type关键字(类型别名)
1 | type NewType = Type //定义别名 |
结构体
实例化的方法
用var实例化
1
1. 结构体本身也是一种类型,可以用var实例化:var name T
创建指针类型的结构体
1 | 语法:ins := new(T) |
- 取结构体的地址实例化
1
2在Go语言中,对结构体进行&取地址操作时,视为对该类型进行一次 new 的实例化操作,取地址格式为:ins := &T{}
带传值的:ins:=&person{name: name,age: age,nickname: nickname}
成员赋值
结构体实例化后字段的默认值是字段类型的默认值,例如 ,数值为 0、字符串为 “”(空字符串)、布尔为 false、指针为 nil 等。
使用多个值的列表初始化结构体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17必须初始化结构体的所有字段。
每一个初始值的填充顺序必须与字段在结构体中的声明顺序一致。
键值对与值列表的初始化形式不能混用。
type Address struct {
Province string
City string
ZipCode int
PhoneNumber string
}
addr := Address{
"四川",
"成都",
610000,
"0",
}
fmt.Println(addr)
1 | //指针结构体,成员可以直接使用.号访问 |
接收器
接收器的格式如下:1
2
3func (接收器变量 接收器类型) 方法名(参数列表) (返回参数) {
函数体
}
指针类型的接收器1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21type person struct{
name string
age uint
nickname string
}
func main(){
lisi:=&person{}
lisi.setValue("lisi")
fmt.Println(lisi.getValue())
}
func (p *person)setValue(v string){
p.name=v
}
func (p *person)getValue() string{
return p.name
}
Go语言类型内嵌和结构体内嵌
- 在一个结构体中对于每一种数据类型只能有一个匿名字段。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17func main() {
type inn struct{
name string
}
type test struct{
nickname string
name string
int
inn
}
abc:=&test{}
abc.nickname="zhangsan"
abc.inn.name="lisi" //另外一种访问方式:abc.name="lisi",前提test这个结构体没有name这个成员
abc.int=1 //直接使用abc.int访问匿名变量,不过每一种类型的匿名变量只能有一个
fmt.Println(abc.inn.name) //也可以abc.name,前提test这个结构体没有name这个成员
}
结构体内嵌模拟类的继承
1 | type Walkable struct{} |
初始化内嵌结构体
例子一:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15type computer struct{
cpu string
RAM uint
ROM uint
}
func main() {
lianxiang:=computer{
cpu:"Intel",
RAM:8,
ROM:1000, //注意最后要有逗号,不然就会报错
}
fmt.Println(lianxiang)
}
例子二:
有时考虑编写代码的便利性,会将结构体直接定义在嵌入的结构体中。也就是说,结构体的定义不会被外部引用到。在初始化这个被嵌入的结构体时,就需要再次声明结构才能赋予数据。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24type computer struct{
cpu string
RAM uint
ROM struct{
size uint
brand string
}
}
func main() {
lianxiang:=computer{
cpu: "Intel",
RAM: 8,
ROM: struct{
size uint
brand string
}{
size: 1000,
brand: "Kingston",
}, //注意这里要有逗号,不然就会报错
}
fmt.Println(lianxiang)
}
map
1 | /* 声明变量,默认 map 是 nil */ |
sync.Map
Go中的 map 在并发情况下,只读是线程安全的,同时读写是线程不安全的。所以就有了sync.Map
1 | 1. sync.Map 和 map 不同,不是以语言原生形态提供,而是在 sync 包下的特殊结构。 |
1 | package main |
数组
如果忽略 [] 中的数字不设置数组大小,Go 语言会根据元素的个数来设置数组的大小:
1 | a:=[...]int {1,1,1,1} //最终长度为4 |
切片
切片并不存储任何元素而只是对现有数组的引用。
1 | 从数组或切片生成新的切片拥有如下特性: |
append1
2
3append只能用于操作切片,不能用于操作数组
当新元素通过调用 append 函数追加到切片末尾时,如果超出了容量,append 内部会创建一个新的数组。并将原有数组的元素被拷贝给这个新的数组,最后返回建立在这个新数组上的切片。这个新切片的容量是旧切片的二倍
append的返回值是添加后的切片
1 | var a= []int{1,2,3} |
list
不是go内置的,要导入包container/list
1 | 1. list 的初始化有两种方法:分别是使用 New() 函数和 var 关键字声明,两种方法的初始化效果都是一致的。 |
函数1
2
3
4
51. mylist.Len() 返回链表的长度
2. mylist.init() 初始化链表,即清空链表
3. PushBack在列表mylist的后面插入一个值为v的新元素e并返回e。
4. PushFront在列表mylist的前面插入一个值为v的新元素e并返回e。
5. Next返回下一个list元素或nil
例子1
2
3
4
5
6
7
8
9
10
11
12
13l := list.New()
// 尾部添加
l.PushBack("canon")
// 头部添加
l.PushFront(67)
// 尾部添加后保存元素句柄
element := l.PushBack("fist")
// 在fist之后添加high
l.InsertAfter("high", element)
// 在fist之前添加noon
l.InsertBefore("noon", element)
// 使用
l.Remove(element) //删除fist这个元素
遍历所有链表1
2
3for i:=mylist.Front();i!=nil;i=i.Next(){
fmt.Println(i.Value)
}
nil
nil 是 map、slice、pointer、channel、func、interface 的零值
if
- if左边的括号必须要和if在同一行
if 还有一种特殊的写法,可以在 if 表达式之前添加一个执行语句,再根据变量值进行判断,代码如下:1
2
3
4if err := Connect(); err != nil {
fmt.Println(err)
return
}
for
break退出指定循环,一个标签只能对应一个循环1
2
3
4
5
6
7
8out:
for i:=1;;i++{
if i>9{
break out
}else{
fmt.Println("test")
}
}
continue 语句可以结束当前循环,开始下一次的循环迭代过程,仅限在 for 循环内使用,在 continue 语句后添加标签时,表示开始标签对应的循环
1 |
|
函数
在函数中,实参通过值传递的方式进行传递,因此函数的形参是实参的拷贝,对形参进行修改不会影响实参,但是,如果实参包括引用类型,如指针、slice(切片)、map、function、channel 等类型,实参可能会由于函数的间接引用被修改。
在Go语言中,函数也是一种类型,可以和其他类型一样保存在变量中,下面的代码定义了一个函数变量 f,并将一个函数名为 echo() 的函数赋给函数变量 f,这样调用函数变量 f 时,实际调用的就是 echo() 函数,代码如下:
1
2
3
4
5
6
7
8
9
10func main(){
var f func(string)
f=echo
f("test")
}
func echo(s string) {
a:=s
fmt.Println(a)
}宕机
发生宕机时后面的代码都不会执行,注意一点就是defer如果在发生宕机前面的话,还是正常执行的1
2
3
4
5
6
7
8
9
10func main(){
defer fmt.Println("a")
defer fmt.Println("b")
panic("Done!")
}
输出:
b
a
panic: Done!
- 宕机恢复recover
一旦发生宕机,其后的代码是不会执行的,但是会调用位于panic代码所在的哪一行之前的defer延迟函数,所以说这个特性就决定recover应该用在defer函数中,否则一旦发生宕机,除了defer延迟函数中的语句还能执行外,其他的语句都是不能执行的。
1 | func main() { |
接口
每个接口类型由数个方法组成。接口的形式代码如下:1
2
3
4
5
6接口声明的格式
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2
…
}
对各个部分的说明:
- 接口类型名:使用 type 将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加 er,如有写操作的接口叫 Writer,有字符串功能的接口叫 Stringer,有关闭功能的接口叫 Closer 等。
- 方法名:当方法名首字母是大写时,且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以被忽略,例如:1
2
3type writer interface{
Write([]byte) error
}
包
package语句
- 要想使用自定义的包中的函数、变量、结构体,则首字母必须要大写(即public),如果首字母是小写则外部包访问(即private)
导入自定义包
- import后面是文件夹名称
- 文件夹名称和package名称不一定要相同
- 调用自定义包中的函数使用
packeage.func()
来调用 - 自定义包的调用和文件名没有关系,只与文件中的
package
语句有关,go会自动查找import文件夹里面的所有文件,然后查找所有package,不同的go文件可以有相同的package名称
import语句
导入匿名包
如果只希望导入包,而不使用任何包内的结构和类型,也不调用包内的任何函数时,可以使用匿名导入包
使用下划线导入一个匿名包格式如下:1
import _ "path/to/package"
注意:匿名包也会和其它包一样,也会被编译到可执行文件中,同时匿名包中的init函数也会被执行
io
ReadByte()
方法
ReadByte()
方法的功能是读取并返回一个字节。如果没有字节可读,则返回错误信息。该方法原型声明如下:
1 | func (b *Reader) ReadByte() (c byte,err error) |
1 | func main() { |
ReadBytes()
方法
ReadBytes() 方法的功能是 ReadBytes 读取数据直到遇到第一个分隔符“delim”,并返回读取的字节序列(包括“delim”)。如果 ReadBytes 在读到第一个“delim”之前出错,它返回已读取的数据和那个错误(通常是 io.EOF)。只有当返回的数据不以“delim”结尾时,返回的 err 才不为空值。该方法原型声明如下:
1 | func (b *Reader) ReadBytes(delim byte) (line []byte, err error) |
1 | arr:=[]byte("Hey,guy") |
接口
接口被实现的条件一:接口的方法与实现接口的类型方法格式一致
在类型中添加与接口签名一致的方法就可以实现该方法。签名包括方法中的名称、参数列表、返回参数列表。也就是说,只要实现接口类型中的方法的名称、参数列表、返回参数列表中的任意一项与接口要实现的方法不一致,那么接口的这个方法就不会被实现。
1 | type Print interface{ |
如果结构体中没有实现接口中的所有方法,那么就会报错。
接口被实现的条件二:接口中所有方法均被实现
当一个接口中有多个方法时,只有这些方法都被实现了,接口才能被正确编译并使用。
如果我们在接口中新添加一个方法IsPrint()
,如果结构体中没有实现,那么也会编译不通过
多个类型可以实现相同的接口
1 | package main |
goland的常见标准库
strings
HasPrefix
判断字符串s是否以prefix开头:
函数签名1
strings.HasPrefix(s, prefix string) bool
HasSuffix
判断字符串 s 是否以 suffix 结尾:
函数签名1
strings.HasSuffix(s, suffix string) bool
1 | func main() { |
Contains
字符串包含关系
功能:字符串s中是否包含substr,返回bool值
1 | func Contains(s, substr string) bool |
1 | fmt.Println(strings.Contains("this my world!","this")) |
Join
(拼接slice到字符串,不能是数组)
Join 用于将元素类型为 string 的 slice 使用分割符号来拼接组成一个字符串
函数签名1
strings.Join(sl []string, sep string) string
例子1
2
3
4
5
6
7arr:=[]string {"abc","def"}
fmt.Println(strings.Join(arr,""))
fmt.Println(strings.Join(arr,","))
output:
abcdef
abc,def
Index
在字符串s中查找sep所在的位置,返回位置值,找不到返回-1
语法:1
strings.Index(s, str string) int
1 | fmt.Println(strings.Index("this is my world","is")) |
LastIndex
LastIndex 返回字符串 str 在字符串 s 中最后出现位置的索引(str 的第一个字符的索引),-1 表示字符串 s 不包含字符串 str:
1 | strings.LastIndex(s, str string) int |
1 | fmt.Println(strings.LastIndex("this is my world","is")) |
如果需要查询非 ASCII 编码的字符在父字符串中的位置,建议使用以下函数来对字符进行定位:1
strings.IndexRune(s string, r rune) int
1 | str:="this is my world" |
Count
Count 用于计算字符串 str 在字符串 s 中出现的非重叠次数:
1 | strings.Count(s, str string) int |
例子1
2
3
4
5str:="this is my world"
fmt.Println(strings.Count(str,"is"))
output:
2
Repeat
重复s字符串count次,并返回一个新的字符串
1 | func Repeat(s string, count int) string |
1 | str:="this is my world" |
Replace (字符串替换)
在s字符串中,把old字符串替换为new字符串,n表示替换的次数,小于0表示全部替换
注意:替换的次数必须有,不然就会报错1
func Replace(s, old, new string, n int) string
例子1
2str:="this is my world"
fmt.Println(strings.Replace(str,"is","are",2))
time
时间戳1
2now:=time.Now()
fmt.Println(now.Unix())
延时1
2
3time.Sleep(3 *time.Second) //延时3秒
time.Sleep(3 *time.Minute) //延时3分钟
time.Sleep(3 *time.Hour) //延时3小时
fmt
Printf
整数型1
2
3
4
5
6
7
8%b 表示为二进制
%c 该值对应的unicode码值
%d 表示为十进制
%o 表示为八进制
%q 该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义表示
%x 表示为十六进制,使用a-f
%X 表示为十六进制,使用A-F
%U 表示为Unicode格式:U+1234,等价于"U+%04X"
math/big
主要用于大数运算
1 | //设置一个大于Int64的10进制整数 |
1 | bigint := new(big.Int) |
os包
1 | fmt.Println(os.Getegid()) // |
os/exec
执行系统命令1
2
3
4
5
6
7
8
9func main() {
c:=exec.Command("ipconfig")
cmd,err:=c.CombinedOutput()
if err!=nil {
fmt.Printf("%v",err)
}else{
fmt.Printf("%s",string(cmd))
}
}
log
1 | package main |
自定义Logger类型,log.Logger提供了一个New方法用来创建对象:1
func New(out io.Writer, prefix string, flag int) *Logger
该函数一共有三个参数:
- 输出位置out,是一个io.Writer对象,该对象可以是一个文件也可以是实现了该接口的对象。通常我们可以用这个来指定日志输出到哪个文件。
- prefix 我们在前面已经看到,就是在日志内容前面的东西。我们可以将其置为 “[Info]” 、 “[Warning]”等来帮助区分日志级别。
- flags 是一个选项,显示日志开头的东西,可选的值有:
1
2
3
4
5
6Ldate = 1 << iota // 形如 2009/01/23 的日期
Ltime // 形如 01:23:23 的时间
Lmicroseconds // 形如 01:23:23.123123 的时间
Llongfile // 全路径文件名和行号: /a/b/c/d.go:23
Lshortfile // 文件名和行号: d.go:23
LstdFlags = Ldate | Ltime // 日期和时间
设置前缀、输出格式1
2
3
4
5
6
7
8
9
10
11package main
import (
"log"
"os"
)
func main() {
// var out io.Writer
debugger:=log.New(os.Stdout,"[info]\t",log.Llongfile)
debugger.Println("Gooood")
debugger.Println("Loooog")
}
regexp
Golang的正则提供16种正则匹配方法1
Find(All)?(String)?(Submatch)?(Index)?
MustCompile
和Compile
区别1
2
3MustCompile 的作用和 Compile 一样
不同的是,当正则表达式 str 不合法时,MustCompile 会抛出异常
而 Compile 仅返回一个 error 值
CompilePOSIX和 Compile的区别1
2
3
4
5CompilePOSIX 的作用和 Compile 一样
不同的是,CompilePOSIX 使用 POSIX 语法,
同时,它采用最左最长方式搜索,
而 Compile 采用最左最短方式搜索
POSIX 语法不支持 Perl 的语法格式:\d、\D、\s、\S、\w、\W
Match
func Match(pattern string, b []byte) (matched bool, err error)
,
1 | search := "baidu:https://www.baidu.com google:https://www.google.com" |
MatchReader
func MatchReader(pattern string, r io.RuneReader) (matched bool, err error)
,r:要在其中进行查找的 RuneReader 接口
1 | search := bytes.NewReader([]byte("baidu:https://www.baidu.com google:https://www.google.com")) |
MatchString
返回是否匹配到结果,true或者false
1 | search := "baidu:https://www.baidu.com google:https://www.google.com" |
Find
func (re *Regexp) Find(b []byte) []byte
,查找byte数组,并返回第一个匹配的内容
1 | search := "baidu:https://www.baidu.com google:https://www.google.com" |
FindAll
func (re *Regexp) FindAll(b []byte, n int) [][]byte
,查找前n个匹配项,如果n<0,则查找所有匹配项
1 | search := "baidu:https://www.baidu.com google:https://www.google.com" |
FindString
func (re *Regexp) FindString(s string) string
,查找字符串,并返回第一个找到的结果
1 | search := "baidu:https://www.baidu.com google:https://www.google.com" |
FindAllString
func (re *Regexp) FindAllString(s string, n int) []string
,查找前n个匹配项,如果n<0,则查找所有匹配项
1 | func main() { |
FindAllStringIndex
func (re *Regexp) FindAllStringIndex(s string, n int) [][]int
,返回匹配到的字符串的位置,[[起始位置, 结束位置], [起始位置, 结束位置], …]
1 | search := "baidu:https://www.baidu.com google:https://www.google.com" |
FindStringSubmatch
func (re *Regexp) FindStringSubmatch(s string) []string
,匹配子组的内容,第一个返回的内容是匹配到的整一个字符串。{完整匹配项, 子匹配项, 子匹配项, …}
1 | search := "baidu:https://www.baidu.com google:https://www.google.com" |
ReplaceAllString
1 | search := "baidu:https://www.baidu.com google:https://www.google.com" |
ReplaceAllLiteralString
func (re *Regexp) ReplaceAllLiteralString(src, repl string) string
, 如果 repl 中有”分组引用符”($1、$name),则将“分组引用符”当普通字符处理
1 | search := "baidu:https://www.baidu.com google:https://www.google.com" |
Split
func (re *Regexp) Split(s string, n int) []string
最多分割出 n 个子串,第 n 个子串不再进行分割 如果 n < 0,则分割所有子串 返回分割后的子串列表
1 | search := "baidu:https://www.baidu.com google:https://www.google.com" |
String
返回 re 中的“正则表达式”字符串
1 | pattern := `(http|https)?://[a-zA-Z0-9\.]+\.(com|net|cn|gov|edu)+` |
encoding
encoding/hex
hex
1 | name:=[]byte("test") |
encoding/base64
1 | //base64编码 |
crypto
crypto/md5
1 | //MD5 |
crypto/sha256
1 | data := []byte("test") |
crypto/sha512
1 | data := []byte("test") |
crypto/rand
rand.Reader
ReadFull从rand.Reader精确地读取len(b)字节数据填充进b,rand.Reader是一个全局、共享的密码用强随机数生成器
1 | b:=make([]byte,64) |
Prime
Prime(rand io.Reader, bits int) (p *big.Int, err error)
,生成一个n bit的素数,如果n<2,则会返回一个error
1 | p,_:=rand.Prime(rand.Reader,2048) //生成一个2048bit的素数 |
rand.Int
Int(rand io.Reader, max *big.Int) (n *big.Int, err error)
,取一个[0,max)的整数
1 | max:=big.NewInt(math.MaxInt64) |
crypto/aes
golang实现aes加密解密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
59package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"fmt"
"io"
)
func padding(data []byte, BlackSize int) []byte {
PadLen := BlackSize - len(data)%BlackSize
PadData := bytes.Repeat([]byte{byte(PadLen)}, PadLen)
return append(data, PadData...)
}
func unpadding(data []byte) []byte {
n := len(data)
UnpadLen := int(data[n-1])
UnpadData := data[:n-UnpadLen]
return UnpadData
}
func DecryptoAES(data []byte, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
Decrypter := cipher.NewCBCDecrypter(block, key)
// var DecryptData []byte
DecryptData := make([]byte, len(data))
Decrypter.CryptBlocks(DecryptData, data)
DecryptData = unpadding(DecryptData)
return DecryptData, nil
}
func EncryptoAES(data []byte, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
paddata := padding(data, block.BlockSize())
Encrypter := cipher.NewCBCEncrypter(block, key)
EncryptData := make([]byte, len(paddata))
Encrypter.CryptBlocks(EncryptData, paddata)
return EncryptData, nil
}
func main() {
data := []byte("Why not go far without dreams")
key := make([]byte, 16)
io.ReadFull(rand.Reader, key)
fmt.Println(key)
endata, _ := EncryptoAES(data, key)
fmt.Println(string(endata))
dedata, _ := DecryptoAES(endata, key)
fmt.Println(string(dedata))
}
crypto/des
crypto/des
包中实现了des加密和三重des加密
des加密
和aes差不多,就改一下方法就行了
1 | package main |
3des1
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
58package main
import (
"bytes"
"crypto/des"
"crypto/cipher"
"crypto/rand"
"fmt"
"io"
)
func padding(data []byte, BlackSize int) []byte {
PadLen := BlackSize - len(data)%BlackSize
PadData := bytes.Repeat([]byte{byte(PadLen)}, PadLen)
return append(data, PadData...)
}
func unpadding(data []byte) []byte {
n := len(data)
UnpadLen := int(data[n-1])
UnpadData := data[:n-UnpadLen]
return UnpadData
}
func DecryptoTripleDES(data []byte, key []byte) ([]byte, error) {
block, err := des.NewTripleDESCipher(key)
if err != nil {
return nil, err
}
Decrypter := cipher.NewCBCDecrypter(block, key[:8])
DecryptData := make([]byte, len(data))
Decrypter.CryptBlocks(DecryptData, data)
DecryptData = unpadding(DecryptData)
return DecryptData, nil
}
func EncryptoTripleDES(data []byte, key []byte) ([]byte, error) {
block, err := des.NewTripleDESCipher(key)
if err != nil {
return nil, err
}
paddata := padding(data, block.BlockSize())
Encrypter := cipher.NewCBCEncrypter(block, key[:8]) //vi的值必须为8,不然就会panic
EncryptData := make([]byte, len(paddata))
Encrypter.CryptBlocks(EncryptData, paddata)
return EncryptData, nil
}
func main() {
data := []byte("Why not go far without dreams")
key := make([]byte, 24) // key的长度必须为24
io.ReadFull(rand.Reader, key)
fmt.Println(key)
endata, _ := EncryptoTripleDES(data, key)
fmt.Println(string(endata))
dedata, _ := DecryptoTripleDES(endata, key)
fmt.Println(string(dedata))
}
crypto/hmac
HMAC是使用密钥对消息进行签名的加密散列
1 | //用hmac对hash值进行签名 |
hash
hash/crc32
1 | data:=crc32.ChecksumIEEE([]byte("crc32")) //压缩包的是采用CRC-32-IEEE 802.3标准 |
reflect
注意:go语言里面struct里面变量如果大写开头则是public,如果是小写开头则是private的,private的时候通过反射不能获取其值,否则会出现如下panic1
panic: reflect.Value.Interface: cannot return value obtained from unexported field or method
从实例到 Value
通过实例获取 Value 对象,直接使用 reflect.ValueOf() 函数。例如:1
func ValueOf(i interface {}) Value
从实例到 Type
通过实例获取反射对象的 Type,直接使用 reflect.TypeOf() 函数。例如:1
func TypeOf(i interface{}) Type
从Value到实例
1 | //该方法最通用,用来将 Value 转换为空接口,该空接口内部存放具体类型实例 |
从 Value 到 Type
从反射对象 Value 到 Type 可以直接调用 Type 的方法,因为 Value 内部存放着到 Type 类型的指针。例如:1
func (v Value) Type() Type
从 Value 的指针到值
1 | 从一个指针类型的 Value 获得值类型 Value 有两种方法,示例如下。 |
Value 值的可修改性
Value 值的修改涉及如下两个方法:
//通过 CanSet 判断是否能修改1
func (v Value ) CanSet() bool
//通过 Set 进行修改1
func (v Value ) Set(x Value)
实例
实例11
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16package main
import (
"fmt"
"reflect"
)
func main() {
var f float64 = 3.1
v := reflect.ValueOf(&f)
fmt.Println("Canset: ",v.CanSet())
fv:=v.Elem()
fmt.Println("Canset: ",fv.CanSet())
fv.SetFloat(3.14)
fmt.Println(f)
}
实例21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22package main
import (
"fmt"
"reflect"
)
type person struct {
Name string
Age uint16
}
func main() {
zhangsan := person{"zhangsan", 20}
v := reflect.ValueOf(&zhangsan).Elem()
t := v.Type()
for i := 0; i < v.NumField(); i++ {
f := v.Field(i)
tt := t.Field(i)
fmt.Println(tt.Name, f.Interface(), tt.Type)
}
}
实例3,通过反射修改结构体的值:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20package main
import (
"fmt"
"reflect"
)
type person struct {
Name string
Age uint16
}
func main() {
zhangsan := person{"zhangsan", 20}
v := reflect.ValueOf(&zhangsan).Elem()
name:="qiyou"
n:=reflect.ValueOf(name)
v.FieldByName("Name").Set(n)
fmt.Println(v.Field(0).Interface())
}
Name和Kind的区别
Go 程序中的类型(Type)指的是系统原生数据类型,如 int、string、bool、float32 等类型,以及使用 type 关键字定义的类型,这些类型的名称就是其类型本身的名称(Name)。例如使用 type A struct{} 定义结构体时,A 就是 struct{} 的类型。
1 | package main |
通过反射获取指针指向的元素类型
Go语言程序中对指针获取反射对象时,可以通过reflect.Elem()
方法获取这个指针指向的元素类型。这个获取过程被称为取元素,等效于对指针类型变量做了一个*操作
1 | package main |
结构体标签
结构体标签由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。键值对之间使用一个空格分隔。
从结构体标签中获取值
StructTag 拥有一些方法,可以进行 Tag 信息的解析和提取,如下所示:1
func(tag StructTag)Get(key string)string
根据 Tag 中的键获取对应的值,例如key1:"value1"key2:"value2"
的 Tag 中,可以传入“key1”获得“value1”。1
func(tag StructTag)Lookup(key string)(value string,ok bool)
根据 Tag 中的键,查询值是否存在
1 | package main |
注意一点就是:key和value之间不能有任何空格,即上面例子中name:”qiyou”之间不能有任何空格,如果有空格则会输出空,不会报错
通过反射获取值信息
Go语言中,使用reflect.ValueOf()
函数获得值的反射值对象(reflect.Value
)。书写格式如下:1
value := reflect.ValueOf(rawValue)
reflect.ValueOf
返回reflect.Value
类型,包含有 rawValue
的值信息。reflect.Value
与原值间可以通过值包装和值获取互相转化。reflect.Value 是一些反射操作的重要类型,如反射调用函数。
从反射值对象(reflect.Value)
中获取值的方法
可以通过下面几种方法从反射值对象 reflect.Value
中获取原值,如下表所示。
反射值获取原始值的方法1
2
3
4
5
6
7Interface() interface {} 将值以 interface{} 类型返回,可以通过类型断言转换为指定类型
Int() int64 将值以 int 类型返回,所有有符号整型均可以此方式返回
Uint() uint64 将值以 uint 类型返回,所有无符号整型均可以此方式返回
Float() float64 将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回
Bool() bool 将值以 bool 类型返回
Bytes() []bytes 将值以字节数组 []bytes 类型返回
String() string 将值以字符串类型返回
例子:1
2
3
4
5
6
7
8
9
10
11
12
13package main
import (
"fmt"
"reflect"
)
func main() {
var i int=12
t:=reflect.ValueOf(i)
fmt.Println(t.Interface())
fmt.Println(t.Int())
}
通过反射访问结构体成员的值
反射值对象(reflect.Value)提供对结构体访问的方法,通过这些方法可以完成对结构体任意值的访问,如下表所示。
反射值对象的成员访问方法1
2
3
4
5Field(i int) Value 根据索引,返回索引对应的结构体成员字段的反射值对象。当值不是结构体或索引超界时发生宕机
NumField() int 返回结构体成员字段数量。当值不是结构体或索引超界时发生宕机
FieldByName(name string) Value 根据给定字符串返回字符串对应的结构体字段。没有找到时返回零值,当值不是结构体或索引超界时发生宕机
FieldByIndex(index []int) Value 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的值。 没有找到时返回零值,当值不是结构体或索引超界时发生宕机
FieldByNameFunc(match func(string) bool) Value 根据匹配函数匹配需要的字段。找到时返回零值,当值不是结构体或索引超界时发生宕机
1 | package main |
判断反射值的空和有效性
IsNil()和IsValid()
通过反射修改变量的值
1 | 使用 reflect.Value 取元素、取地址及修改值的属性方法请参考下表。 |
我们可以通过调用 reflect.ValueOf(&x).Elem()
,来获取任意变量x对应的可取地址的 Value。
1 | package main |
值修改相关方法
使用 reflect.Value
修改值的相关方法如下表所示。
反射值对象修改值的方法1
2
3
4
5
6
7
8Set(x Value) 将值设置为传入的反射值对象的值
Setlnt(x int64) 使用 int64 设置值。当值的类型不是 int、int8、int16、 int32、int64 时会发生宕机
SetUint(x uint64) 使用 uint64 设置值。当值的类型不是 uint、uint8、uint16、uint32、uint64 时会发生宕机
SetFloat(x float64) 使用 float64 设置值。当值的类型不是 float32、float64 时会发生宕机
SetBool(x bool) 使用 bool 设置值。当值的类型不是 bod 时会发生宕机
SetBytes(x []byte) 设置字节数组 []bytes值。当值的类型不是 []byte 时会发生宕机
SetString(x string) 设置字符串值。当值的类型不是 string 时会发生宕机
以上方法,在 reflect.Value 的 CanSet 返回 false 仍然修改值时会发生宕机。
通过反射调用函数
1 | package main |
并发
- 当main函数返回时,所有的
goroutine
都会退出,然后程序就退出 main函数是不会等待
goroutine
执行完的,比如如下代码没有输出A,因为还没有执行到匿名函数的goroutine
main函数就已经结束了1
2
3
4
5
6
7
8
9
10
11
12
13
14package main
import(
"fmt"
"time"
)
func main() {
go func(){
fmt.Println("A")
}()
fmt.Println("B")
fmt.Println("Done")
}不同的
goroutine
是不会相会影响的,不如如下代码,第一个匿名函数中的sleep是不会影响第二匿名函数的goroutine
的1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25package main
import(
"fmt"
"time"
)
func main() {
go func(){
for i:=0;i<=10;i++{
fmt.Println("A")
time.Sleep(200 *time.Millisecond)
}
}()
go func(){
for i:=0;i<=10;i++{
fmt.Println("C")
}
}()
fmt.Println("B")
time.Sleep(1 *time.Second)
fmt.Println("Done")
}要注意的一点是,如果主
goroutine
一直阻塞的话,会报错,但是其它goroutine
是没有影响的,比如如下代码,最后是没有输入Die
的,说明这个goroutine
被一直阻塞着,但是对整个程序来说没有影响,反过来,如果是主goroutine
阻塞了,没有接收或者发送给其它goroitine
,那么就会报错:fatal error: all goroutines are asleep - deadlock!
,主goroutine
等一个永远都不会接收或者发送的数据,那么程序就会一直等下去,显然这是不允许的,所以就会报错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
27package main
import (
"fmt"
"time"
)
var ch chan int = make(chan int)
func test() {
for data := range ch {
fmt.Println(data)
}
fmt.Println("Die")
}
func main() {
go test()
for i := 0; i <= 9; i++ {
ch <- i
}
time.Sleep(1 * time.Second)
data := 10
if data == 10 {
fmt.Println("Done")
}
}
带缓冲通道的阻塞条件
带缓冲通道在很多特性上和无缓冲通道是类似的。无缓冲通道可以看作是长度永远为 0 的带缓冲通道。因此根据这个特性,带缓冲通道在下面列举的情况下依然会发生阻塞:1
2带缓冲通道被填满时,尝试再次发送数据时发生阻塞。
带缓冲通道为空时,尝试接收数据时发生阻塞。
1 | package main |
通道的超时机制
配合select机制
如果有一个或多个IO操作可以完成,则Go运行时系统会随机的选择一个执行,否则的话,如果有default分支,则执行default分支语句,如果连default都没有,则select语句会一直阻塞,直到至少有一个IO操作可以进行.
1 | package main |
1). 如果我们把上面的default注释掉的话,会怎么样呢,会输出timeout
,因为没有了default语句,如果其它通道一直接收到数据的话就会一直阻塞,直到有其它goroutine
给它发送数据,上面的匿名goroutine
sleep 3秒之后就会给通道timeout
发送数据,所以就会输出timeout
2). 那如果我们把上面代码改为如下代码会输出什么呢1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25package main
import "fmt"
// import "time"
func main() {
ch:=make(chan interface{})
timeout:=make(chan bool,1)
go func(){
timeout<-true
ch<-1
}()
fmt.Println("main goroutine begin")
select{
case <-ch:
fmt.Println("No timeout")
case <-timeout:
fmt.Println("timeout")
default:
fmt.Println("default")
}
}
output
会随机输出timeout、No timeout和default,因为所有通道都满足case,则go运行的时候会随机选择一个case执行
简单模拟一下客户端服务端通信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
38package main
import (
"errors"
"fmt"
"time"
)
func Client(ch chan interface{}, content string) (interface{}, error) {
ch <- content
select {
case resp := <-ch:
return resp, nil
case <-time.After(2 * time.Second):
return "", errors.New("time out")
}
}
func Server(ch chan interface{}, content string){
for{
data:=<-ch
fmt.Println("received:",data)
// time.Sleep(3 *time.Second) 如果想模拟超时可以加上这条代码
ch<-content
}
}
func main() {
ch:=make(chan interface{})
go Server(ch,"Send data")
recv,err:=Client(ch,"hello")
if err!=nil{
fmt.Println(err)
}else{
fmt.Println(recv)
}
}
通道响应计时器
1). 延时回调1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24package main
import (
"fmt"
"time"
)
func main() {
IsExit:=make(chan bool)
fmt.Println("Start")
time.AfterFunc(2 * time.Second,func(){
fmt.Println("Exec")
IsExit<-true
})
<-IsExit
fmt.Println("Exit")
}
output:
Start
Exec
Exit
2). 定点计时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
28package main
import (
"fmt"
"time"
)
func main() {
ticker := time.NewTicker(200 * time.Millisecond) //创建一个打点器,每200毫秒触发一次
breaker:= time.NewTimer(3 * time.Second) //创建一个计时器,3秒后触发一次
var stop bool = false
var count int
for{
select{
case <-ticker.C:
count++
case <-breaker.C:
fmt.Println("Time out break")
stop=true
}
if stop{
break //如果breaker通道接收到数据则退出
}
}
fmt.Println(count)
}
从已经关闭中的通道获取数据
- 关闭的通道依然可以被访问,访问被关闭的通道将会发生一些问题。
- 被关闭的通道不会被置为 nil
- 如果尝试对已经关闭的通道进行发送,将会触发宕机
从已经关闭的通道接收数据或者正在接收数据时,将会接收到通道类型的零值,然后停止阻塞并返回,如下代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package main
import (
"fmt"
)
func main() {
ch:=make(chan int,2)
ch<-1
ch<-2
close(ch)
for i:=0;i<=cap(ch);i++{
data,ok:=<-ch
fmt.Println(data,ok)
}
}
output:
1 true
2 true
0 false //false 表示没有获取成功,因为此时通道已经空了
活锁、死锁、饥饿
死锁
死锁:会使得所有并发程序在等待,如果没有外界干预,程序不能恢复
1 |
出现死锁的几个必要条件,也被称为Coffman
条件
Coffman
条件如下:
- 相互排斥:井发进程同时拥有资源的独占权。
- 等待条件:并发进程必须同时拥有一个资源,并等待额外的资源。
- 没有抢占:并发进程拥有的资掘只能被该进程释放,即可满足这个条件。
- 循环等待:一个并发进程(P1)必须等待一系列其他井发进程(P2),这些并发进程同时也在等待进程(P2),这样便满足了这个最终条件。
一些杂项
生成二维码
获取包:go get github.com/skip2/go-qrcode
生成一个跳转到百度的二维码1
2
3
4
5package main
import "github.com/skip2/go-qrcode"
func main() {
qrcode.WriteFile("http://www.baidu.com/",qrcode.Medium,256,"./qrcode.png")
}