目录

golang结构体

结构体的匿名字段

匿名字段是指结构体中没有字段名的字段

结构体的嵌套

正常的结构体嵌套

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

type Address struct {
City string
Phone string
}

type Person struct {
Name string
Age int
Address Address // 这就是嵌套的结构体
}


// 这样使用

func main() {
user1 := Person{
Name: "Tom",
Age: 20,
Address: Address{
City: "Beijing",
Phone: "1234567890",
},
}

user1.Address.City = "Shanghai" // 改变嵌套结构体的属性值

fmt.Println(user1.Address.City) // 输出 Shanghai
}

结构体匿名字段嵌套

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
type Address struct {
City string
Phone string
}

type Person struct {
Name string
Age int
Address // 这就是嵌套的结构体
}


// 这样使用

func main() {
user1 := Person{
Name: "Tom",
Age: 20,
Address: Address{
City: "Beijing",
Phone: "1234567890",
},
}

user1.City = "Shenzhen" // 可以这么访问改变结构体的属性值

fmt.Println(user1.Address.City) // 输出 Shanghai
}

注意:

  1. 如果外层结构体与嵌套结构体有同名字段,使用user1.City访问的时候,会优先访问外层字段
  2. 如果两个嵌套匿名结构体有同名字段,而外层没有同名字段,例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

type Address struct {
City string
Phone string
}

type Email stuct {
Account string
City string
}

type Person struct {
Name string
Age int
Address
Email
}

user1 := Person{}


定义方式如上,如果使用user1.City访问,会访问哪个字段呢,这样就会报错,必须使用user1.Address.City访问或者使用user1.Email.City访问,否则会报错

结构体的继承

  • 结构体继承就能看作是一个结构体匿名嵌套
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

package main

import "fmt"

type Animal struct {
Name string
}

func (a Animal) run() {
fmt.Println(a.Name, " is running!")
}

type Dog struct {
Animal // 继承Animal结构体
Age int
}

func (d Dog) wang() {
fmt.Println(d.Name, " is wang!")
}

func main() {
d := Dog{
Animal: Animal{
Name: "小狗",
},
Age: 4,
}

d.run()
d.wang()
}

/*
输出:
小狗 is running!
小狗 is wang!
*/

golang接口

接口的定义方式

1
2
3
4
type Animal interface {
speak()
eat()
}

实现接口的方式

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
type Dog struct {
name string
}

func (d Dog) speak() {
fmt.Println(d.name, " says woof!")
}

func (d Dog) eat() {
fmt.Println(d.name, " is eating meat!")
}

type Cat struct {
name string
}

func (c Cat) speak() {
fmt.Println(c.name, " says meow!")
}

func (c Cat) eat() {
fmt.Println(c.name, " is eating fish!")
}

func main() {
var animal Animal
var dog Dog
var cat Cat
animal = dog // 让狗实现 动物接口
animal.speak()
animal.eat()
cat.speak()
cat.eat()

}

使用接口的注意事项

1. 接口的定义中,**方法的签名必须一致,参数列表也必须一致。**
2. 接口的实现中,**必须实现接口中定义的所有方法。**

定义时值接受和指针接受的区别

1. 定义值接受的时候, 实例化后的结构体值类型和指针类型都可以赋值给接口变量
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
package main

import (
"fmt"
)

type Usber interface {
start()
stop()
}

type Phone struct {
Name string
}

func (p Phone) start() {
fmt.Println(p.Name, "is starting")
}

func (p Phone) stop() {
fmt.Println(p.Name, "is stopping")
}

func main() {

var u1 Usber = Phone{Name: "xiaomi"}
u1.start()
u1.stop()

// 正确使用方法
var u Usber = &Phone{Name: "iPhone"}
u.start()
u.stop()
}

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
28
29
30
31
32
33
34
35
36
  package main

import (
"fmt"
)

type Usber interface {
start()
stop()
}

type Phone struct {
Name string
}

func (p *Phone) start() {
fmt.Println(p.Name, "is starting")
}

func (p *Phone) stop() {
fmt.Println(p.Name, "is stopping")
}

func main() {
// 错误使用方法
/*
var u Usber = Phone{Name: "iPhone"}
u.start()
u.stop()
*/

// 正确使用方法
var u Usber = &Phone{Name: "iPhone"}
u.start()
u.stop()
}
3. 既有指针又有值接收,与指针接受类似

如何实现多个接口

直接让结构体实例化两次接口就行,如下

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
package main

import "fmt"

type Animaler1 interface {
SetName(string)
}

type Animaler2 interface {
GetName() string
}

type Dog struct {
name string
}

func (d *Dog) SetName(name string) {
d.name = name
}

func (d Dog) GetName() string {
return d.name
}

func main() {
d := &Dog{"小黑"}
var a1 Animaler1 = d // 实现Animaler1接口
var a2 Animaler2 = d // 实现Animaler2接口
fmt.Println(a2.GetName())
a1.SetName("小白")
fmt.Println(a2.GetName())
}

  • 接口的嵌套
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
package main

import "fmt"

type Animaler1 interface {
SetName(string)
}

type Animaler2 interface {
GetName() string
}

type Animaler interface { // 嵌套Animaler1和Animaler2接口
Animaler1
Animaler2
}

type Dog struct {
name string
}

func (d *Dog) SetName(name string) {
d.name = name
}

func (d Dog) GetName() string {
return d.name
}

func main() {
d := &Dog{"小黑"}
var a Animaler = d
fmt.Println(a.GetName())
a.SetName("小白")
fmt.Println(a.GetName())
}

空接口

没有定义任何方法的接口就叫做空接口,空接口表示没有任何约束,因此任何类型变量都可以实现空接口
1. 灵活性:由于空接口可以接受任何类型的值,它为编写更灵活的函数和数据结构提供了便利。例如,你可以编写一个接收空接口作为参数的函数,这个函数可以处理不同类型的输入。

1
2
3
func PrintValue(v interface{}) {
fmt.Println(v)
}

