数组
固定大小
- 初始化
arr1 := [3]int{1, 2, 3}
arr2 := [...]int{1, 2, 3}
var arr3 []int
var arr4 [4]int
切片
长度是动态的
- 初始化
arr[0:3]
slice := []int{1,2,3}
slice := make([]int, 10)
- 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{
}
字符串
- 不可以直接修改,需要转换成字节数组
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
}
反射
- 三大法则
- 从 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)等。