后端——go系统学习笔记(不断更新中......)

本文详细介绍了Go语言中的切片、动态长度、容量(cap)、扩容与复制、哈希表操作、字符串处理、函数调用、接口、反射机制,以及for循环、range、map、select、channel、defer、panic和recover等关键概念。还讨论了文件、数据库和缓存的持久化方法。
摘要由CSDN通过智能技术生成

数组

固定大小

  1. 初始化
arr1 := [3]int{1, 2, 3}
arr2 := [...]int{1, 2, 3}
var arr3 []int
var arr4 [4]int

切片

长度是动态的

  1. 初始化
arr[0:3]
slice := []int{1,2,3}
slice := make([]int, 10)
  1. len和cap
  • len是获取切片、数组、字符串的长度——元素的个数
  • cap是获取切片的容量——切片底层数组的长度
res := [5]int{1, 2, 3}
res1 := res[1:4]
fmt.Println(res1)
fmt.Println("1111", cap(res1))

在这里插入图片描述
3. 用法

  • 扩容:append
  • 拷贝切片(后面再啃)

哈希表(后面再啃)

读取

//方法一
hash[key] = value
delete(hash,key)
//方法二
for k,v := range hask{
}

字符串

  1. 不可以直接修改,需要转换成字节数组
str := "hello"
newStr := []byte(str)
fmt.Println(str)
fmt.Println(newStr)

在这里插入图片描述

函数调用

接口

Go 语言中的接口是隐式实现的,也就是说,如果一个类型实现了一个接口定义的所有方法,那么它就自动地实现了该接口。因此,我们可以通过将接口作为参数来实现对不同类型的调用,从而实现多态。

type Shape interface {
	area() float64
}
type Rectangle struct {
	width  float64
	height float64
}

func (r Rectangle) area() float64 {
	return r.width * r.height
}

反射

  1. 三大法则
  • 从 interface{} 变量可以反射出反射对象;
  • 从反射对象可以获取 interface{} 变量;
  • 要修改反射对象,其值必须可设置;

第一法则

举例:

func main() {
	author := "draven"
	fmt.Println("TypeOf author:", reflect.TypeOf(author))
	fmt.Println("ValueOf author:", reflect.ValueOf(author))
}

在这里插入图片描述
对于不同的类型,我们也可以调用不同的方法获取相关信息:

  • 结构体:获取字段的数量并通过下标和字段名获取字段 StructField;
  • 哈希表:获取哈希表的 Key 类型;
  • 函数或方法:获取入参和返回值的类型;

第二法则

举例:

func main() {
	i := 1
	v := reflect.ValueOf(&i)
	v.Elem().SetInt(10)
	fmt.Println(i)
}

$ go run reflect.go
10
  • 调用 reflect.ValueOf 获取变量指针;
  • 调用 reflect.Value.Elem 获取指针指向的变量;
  • 调用 reflect.Value.SetInt 更新变量的值:
    由于 Go 语言的函数调用都是值传递的,所以我们只能只能用迂回的方式改变原变量:
    先获取指针对应的 reflect.Value,再通过 reflect.Value.Elem 方法得到可以被设置的变量,我们可以通过下面的代码理解这个过程:
func main() {
	i := 1
	v := &i
	*v = 10
}

如果不能直接操作 i 变量修改其持有的值,我们就只能获取 i 变量所在地址并使用 *v 修改所在地址中存储的整数。

举例理解reflect.TypeOf()和 reflect.ValueOf():

type User struct {
	Username string `json:"username" db:"user_name"`
	Age      int    `json:"age" db:"user_age"`
}
user := User{
	Username: "john_doe",
	Age:      25,
}

取出tag值的两种方法:

userType := reflect.TypeOf(user)

for i := 0; i < userType.NumField(); i++ {
    field := userType.Field(i)
    s, _ := userType.FieldByName(field.Name)
    fmt.Println("s:", s)
    tag := field.Tag
    fmt.Printf("Field: %s, Tag: %s\n", field.Name, tag)
}

这段代码通过 reflect.TypeOf 获取了结构体 user 的类型,然后使用 userType.Field(i) 获取结构体的每个字段的信息。注意 s, _ := userType.FieldByName(field.Name) 这一行代码是多余的,因为 field 已经是该字段的信息了。最后,通过 field.Tag 获取字段的标签信息。

userValue := reflect.ValueOf(user)

for i := 0; i < userValue.NumField(); i++ {
    field := userValue.Field(i)
    tag := userValue.Type().Field(i).Tag
    fmt.Printf("field: %s,Tag: %s,Value: %v \n", userValue.Type().Field(i).Name, tag, field.Interface())
}