2. map的val为空接口,表示可以存储任意类型的值
1
2
3
4
5
var m map[string]interface{}
m = make(map[string]interface{})
m["key"] = 123
m["key"] = "hello"
fmt.Println(m["key"]) // 输出 123 hello

3. 切片实现空接口
1
2
var slice = []interface{}{1, "hello", true}
fmt.Println(slice) // 输出 [1 hello true]

4. 类型断言在使用空接口时,通常需要通过类型断言来获取实际值的具体类型。这使得在需要时可以安全地访问原始值。
1
2
3
4
5
6
// v.(类型) 即为断言
var v interface{} = "Hello"
str, ok := v.(string);
if ok {
fmt.Println(str) // 输出: Hello
}

5. 实现多态:通过使用空接口,可以实现某种程度的多态,使得不同类型的对象可以通过同一个接口进行操作。这在设计API时尤为有用。

空接口的使用细节,以及类型断言的一种使用方式

1. 空接口给切片或者map作为值类型的时候在使用的时候,不能详细访问里面的细节(详细下标,或者结构体里的值),例如:
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
package main

import "fmt"

type Address struct {
City string
Phone string
}

func main() {
var mp map[string]interface{} = make(map[string]interface{})
mp["name"] = "Alice"
mp["age"] = 25
mp["address"] = Address{"New York", "123-456-7890"}
mp["arrays"] = []int{1, 2, 3}

// 错误用法 因为空接口无法详细访问
//fmt.Println(mp["address"].City)
//fmt.Println(mp["arrays"][0])

// 正确用法, 使用类型推断来实现
v, ok := mp["address"].(Address)
if ok {
fmt.Println(v.City)
}

k, ok := mp["arrays"].([]int)
if ok {
fmt.Println(k[0])
}

fmt.Println(mp["arrays"].([]int)[1])

}

golang协程

  • 进程,线程,协程之间的区别

  • 获取及设置本机CPU核数

    1
    2
    3
    4
    5
    nums := runtime.NumCPU() // 获取CPU的核数
    fmt.Println("CPU核数:", nums)

    runtime.GOMAXPROCS(nums - 1) // 设置最大的CPU核数
    fmt.Println("CPU核数:", runtime.GOMAXPROCS(0))
    1. 以上两种方式 一个属获取本机cpu核数,一个是(runtime.GOMAXPROCS 函数)设置项目中的最大协程数且返回调用之前的最大线程数(即之前的 GOMAXPROCS 值)。当传入0的时候,表示只获取值而不修改

协程的创建方式

  • 使用go关键字创建协程
    使用go关键字即可创建, go后面跟着协程要执行的任务函数
    1
    go text()

协程的同步方式

使用sync.WaitGroup来等待协程执行完毕

  • sync.WaitGroup是一种类型 其有三种函数 Add()、Done()、Wait(), 其中Add()用来增加协程的数量,Done()用来减少协程的数量,Wait()用来等待所有的协程执行完毕。在代码中使用方式如下:

    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
    package main

    import (
    "fmt"
    "sync"
    "time"
    )

    var wd sync.WaitGroup //

    func test1() {
    for i := 1; i <= 10; i++ {
    fmt.Printf("test1() 执行%d次\n", i)
    time.Sleep(time.Millisecond * 100) // 延迟100毫秒
    }
    wd.Done() // 通知main()函数结束 计数器减1
    }

    func test2() {
    for i := 1; i <= 10; i++ {
    fmt.Printf("test2() 执行%d次\n", i)
    time.Sleep(time.Millisecond * 50) // 延迟50毫秒
    }
    wd.Done() // 通知main()函数结束
    }

    func main() {
    wd.Add(1) // 计数器加1
    go test1() // 使用go关键字启动一个协程
    wd.Add(1) // 计数器加1
    go test2() // 使用go关键字启动另一个协程
    wd.Wait() // 等待两个协程执行完毕
    fmt.Println("main() 执行完毕")
    }

    1. 使用互斥锁 Mutex
    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
    package main

    import (
    "fmt"
    "strconv"
    "sync"
    )

    var wg sync.WaitGroup

    var (
    mu sync.Mutex // 互斥锁
    cond = sync.NewCond(&mu) // 条件变量
    ch chan string = make(chan string, 100) // 管道
    )

    func producer() {
    for i := 0; i < 200; i++ {
    mu.Lock()
    for len(ch) == cap(ch) { // 如果管道已满,等待
    cond.Wait()
    }
    // 生产者生产数据
    s := "生产者生产" + strconv.Itoa(i)
    fmt.Println(s)
    ch <- s // 放入管道
    cond.Signal() // 通知消费者
    mu.Unlock()
    }

    mu.Lock()
    for len(ch) == cap(ch) { // 等待所有生产者完成
    cond.Wait()
    }
    ch <- "结束" // 生产者结束
    cond.Signal() // 通知消费者
    mu.Unlock()
    wg.Done() // 生产者完成
    }

    func consumer() {
    for {
    mu.Lock()
    for len(ch) == 0 { // 如果管道为空,等待
    cond.Wait()
    }
    s := <-ch // 从管道取出数据
    mu.Unlock()

    if s == "结束" {
    break
    }
    fmt.Println("消费者消费", s)

    cond.Signal() // 通知生产者
    }
    wg.Done() // 消费者完成
    }

    func main() {
    wg.Add(1)
    go consumer() // 启动消费者

    wg.Add(1)
    go producer()

    wg.Wait()
    close(ch) // 关闭管道
    }

    1. 使用读写锁 RWMutex
    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
    package main

    import (
    "fmt"
    "sync"
    )

    var (
    wg sync.WaitGroup
    numChan = make(chan int, 1000)
    mutex sync.RWMutex
    )

    func reader() {
    mutex.RLock()
    for i := 0; i < len(numChan); i++ {
    fmt.Println(i)
    }
    mutex.RUnlock()
    wg.Done()
    }

    func writer() {
    mutex.Lock()
    for i := 0; i < 100; i++ {
    numChan <- i
    }
    mutex.Unlock()
    wg.Done()
    }

    func main() {

    wg.Add(1)
    go writer()
    go writer()
    for i := 0; i < 100; i++ {
    wg.Add(1)
    go reader()
    }
    wg.Wait()
    }

golang管道

  • 管道(channel)在go中是一种类型,并且是引用类型,声明方式如下:

    1
    2
    3
    var ch chan int // 声明一个管道,其中int为管道中数据的类型

    var ch3 chan []int // 声明一个管道,其中[]int为管道中数据的类型
  • 因为是引用类型,必须要创建空间才能使用管道,创建管道的函数有make()和new()两种,区别如下:

    • make()函数:make()函数用于创建管道,其返回值是一个管道,并且会初始化管道的缓冲区大小,如果缓冲区大小为0,则表示无缓冲区,如果缓冲区大小大于0,则表示有缓冲区。
    • new()函数:new()函数用于创建管道,其返回值是一个指针,指向一个管道,但是并不会初始化管道的缓冲区大小。
    1
    2
    // 格式是:make(chan 数据类型, 缓冲区大小)
    var ch chan int = make(chan int, 10)
  • 管道的操作方式

    1. 接受与发送都使用**<-**运算符
    2. 发送数据到管道:使用管道的 <- 运算符,将数据发送到管道中。
    1
    ch <- 10 // 发送数据10到管道ch
    1. 从管道接收数据:使用管道的 <- 运算符,从管道中接收数据。
    1
    x := <-ch // 从管道ch接收数据并赋值给变量x
    1. 关闭管道:当管道中的数据全部被接收完毕后,需要关闭管道,以防止其他协程继续向管道中发送数据。
    1
    close(ch) // 关闭管道ch
    1. 管道的阻塞与非阻塞:当管道中没有数据时,从管道接收数据会阻塞,直到有数据可接收。当管道中没有数据时,向管道发送数据会阻塞,直到有数据接收者。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 管道的阻塞
    ch := make(chan int, 2)
    ch <- 2
    ch <- 3
    ch <- 4 // 此时管道中有三个数据,但是只有两个接收者,所以此时会阻塞,直到有接收者接收数据

    ch1 := make(chan int, 1) // 无缓冲区的管道
    m := <-ch1 // 此时会阻塞,直到有数据被发送到管道中

    1. 管道的容量:管道的容量表示管道中可以存储的数据的数量,如果管道的容量为0,则表示无缓冲区,如果管道的容量大于0,则表示有缓冲区。
    1
    cap(ch) // 获取管道ch的容量
    1. 管道的长度:管道的长度表示管道中当前存储的数据的数量。
    1
    len(ch) // 获取管道ch的长度
    1. 管道的遍历:
      1. 管道的遍历可以使用for range语句,遍历管道中的数据,在遍历前需要先关闭管道。否则会报错 all goroutines are asleep - deadlock!
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        ch := make(chan int, 3)
        ch <- 1
        ch <- 2
        ch <- 3
        close(ch) // 关闭管道

        // 遍历管道
        for i := range ch { // 管道没有key值
        fmt.Println(i)
        }
      2. 使用for循环遍历管道中的数据,在遍历前可以不关闭管道
        1
        2
        3
        4
        5
        6
        7
        8
        9
        ch := make(chan int, 3)
        ch <- 1
        ch <- 2
        ch <- 3

        // 遍历管道
        for i := 1; i <= 3; i ++ {
        fmt.Println(<-ch) // 从管道中接收数据并打印
        }
  • 管道的应用

    1. 生产者-消费者模型:生产者生产数据,并将数据放入管道中,消费者从管道中取出数据进行处理。
    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
    package main

    import (
    "fmt"
    "strconv"
    "sync"
    )

    var wg sync.WaitGroup

    var (
    mu sync.Mutex // 互斥锁
    cond = sync.NewCond(&mu) // 条件变量
    ch chan string = make(chan string, 100) // 管道
    )

    func producer() {
    for i := 0; i < 200; i++ {
    mu.Lock()
    for len(ch) == cap(ch) { // 如果管道已满,等待
    cond.Wait()
    }
    // 生产者生产数据
    s := "生产者生产" + strconv.Itoa(i)
    fmt.Println(s)
    ch <- s // 放入管道
    cond.Signal() // 通知消费者
    mu.Unlock()
    }

    mu.Lock()
    for len(ch) == cap(ch) { // 等待所有生产者完成
    cond.Wait()
    }
    ch <- "结束" // 生产者结束
    cond.Signal() // 通知消费者
    mu.Unlock()
    wg.Done() // 生产者完成
    }

    func consumer() {
    for {
    mu.Lock()
    for len(ch) == 0 { // 如果管道为空,等待
    cond.Wait()
    }
    s := <-ch // 从管道取出数据
    mu.Unlock()

    if s == "结束" {
    break
    }
    fmt.Println("消费者消费", s)

    cond.Signal() // 通知生产者
    }
    wg.Done() // 消费者完成
    }

    func main() {
    wg.Add(1)
    go consumer() // 启动消费者

    wg.Add(1)
    go producer()

    wg.Wait()
    close(ch) // 关闭管道
    }

  • 引用类型与C++中的区别

    1. 首先在函数传参中,go语言中传参是值传递,而C++中传参是引用传递(别名),虽说go中传递引用是值传递,但是却是复制的一个类似结构体的东西,里面有一个指针,指向的内存与传入的实参是一致的,但是参数地址与实参不一致
    2. go中引用类型有切片,map,channel等
  • select多路复用与管道的结合

    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
    package main

    import (
    "fmt"
    "strconv"
    "sync"
    )

    var (
    wg sync.WaitGroup
    numChan = make(chan int, 1000)
    )

    func main() {

    intChan := make(chan int, 10)
    stringChan := make(chan string, 10)

    for i := 0; i < 10; i++ {
    intChan <- i
    stringChan <- strconv.Itoa(i)
    }

    for {
    select {
    case v := <-intChan:
    fmt.Printf("Received int value: %d\n", v)
    case k := <-stringChan:
    fmt.Printf("Received string value: %s\n", k)
    default:
    fmt.Println("No more values")
    return
    }
    }
    }
    1. select与操作系统的IOselect有联系吗?
      没什么联系,操作系统的select是IO多路复用,每次调用得到一组就绪的文件描述符,然后程序逐个处理。golang这个select就是runtime.selectgo函数,实现的是channel批量监听,但是每次等待成功后只处理一个channel。两者互不依赖,有点像的话,也就都是批量监听了。