这段代码通过 reflect.ValueOf 获取了结构体 user 的值,然后使用 userValue.Field(i) 获取结构体的每个字段的值。通过 userValue.Type().Field(i).Tag 获取字段的标签信息。

常用关键字

for 和 range

hash := map[string]int{
	"1": 1,
	"2": 2,
	"3": 3,
}
for k, v := range hash {
	println(k, v)
}

在这里插入图片描述
在 Go 语言中,map 是无序的数据结构。这意味着,遍历 map 的时候,元素的顺序是不确定的,每次遍历可能得到的结果顺序是不同的。

将键值提取出来进行排序:

hash := map[string]int{
	"1": 1,
	"2": 2,
	"3": 3,
}

// 提取键
keys := make([]string, 0, len(hash))
for k := range hash {
	keys = append(keys, k)
}

// 按照键进行排序
sort.Strings(keys)

// 遍历排序后的键
for _, k := range keys {
	v := hash[k]
	fmt.Println(k, v)
}

select

  • 创建channel
// 非带缓冲通道
unbufferedChannel := make(chan int)

// 带缓冲通道,缓冲大小为 3
bufferedChannel := make(chan string, 3)
  • 包含 Channel 收发操作的 select 结构:
func fibonacci(c, quit chan int) {
	x, y := 0, 1
	for {
		select {
		case c <- x:
			x, y = y, x+y
		case <-quit:
			fmt.Println("quit")
			return
		}
	}
}
func main() {
	c := make(chan int, 1)
	quit := make(chan int, 1)
	quit <- 42 // 将值 42 发送到通道 quit,创建了一个新的 Go 协程来异步发送值到通道
	go func() {
		quit <- 42 // 将值 42 发送到通道 quit
	}()
	fibonacci(c, quit)
}

select 是与 switch 相似的控制结构,与 switch 不同的是,select 中虽然也有多个 case,但是这些 case 中的表达式必须都是 Channel 的收发操作。
上述控制结构会等待 c <- x 或者 <-quit 两个表达式中任意一个返回。无论哪一个表达式返回都会立刻执行 case 中的代码,当 select 中的两个 case 同时被触发时,会随机执行其中的一个。
在这里插入图片描述
在这里插入图片描述

  • select 的作用是同时监听多个 case 是否可以执行,如果多个 Channel 都不能执行,那么运行 default 也是理所当然的。
  • 解释下面代码:
errCh := make(chan error, len(tasks))
wg := sync.WaitGroup{}
wg.Add(len(tasks))
for i := range tasks {
    go func() {
        defer wg.Done()
        if err := tasks[i].Run(); err != nil {
            errCh <- err
        }
    }()
}
wg.Wait()

select {
case err := <-errCh:
    return err
default:
    return nil
}
  • 解释
    • errCh := make(chan error, len(tasks)): 创建一个带有缓冲区大小为 len(tasks)
      的错误通道,用于收集任务执行过程中产生的错误。

    • wg := sync.WaitGroup{}: 创建一个 WaitGroup,用于等待所有任务完成。在开始执行任务之前,通过 wg.Add(len(tasks)) 增加计数器的值,表示有多少个任务需要等待。

    • for i := range tasks { go func() {…}()}: 遍历任务列表,并为每个任务启动一个 goroutine。每个 goroutine 都会调用任务的 Run 方法,并在执行完成后通过 defer wg.Done() 减少
      WaitGroup 计数器,表示一个任务已经完成。如果任务执行过程中发生错误,则将错误发送到 errCh 通道。

    • wg.Wait(): 主线程等待所有任务完成。WaitGroup 的计数器会在每个任务完成时减少,直到所有任务完成后,Wait 才会返回。

    • select {…}: 使用 select 语句来等待 errCh 中的错误。如果有错误发生,将其返回;否则,返回 nil 表示所有任务均执行成功。

    • 这样的设计允许并发执行任务,并在任何一个任务发生错误时中止执行,并返回第一个发生的错误。需要注意的是,由于 select 语句只会选择一个通信操作执行,所以只会返回第一个发生的错误,其他可能在通道中的错误会被忽略。

defer

  • 举例一
func main() {
	for i := 0; i < 5; i++ {
		defer fmt.Println(i)
	}
}

$ go run main.go
4
3
2
1
0

运行上述代码会倒序执行传入 defer 关键字的所有表达式,因为最后一次调用 defer 时传入了 fmt.Println(4),所以这段代码会优先打印 4。

  • 举例二
func main() {
    {
        defer fmt.Println("defer runs")
        fmt.Println("block ends")
    }
    
    fmt.Println("main ends")
}

$ go run main.go
block ends
main ends
defer runs