管道底层的数据结构

  • 首先管道支持多个协程访问,所以底层肯定有,其次管道可能会有缓冲区,所以需要指向缓冲区的指针以及最大长度还有已经存储了多少数据以及每个元素占用内存大小和其类型
    由于管道会阻塞所以有两个队列,发送队列和接受队列,这两个队列来存储被阻塞的协程,由于是基于缓冲区的,所以会有一个写下标和读下标,写下标表示管道中可以写入数据的位置,
    读下标表示管道中可以读取数据的位置,最后还有一个标志是否关闭的状态位,其内容大致如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
stuct chan {
lock mutex // 锁
qcount uint // 队列长度
dataqsiz uint // 缓冲区大小
buf unsafe.Pointer // 指向缓冲区的指针
elemsize uint16 // 每个元素的大小
closed uint32 // 是否关闭的状态位
sendx uint // 发送队列的下标
recvx uint // 接收队列的下标
recvq waitq // 接收队列
sendq waitq // 发送队列
elemtype *_type // 每个元素的类型
}

select的底层原理

  • 会被编译器翻译成执行selectgo函数,其函数由6个参数和两个返回值
  • 参数:首先由一个指向一个数组,里面装的是select中所有的case分支,第二个参数是指向一个uint16类型的数组,其大小等于case的两倍。实际会被分为两个数组,一个数组用来乱序轮询(保证公平性),一个用来对所有加锁操作进行排序(避免死锁),第四第五个参数分别表示send和recv操作的分支分别有多少个
    block参数则是表示多路select是否要阻塞等待(对应是否由default分支决定)
  • 返回值:第一个返回值表示哪个分支被执行(对应数组下标,如果阻塞为-1),第二个表示是实际收到了一个值还是得到了0值
    大致如下:
    alt text

select的执行过程

  • 首先对所有管道进行顺序加锁,然后按照乱序的轮询顺序检查所有channel的等待队列和缓冲区,接下来就是channel相关操作的判断看是否需要阻塞,如果阻塞了就要挂起等待,当有数据时就按序解锁并唤醒执行,执行完后按序加锁,阻塞协程离开队列最后按序解锁

go的反射机制

反射机制的定义

  • 在计算机学中,反射式编程(英语:reflective programming)或反射(英语:reflection),是指计算机程序(runtime)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。

反射的重要性

反射在许多方面都非常有用,比如:

  1. 动态编程: 通过反射,你可以动态地创建对象,调用方法,甚至构建全新的类型。
  2. 框架与库开发: 很多流行的Go框架,如Gin、Beego等,都在内部使用反射来实现灵活和高度可定制的功能。
  3. 元编程: 你可以写出可以自我分析和自我修改的代码,这在配置管理、依赖注入等场景中尤为有用。

反射的分类

反射在Go中主要有两个方向:

  1. 类型反射(Type Reflection): 主要关注于程序运行时获取变量的类型信息。
  2. 值反射(Value Reflection): 主要关注于程序运行时获取或设置变量的值

类型反射:

  • 通过reflect库中的Typeof函数实现,Kind就是获取其底层的类型,比如你定义一个Person结构体,Kind就是struct而Name就是Person
1
2
3
4
5
6
7
8
9
10
11
// 代码示例:类型反射
func inspectType(x interface{}) {
t := reflect.TypeOf(x)
fmt.Println("Type Name:", t.Name()) // 名称
fmt.Println("Type Kind:", t.Kind()) // 种类
}

func main() {
inspectType(42) // Type Name: int, Type Kind: int
inspectType("hello")// Type Name: string, Type Kind: string
}

值反射

  • 通过reflect库中的ValueOf函数实现
1
2
3
4
5
6
7
8
9
10
11
// 代码示例:值反射
func inspectValue(x interface{}) {
v := reflect.ValueOf(x)
fmt.Println("Value:", v)
fmt.Println("Is Zero:", v.IsZero())
}

func main() {
inspectValue(42) // Value: 42, Is Zero: false
inspectValue("") // Value: , Is Zero: true
}

反射的应用

与空接口配合使用,得到真实数据

  • 可以使用类型断言 也能使用反射来实现
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
package main

import (
"fmt"
"reflect"
)

func reflectValue(value interface{}) {
// 想要实现value根据对应类型得到不同操作

// 使用反射来获取值
v := reflect.ValueOf(value)
switch v.Kind() {
case reflect.Int:
fmt.Println("int value:", v.Int()+10)
case reflect.String:
fmt.Println("string value:", v.String()+" world")
case reflect.Bool:
fmt.Println("bool value:", !v.Bool())
case reflect.Float32:
fmt.Println("float32 value:", v.Float()+1.0)
default:
fmt.Println("unknown type")
}

// 使用类型断言也能实现
x, ok := value.(int)
if ok {
fmt.Println("int value:", x+10)
}
y, ok := value.(string)
if ok {
fmt.Println("string value:", y+" world")
}
z, ok := value.(bool)
if ok {
fmt.Println("bool value:", !z)
}
h, ok := value.(float32)
if ok {
fmt.Println("float32 value:", h+1.0)
}
}
func main() {
var a int = 10
var b string = "hello"
var c bool = true
var d float32 = 3.14

reflectValue(a)
reflectValue(b)
reflectValue(c)
reflectValue(d)
}

通过反射设置变量的值

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
package main

import (
"fmt"
"reflect"
)

// 想要修改传入的值(前提参数为空接口)

// 错误用法1:直接传入值,而不是指针
// 错误原因 reflect.Value.SetInt using unaddressable value
// func SetVal(val interface{}) {
// // 首先 获取传入值
// v := reflect.ValueOf(val)
// // 然后根据获取的值的类型进行修改使得改变值
// if v.Kind() == reflect.Int {
// v.SetInt(100)
// } else if v.Kind() == reflect.String {
// v.SetString("new string")
// }
// }

func SetVal(val interface{}) {
// 首先 获取传入值
v := reflect.ValueOf(val).Elem()
fmt.Println(v.Kind())
// 然后根据获取的值的类型进行修改使得改变值
if v.Kind() == reflect.Int {
v.SetInt(100)
} else if v.Kind() == reflect.String {
v.SetString("new string")
}
}

func main() {
var a = 10
b := "string"

// 错误用法
// SetVal(a)
// SetVal(b)

// 正确用法
SetVal(&a)
SetVal(&b)

fmt.Println(a)
fmt.Println(b)
}

反射获取结构体的值与类型

获取属性
  • 使用Filed(i)来获取第i个字段
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
package main

import (
"fmt"
"reflect"
)

type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Address string `json:"address"`
}

func GetStruct(p interface{}) {
// 获取类型指定字段类型
t := reflect.TypeOf(p)
// 使用Filed
field0 := t.Field(0)

fmt.Println("第一个字段名", field0.Name)
fmt.Println("第一个字段类型", field0.Type)
fmt.Println("第一个字段标签", field0.Tag.Get("json"))

}

func main() {
person1 := Person{"Tom", 20, "China"}
GetStruct(person1)
}
  • 使用FiledByName(name)来获取指定字段
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"
"reflect"
)

type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Address string `json:"address"`
}

func GetStruct(p interface{}) {
// 获取类型指定字段类型
t := reflect.TypeOf(p)
// 使用FiledByName获取指定字段
field0, ok := t.FieldByName("Age")
if ok {
fmt.Println("第一个字段名", field0.Name)
fmt.Println("第一个字段类型", field0.Type)
fmt.Println("第一个字段标签", field0.Tag.Get("json"))
}

}

func main() {
person1 := Person{"Tom", 20, "China"}
GetStruct(person1)
}
  • 通过NumField()来获取结构体的字段数量,并且遍历所有字段
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"
"reflect"
)

type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Address string `json:"address"`
}

func GetStruct(p interface{}) {
// 获取类型指定字段类型
t := reflect.TypeOf(p)
// 使用FiledByName获取指定字段
for i := 0; i < t.NumField(); i++ {
fmt.Printf("第%d个字段的名称为:%s\n", i, t.Field(i).Name)
fmt.Printf("第%d个字段的类型为:%s\n", i, t.Field(i).Type)
}

}

func main() {
person1 := Person{"Tom", 20, "China"}
GetStruct(person1)
}
  • 获取值也有上述三种方法,不过反射的时候改为Valueof,例如
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"
"reflect"
)

type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Address string `json:"address"`
}

func GetStruct(p interface{}) {
// 获取类型指定字段类型
t := reflect.ValueOf(p)
// 使用FiledByName获取指定字段
for i := 0; i < t.NumField(); i++ {
fmt.Printf("第%d个字段的值为:%v\n", i, t.Field(i))
}

}

func main() {
person1 := Person{"Tom", 20, "China"}
GetStruct(person1)
}

  • 通过反射修改结构体值,也是与修改变量类似
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
package main

import (
"fmt"
"reflect"
)

type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Address string `json:"address"`
}

func (p Person) GetPerson() string {
var str = fmt.Sprintf("%+v\n", p)
return str
}

func (p *Person) SetPerson(name string, age int, address string) {
p.Name = name
p.Age = age
p.Address = address
}

func (p Person) Print() {
fmt.Println(p.GetPerson())
}

func SetStructField(p interface{}, key string, str string) {
// 获取类型指定字段类型
v := reflect.ValueOf(p)
// 调用指定名字的方法有参数

if v.Kind() != reflect.Ptr {
fmt.Println("not a pointer")
return
}

if v.Elem().Kind() != reflect.Struct {
fmt.Println("not a struct pointer")
return
}

v.Elem().FieldByName(key).SetString(str)

}

func main() {
person1 := Person{"Tom", 20, "China"}
fmt.Println(person1.GetPerson())
SetStructField(&person1, "Name", "Jerry")
fmt.Println(person1.GetPerson())

}

获取方法
  • 使用Method(i)来获取方法,其中方法的顺序是通过Ascii码确定的+NumMethod()来获取方法数量
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
package main

import (
"fmt"
"reflect"
)

type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Address string `json:"address"`
}

func (p Person) GetPerson() string {
var str = fmt.Sprintf("%+v\n", p)
return str
}

func (p *Person) SetPerson(name string, age int, address string) {
p.Name = name
p.Age = age
p.Address = address
}

func (p Person) Print() {
fmt.Println(p.GetPerson())
}

func GetStructMethod(p interface{}) {
// 获取类型指定字段类型
t := reflect.TypeOf(p)
// 使用FiledByName获取指定字段
for i := 0; i < t.NumMethod(); i++ {
fmt.Printf("第%d个方法的名称为:%v\n", i, t.Method(i).Name)
fmt.Printf("第%d个方法的类型为:%v\n", i, t.Method(i).Type)
}
}

func main() {
person1 := Person{"Tom", 20, "China"}
GetStructMethod(person1)
}

  • 通过MethodByName(str)来获取
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
package main

import (
"fmt"
"reflect"
)

type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Address string `json:"address"`
}

func (p Person) GetPerson() string {
var str = fmt.Sprintf("%+v\n", p)
return str
}

func (p *Person) SetPerson(name string, age int, address string) {
p.Name = name
p.Age = age
p.Address = address
}

func (p Person) Print() {
fmt.Println(p.GetPerson())
}

func GetStructMethod(p interface{}) {
// 获取类型指定字段类型
t := reflect.TypeOf(p)
// 使用FiledByName获取指定字段
method1, ok := t.MethodByName("Print")
if ok {
fmt.Println(method1.Name)
fmt.Println(method1.Type)
}

}

func main() {
person1 := Person{"Tom", 20, "China"}
GetStructMethod(person1)
}
反射调用方法

首先必须通过反射获取值也就是使用felect.ValueOf(),然后通过反射调用方法,调用方法的语法如下:修改方法必须传入引用

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
package main