从上述代码的输出我们会发现,defer 传入的函数不是在退出代码块的作用域时执行的,它只会在当前函数和方法返回之前被调用。## panic 和 recover

make 和 new

一些还不知道怎么分类的细碎知识点

1、

fmt.Printf("成员的值:%v\n", user)
fmt.Printf("成员的名称和值:%+v\n", user)
fmt.Printf("结构体名称、成员的名称和值:%#v\n", user)

输出:

成员的值:{john_doe 25}
成员的名称和值:{Username:john_doe Age:25}
结构体名称、成员的名称和值:main.User{Username:"john_doe", Age:25}

2、封装、继承和多态

  • 封装(Encapsulation): Go 支持通过首字母大小写来控制成员或方法的可见性,从而实现简单的封装。首字母大写的标识符是可导出的,可以在包外部访问。
  • 继承(组合):
    不支持经典的类继承,支持组合
type Address struct {
    City  string
    State string
}
type Person struct {
    Name    string
    Age     int
    Address Address
}
  • 接口:接口定义了一组方法的集合,任何类型只要实现了接口中定义的方法,就被认为是实现了该接口。接口提供了一种方式来实现多态。
type Speaker interface {
    Speak()
}
func (p *Person) Speak() {
    fmt.Printf("Hello, my name is %s and I'm %d years old.\n", p.Name, p.Age)
}

3、go什么时候发生阻塞

  • 通道阻塞: 当往一个已经满(没有缓冲区或缓冲区已满)的通道发送数据,或者从一个空通道接收数据时,会发生阻塞。
ch := make(chan int, 1) // 创建一个有一个缓冲区的通道

// 发送数据,通道不会阻塞
ch <- 42

// 再次尝试发送数据,此时通道已满,发生阻塞
ch <- 23
ch := make(chan int) // 创建一个无缓冲区的通道

// 尝试接收数据,但通道是空的,发生阻塞
data := <-ch
  • 互斥锁阻塞: 当一个 Goroutine 获得了互斥锁,而另一个 Goroutine 尝试获取相同的锁时,后者会发生阻塞。
var mu sync.Mutex

// Goroutine 1
go func() {
    mu.Lock()
    defer mu.Unlock()
    // 执行一些操作
}()

// Goroutine 2
go func() {
    mu.Lock() // 阻塞,直到 Goroutine 1 释放锁
    defer mu.Unlock()
    // 执行一些操作
}()
  • Select 语句阻塞: 在 select 语句中,如果所有的 case 都无法执行,且没有默认分支,那么 select 将会阻塞,直到某个 case 可以执行。
ch1 := make(chan int)
ch2 := make(chan int)

select {
case <-ch1:
    // ch1 可以执行
case <-ch2:
    // ch2 可以执行
default:
    // 所有的 case 都无法执行,发生阻塞
}

4、请举例recover捕获panic的例子

被 defer 关键字修饰的语句会在包含 defer 的函数或方法执行完毕后才执行,即使函数中途发生了 return、panic 或者运行时错误。

package main

import "fmt"

func main() {
    exampleFunction()
    fmt.Println("Program continues...")
}

func exampleFunction() {
    // 使用 defer 来注册一个匿名函数,其中包含 recover() 来捕获 panic
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered from panic:", r)
        }
    }()

    // 一些代码,可能导致 panic
    doSomething()
}

func doSomething() {
    // 故意触发 panic
    panic("Oops! Something went wrong.")
}

5、golang持久化怎么做的

  • 文件持久化:
    • 文件读写(I/O): 使用 os 包提供的文件读写功能,可以将数据以文本或二进制的形式存储到文件中。这适用于小型数据。

    • JSON 或其他格式序列化: 使用 encoding/json 包将数据序列化为 JSON 格式,然后写入文件,或者使用其他格式(如 XML、YAML)。

// 例子:将数据以 JSON 格式写入文件
package main

import (
    "encoding/json"
    "os"
)

type Data struct {
    Name  string `json:"name"`
    Value int    `json:"value"`
}

func main() {
    data := Data{"example", 42}

    file, err := os.Create("data.json")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    encoder := json.NewEncoder(file)
    if err := encoder.Encode(data); err != nil {
        panic(err)
    }
}
  • 数据库持久化:
    • 使用 SQL 数据库: 使用 database/sql 包,结合相应的数据库驱动,如 github.com/go-sql-driver/mysql、github.com/lib/pq 等,进行 SQL 数据库的连接、查询和持久化操作。
  • 缓存持久化:
    • 使用缓存数据库: Golang 中有一些流行的缓存数据库驱动,如 github.com/go-redis/redis(Redis)等。
  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值