import (
"fmt"
"reflect"
)

type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Address string `json:"address"`
}

func (p Person) GetPerson() string {
var str = fmt.Sprintf("%+v\n", p)
return str
}

func (p *Person) SetPerson(name string, age int, address string) {
p.Name = name
p.Age = age
p.Address = address
}

func (p Person) Print() {
fmt.Println(p.GetPerson())
}

func GetStructMethod(p interface{}) {
// 获取类型指定字段类型
v := reflect.ValueOf(p)
// 调用指定名字的方法有参数

// 首先得定义切片
var args []reflect.Value
args = append(args, reflect.ValueOf("小黑"), reflect.ValueOf(18), reflect.ValueOf("Earth"))

v.MethodByName("SetPerson").Call(args)

// 无参数

v.MethodByName("Print").Call(nil)

}

func main() {
person1 := Person{"Tom", 20, "China"}
GetStructMethod(&person1)
}

go的文件操作

使用os打开并使用其返回的file读取文件

  • 注意倒数第三行拷贝数据的写法
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"
"io"
"os"
)

func main() {
// 可以使用相对路径,也可以使用绝对路径(此方法打开是只读)
file, err := os.Open("./main.go")
if err != nil {
fmt.Println(err)
return
}
// 打开一定要记得关闭
defer file.Close()

// 开始读取文件 使用for循环读,并且需要传入存储读取数据的切片
// 最终读取数据的存放位置
var endSlice []byte
// 临时读取数据的存放位置
tmpSlice := make([]byte, 128)

for {
n, err := file.Read(tmpSlice)
if err == io.EOF {
fmt.Println("文件读取完毕")
break
}
if err != nil {
fmt.Println("文件读取失败")
return
}

endSlice = append(endSlice, tmpSlice[:n]...) // 注意此处写法,必须要有:n以及...,只有这样才能读取完整数据,否则会读取到随机数据,因为tmpSlice可能未读满
}

fmt.Println(string(endSlice))
}

使用bufio读取数据

  • 用此方法时候注意在读取到EOF的时候也要累加数据,否则会丢失数据
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
package main

import (
"bufio"
"fmt"
"io"
"os"
)

func main() {
// 可以使用相对路径,也可以使用绝对路径(此方法打开是只读)
file, err := os.Open("./main.go")
if err != nil {
fmt.Println(err)
return
}
// 打开一定要记得关闭
defer file.Close()

// 开始读取文件 使用for循环读,使用bufio来读取
reader := bufio.NewReader(file)

var ans string

for {
line, err := reader.ReadString('\n') // 读取一行
if err == io.EOF { // 读取到文件末尾
// 注意还此时line可能还有数据
ans += line
break
}

if err != nil {
fmt.Println(err)
return
}

ans += line // 读取到一行数据,添加到ans中
}

fmt.Println(ans) // 输出ans
}
  • 以上两种都是流的读取方法
  • 下述是一次读取整个文件的方法

使用ioutil读取文件

  • 此方法在go1.16以上版本被弃用,可以使用os.ReadFile()代替,因为这个实现上就是用了os.ReadFile
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"
"os"
)

func main() {
// 开始读取文件
slice, err := os.ReadFile("./main.go")

if err != nil {
fmt.Println(err)
return
}

// 打印文件内容
fmt.Println(string(slice))

}

os.openfile()和file.Write()写入文件

alt text

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
package main

import (
"fmt"
"os"
"strconv"
)

func main() {
// 开始读取文件
// 三个参数分别是文件名、打开模式、权限(用于Linux)
file, err := os.OpenFile("./test.txt", os.O_CREATE|os.O_RDWR, 0666)

if err != nil {
fmt.Println(err)
return
}

defer file.Close()

// 写入文件 方式以一
for i := 0; i < 10; i++ {
file.WriteString("hello world" + strconv.Itoa(i) + "\r\n")
}

str := "omg no hello world \r\n"

// 写入文件方式二
file.Write([]byte(str))

}

使用bufio.writer写入文件

  • 总体流程是这样的:
    1. 先通过os.OpenFile打开文件,并通过bufio.NewWriter创建一个bufio.Writer
    2. 然后通过WriteString或Write方法写入数据(此时会先写入缓存区
    3. 最后通过Flush方法将缓存区的数据写入文件
    4. 最后关闭文件
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 (
"bufio"
"fmt"
"os"
)

func main() {
// 开始读取文件
// 三个参数分别是文件名、打开模式、权限(用于Linux)
file, err := os.OpenFile("./test.txt", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)

if err != nil {
fmt.Println(err)
return
}

defer file.Close()

writer := bufio.NewWriter(file)

// 写入缓存区
writer.WriteString("hello world zxsssn\r\n")

// 写入文件
writer.Flush()

}

使用os.WriteFile()写入文件

  • 此方法虽简单但会清空文件里的内容再写入
1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"os"
)

func main() {
err := os.WriteFile("./test.txt", []byte("hello world"), 0666)
if err != nil {
return
}
}

go排序方法

排序整数,浮点数和字符串切片

对于 []int, []float, []string 这种元素类型是基础类型的切片使用 sort 包提供的下面几个函数进行排序。

  • sort.Ints(slice)
  • sort.Float64s(slice)
  • sort.Strings(slice)

使用自定义比较器排序

  • 使用 sort.Slice 函数排序,它使用一个用户提供的函数来对序列进行排序,函数类型为 func(i, j int) bool,其中参数 i, j 是序列中的索引。
  • sort.SliceStable 在排序切片时会保留相等元素的原始顺序。
  • 上面两个函数让我们可以排序结构体切片 (order by struct field 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
package main

import (
"fmt"
"sort"
)

type Person struct {
Key string
Value int
}

func main() {

people := []Person{
{"Alice", 25},
{"Cob", 30},
{"Vharlie", 20},
{"Favid", 35},
{"Eve", 28},
}

fmt.Println(people)

// 使用slice排序
sort.Slice(people, func(i, j int) bool {
return people[i].Key < people[j].Key
})

fmt.Println(people)

// 使用sliceStable降序排序
sort.SliceStable(people, func(i, j int) bool {
return people[i].Key > people[j].Key
})
fmt.Println(people)
}

排序任意数据结构

  • 使用 sort.Sort 或者 sort.Stable 函数。
  • 他们可以排序实现了 sort.Interface 接口的任意类型
  • 也就是说每个自定义的切片中都有一些内置的接口,比如sort.Interface,sort.Interface定义了三个方法,我们可以实现这三个方法,然后传入自定义的切片进行排序。
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
package main

import (
"fmt"
"sort"
)

type Person struct {
Key string
Value int
}

type ByValue []Person

func (a ByValue) Len() int { return len(a) }
func (a ByValue) Less(i, j int) bool { return a[i].Value < a[j].Value }
func (a ByValue) Swap(i, j int) { a[i], a[j] = a[j], a[i] }

func main() {

people := []Person{
{"Alice", 25},
{"Cob", 30},
{"Vharlie", 20},
{"Favid", 35},
{"Eve", 28},
}

fmt.Println(people)

sort.Sort(ByValue(people))

fmt.Println(people)

}

  • 也可以实现结构体内部数组的排序
1
2
3
4
5
6
7
8
type customSort struct {
p []*Person
less func(x, y *Person) bool
}

func (x customSort) Len() int {len(x.p)}
func (x customSort) Less(i, j int) bool { return x.less(x.p[i], x.p[j]) }
func (x customSort) Swap(i, j int) { x.p[i], x.p[j] = x.p[j], x.p[i] }
  • 让我们定义一个根据多字段排序的函数,它主要的排序键是 Age,Age 相同了再按 Name 进行倒序排序。下面是该排序的调用,其中这个排序使用了匿名排序函数:
1
2
3
4
5
6
7
8
9
sort.Sort(customSort{persons, func(x, y *Person) bool {
if x.Age != y.Age {
return x.Age < y.Age
}
if x.Name != y.Name {
return x.Name > y.Name
}
return false
}})

排序具体的算法和复杂度

  • Go 的 sort 包中所有的排序算法在最坏的情况下会做 n log n 次 比较,n 是被排序序列的长度,所以排序的时间复杂度是 O(n log n*)。其大多数的函数都是用改良后的快速排序算法实现的。

go序列化相关知识

Json序列化

  • GO提供了 Marshal 方法:Go Struct转换为JSON对象,函数签名:
1
func Marshal(v interface{}) ([]byte, error)
  • 举例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type Person struct {
Name string
Gender string
Age uint32
}

// 默认初始化
p := Person{"a", "male", 23}
fmt.Println(p)
fmt.Printf("%v\n", p) //{a male 23}

// 指定成员初始化
p1 := Person{Name: "wsq", Gender: "male"}
fmt.Println(p1) //{wsq male 0}

// 序列化
b, _ := json.Marshal(p)
fmt.Println(string(b)) //{"Name":"a","Gender":"male","Age":23}
}

  • 只支持struct中导出的field才能被序列化,即首字母大写的field
  • GO中不是所有类型都支持序列化,其中key只支持string
  • 无法对channel,complex,function序列化
  • 数据中如存在循环引用,不支持序列化,因为会递归。
  • pointer序列化后是其指向的值或者是nil

Struct Tag

  • 指定 JSON filed name
    序列化后的json串中的name一般为小写,我们通过struct tag实现

  • 指定field为empty
    使用omitempty告诉Marshal函数,如field对应类型为zero-value,那么序列化的json对象中不包含此field

  • 跳过field
    仅使用"-"表示跳过指定的field,保护某些字段不被序列化

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
type Person struct {
Name string `json:"name"`
Gender string `json:"gender"`
Age uint32 `json:"age,omitempty"`
Passwd string `json:"-"`
}

// 默认初始化
p := Person{"a", "male", 23, "mimi"}
fmt.Println(p)
fmt.Printf("%v\n", p) //{a male 23}

// 指定成员初始化
p1 := Person{Name: "wsq", Gender: "male"}
fmt.Println(p1) //{wsq male 0}

// 序列化
b, _ := json.Marshal(p)
fmt.Println(string(b)) //{"Name":"a","Gender":"male","Age":23}

// 反序列化
var pp Person
err := json.Unmarshal(b, &pp)
if err != nil {
errors.New("unmarshal error")
}
fmt.Printf("%T, %v\n", pp, pp)

// Struct Tag
// 指定JSON的field name
c, _ := json.Marshal(p)
fmt.Println(string(c)) //{"name":"a","gender":"male","age":23}

// 指定field是empty时的行为
d, _ := json.Marshal(p1)
fmt.Println(string(d)) //{"name":"wsq","gender":"male"}
// 跳过指定field
importPerson := Person{Name: "wsq", Passwd: "password"}
importPersonMar, _ := json.Marshal(importPerson)
fmt.Println(string(importPersonMar)) //{"name":"wsq","gender":""}

Json反序列化

  • GO提供了 Unmarshal 方法:JSON对象转换为Go Struct,函数签名:
1
func Unmarshal(data []byte, v interface{}) error
  • 举例
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
type Person struct {
Name string `json:"name"`
Gender string `json:"gender"`
Age uint32 `json:"age,omitempty"`
Passwd string `json:"-"`
}

// 默认初始化
p := Person{"a", "male", 23, "mimi"}
fmt.Println(p)
fmt.Printf("%v\n", p) //{a male 23}

// 指定成员初始化
p1 := Person{Name: "wsq", Gender: "male"}
fmt.Println(p1) //{wsq male 0}

// 序列化
b, _ := json.Marshal(p)
fmt.Println(string(b)) //{"Name":"a","Gender":"male","Age":23}

// 反序列化
var pp Person
err := json.Unmarshal(b, &pp)
if err != nil {
errors.New("unmarshal error")
}
fmt.Printf("%T, %v\n", pp, pp)

使用Encoder和Decoder进行序列化和反序列化

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
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}

func main() {
var err error
person1 := Person{"张三", 30}
// 编码结果暂存到 buffer
bytes1 := new(bytes.Buffer)
_ = json.NewEncoder(bytes1).Encode(person1)
if err == nil {
fmt.Print("json.NewEncoder 编码结果: ", string(bytes1.Bytes()))
}

// 解码
str2 := bytes1.String()
var person2 Person
// 创建一个 string reader 作为参数
err = json.NewDecoder(strings.NewReader(str2)).Decode(&person2)
if err == nil {
fmt.Println("json.NewDecoder 解码结果: ", person2.Name, person2.Age)
}

}

gob序列化

  • Gob 是 Go 自己的以二进制形式序列化和反序列化程序数据的格式;可以在 encoding 包中找到。 这种格式的数据简称为 Gob (即 Go binary 的缩写)。 类似于 Python 的 “pickle” 和 Java 的 “Serialization”。 Gob 通常用于远程方法调用(RPCs,参见 15.9 的 rpc 包)参数和结果的传输,以及应用程序和机器之间的数据传输。

Gob序列化流程

其调用流程和json类似

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
// gob1.go
package main

import (
"bytes"
"fmt"
"encoding/gob"
"log"
)

type P struct {
X, Y, Z int
Name string
}

type Q struct {
X, Y *int32
Name string
}

func main() {
// Initialize the encoder and decoder. Normally enc and dec would be
// bound to network connections and the encoder and decoder would
// run in different processes.
var network bytes.Buffer // Stand-in for a network connection
enc := gob.NewEncoder(&network) // Will write to network.
dec := gob.NewDecoder(&network) // Will read from network.
// Encode (send) the value.
err := enc.Encode(P{3, 4, 5, "Pythagoras"})
if err != nil {
log.Fatal("encode error:", err)
}
// Decode (receive) the value.
var q Q
err = dec.Decode(&q)
if err != nil {
log.Fatal("decode error:", err)
}
fmt.Printf("%q: {%d,%d}\n", q.Name, *q.X, *q.Y)
}
// Output: "Pythagoras": {3,4}

该例子是以字节缓冲模拟网络传输的简单例子

go的内置定时器的使用

  • 在go的time包中一共提供了三种定时器的使用方式:
    • Timer:定时器,只执行一次
    • After():定时器,只执行一次,返回一个通道,可以接收定时器的执行结果
    • Tick:定时器,周期性执行,返回一个通道,可以接收定时器的执行结果

Ticker定时器

  • 使用方式如下:
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"
"time"
)

func main() {
ticker := time.NewTicker(time.Second * 1) // 创建一个Ticker,并且设置时间间隔为1秒
fmt.Println("start")
ch := make(chan int)
go func() {
var x int
for x < 10 {
select {
case <-ticker.C:
x++
fmt.Printf("%d\n", x)
}
}
ticker.Stop()
ch <- 0
}()
<-ch // 通过通道阻塞,让任务可以执行完指定的次数
}
  • 每次超时后,Ticker会向内部的管道发送当前时间,也就是说我们可以通过内部管道是否有新消息到来来判断是否超时。

Timer定时器

  • 使用方式如下:
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"
"time"
)

func main() {
Timer := time.NewTimer(time.Second * 1)
ch := make(chan int)

go func() {
x := 0
for {
select {
case <-Timer.C:
fmt.Println("Timer expired")
x++
if x <= 10 {
fmt.Println(x)
Timer.Reset(time.Second)
} else {
ch <- 0
}
}
}
}()
<-ch
}

  • 思路和Ticker类似,但是只执行一次

After()

  • 方法定义如下:
1
2
3
func After(d Duration) <-chan Time {
return NewTimer(d).C
}
  • 从定义来看after函数只是timer的语法糖

  • 使用方式如下:

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"
"time"
)

func main() {
ch := make(chan int)
go func() {
tt := time.NewTimer(time.Second * 2)
<-tt.C
fmt.Println("over.")

<-time.After(time.Second * 4)
fmt.Println("再等待4秒退出。tt 没有终止,打印出 over 后会看见在继续执行...")
tt.Stop()
t := <-time.After(time.Second * 2)
fmt.Println("tt.Stop()后, tt 仍继续执行,只是关闭了 tt.C 通道。", t)

ch <- 1
}()
<-ch
}

  • 在我看来After函数的出现是为了让使用timer的时候更方便,而不需要调用timer.Reset函数

  • 这种方式会阻塞当且协程,直到超时或接收到消息,有没有什么方法可以不阻塞,并且判断是否超市呢?

  • 以下方法便能做到

1
time.Now().After(t time.Time) bool  // 判断当前时间是否在t之后(可以做到不阻塞还能判断是否超时)

做项目遇到的一些问题

Map通过索引返回的是副本,改变副本结构体不会改变map中的结构体原数据

将一个变量声明为map[int]Task 类型,然后我想要通过索引修改Task结构体的内容(map[task.id].status = “done”)会出错提示"cannot assign to struct field value in map"
原因:Go 的行为: 在 Go 中,map[] 返回的是结构体的副本,因此无法直接修改 map 中的结构体字段。需要取出副本、修改后再放回,这与C++不一样

不同进程运行相同包下的不同源代码文件的一些现象

  1. 首先进程间的数据不共享,而且你在两个源文件定义的东西,都会在进程中被声明与初始化,但是你在一个进程中改变某值并不会影响另外一个值
    例如: 你在A.go中定义了numReduce初始化为10,然后你在A.go中修改为20,A.go运行在一个进程中,B.go在另外一个进程运行,获取A的值只能获得10,因为两个进程中A.go的numReduce的值是不共享的。
    同理使用单例模式也一样,在A进程中初始化了单例,在B进程再次获得单例的时候,还是会初始化一次,而且使用的是B进程内的变量
    我在写项目的时候并没有第一时间发现这个问题,导致找bug花费了不少时间

select 配合通道使用的时候的一些逻辑

  1. select 配合通道使用的时候,如果没有任何case可以执行,则会阻塞,直到有case可以执行
  2. select 配合通道使用的时候,如果有多个case都可以执行,则会随机选择一个执行
  3. 通道先有数据的情况下,会执行现有数据的case,然后退出select