Golang入门

一、关键字

Go语言中类似if和switch的关键字有25个,不能用于自定义名字,只能在特定语法结构中使用。

break      default       func     interface   select
case       defer         go       map         struct
chan       else          goto     package     switch
const      fallthrough   if       range       type
continue   for           import   return      var

二、基本数据类型

类型分类Go语言类型默认值Java对应类型Java包装类备注
布尔型boolfalsebooleanBoolean
整数类型int0intInteger平台相关(32或64位)
int80byteByteGo的int8范围同Java的byte(-128~127)
int160shortShort
int320intInteger
int640longLong
uint0无直接对应Go特有无符号整数
uint80无符号需求时用intGo的uint8常用作字节
uint16/uint32/uint640无直接对应
byte0byteBytebyteuint8的别名
rune0charCharacterruneint32的别名,表示Unicode码点
浮点型float320floatFloat
float640doubleDouble
复数类型complex64(0+0i)无直接对应Go特有
complex128(0+0i)无直接对应
字符串string""(空字符串)StringStringJava的String是类而非基本类型
指针指针类型(如*intnil引用类型Go指针更类似C/C++
其他引用类型slicenilArrayList/数组
mapnilHashMap
channelnil无直接对应Go特有
functionnil函数式接口
interfacenilObject/接口

三、声明常量和变量

1、常量(const关键字)

//声明一个常量
const a = 1

//声明多个常量
const (
	a = 1
	b = true
)

//声明可导出的常量
const (
	D = 10
	E = false
)

/*
常量的访问范围:
在 Go 中,常量的访问范围由其 首字母大小写 决定:
首字母大写:常量是 导出的(exported),可以被其他文件访问。
首字母小写:常量是 未导出的(unexported),只能在定义它的文件中访问。
*/

2、变量(格式:var 变量名字 类型 = 表达式)

//声明一个变量
var s string

//声明单个变量并初始化
var a int = 1
var b bool = true

//声明多个变量并初始化
var a, b = 10, false

//简短变量声明
 a, b := 10, false

//变量也可以调用函数并初始化
freq := rand.Float64() * 3.0

/*
1、在包级别声明的变量会在main入口函数执行前完成初始化,局部变量将在声明语句被执行到时完成初始化。
2、“:=”是一个变量声明语句,而“=”是一个变量赋值操作。
3、简短变量声明语句也可以用函数的返回值来声明和初始化变量
4、假设一个变量在上面已经声明了,那么下面针对这个变量的操作就是“赋值”行为
*/

3、指针(&表示取地址,*表示取值)

//示例1:&代表取地址,*代表取值
func main() {
	x := 1
	// 把x的地址赋值给p,相当于取x变量的内存地址。或者说“p指针保存了x变量的内存地址”
	p := &x
	fmt.Println(*p) // 那么*p就是x的值,输出"1"
	*p = 2          // 通过指针修改x的值
	fmt.Println(x)  // 那么x的值变成"2"
}
//示例2:
func main() {
	var x, y int
	fmt.Println(&x == &x)  // "true"   变量x地址相等
	fmt.Println(&x == &y)  // "false"  变量x,y地址不相等
	fmt.Println(&x == nil) // "false"  变量x地址不为空
}

//示例3:在Go语言中,返回函数中局部变量的地址也是安全的。例如下面的代码,调用f函数时创建局部变量v,在局部变量地址被返回之后依然有效,因为指针p依然引用这个变量。
func f() *int {
	v := 1
	return &v
}
func main() {
	fmt.Println(f() == f()) // "false" 这里调用了两次f函数,虽然都是变量v,但是地址不一样
}

//示例4:指针包含了一个变量的地址,如果将指针作为参数调用函数,就可以在函数中通过该指针来更新变量的值。
func incr(p *int) int { //这里只改变了参数的变量值,并不改变p指针
	*p++ // 非常重要:只是增加p指向的变量的值,并不改变p指针!!!
	return *p
}
func main() {
	v := 1
	incr(&v)
	fmt.Println(v) // 所以这里第一次输出2
	fmt.Println(incr(&v)) // 在此调用+1输出了3
}

/*
1、变量有时候被称为可寻址的值。即使变量由表达式临时生成,那么表达式也必须能接受&取地址操作。
2、任何类型的指针的零值都是nil。如果p指向某个有效变量,那么p != nil测试为真。指针之间也是可以进行相等测试的,只有当它们指向同一个变量或全部是nil时才相等。
*/

4、new函数(不是关键字)

new函数是创建变量的另一种方式,类似于一种语法糖,表达式new(T)将创建一个T类型的匿名变量,初始化为T类型的零值,然后返回变量地址,返回的指针类型为*T。注意:由于new只是一个预定义的函数,它并不是一个关键字,因此我们可以将new名字重新定义为别的类型。

func main() {
	p := new(int)   // 初始化变量p, 数据类型为int 类型,默认值为0
	fmt.Println(*p) // "0" 
	*p = 2          // 设置 int 匿名变量p的值为 2
	fmt.Println(*p) // "2" 
}

5、变量的生命周期

包一级:在包一级声明的变量来说,它们的生命周期和整个程序的运行周期是一致的。

局部变量:局部变量的生命周期则是动态的,每次从创建一个新变量的声明语句开始,直到该变量不再被引用为止,然后变量的存储空间可能被回收。函数的参数变量和返回值变量都是局部变量,它们在函数每次被调用的时候创建。

垃圾回收:从每个包级的变量和每个当前运行函数的每一个局部变量开始,通过指针或引用的访问路径遍历,是否可以找到该变量。如果不存在这样的访问路径,那么说明该变量是不可达的,也就是说它是否存在并不会影响程序后续的计算结果。(注意:和JVM的JIT逃逸分析类似,变量如果逃逸到函数之外,一般情况下会在栈上分配,也可以选择在堆上分配,然后由Go语言的GC回收这个变量的内存空间。未逃逸的就会在堆上分配。)

四、赋值

//示例1:最简单的赋值语句是将要被赋值的变量放在=的左边,新值的表达式放在=的右边。
func main() { //自增和自减是语句,而不是表达式
    var v int
	v = 1
	fmt.Println(v) // 输出 1
	v++            // 等价方式 v = v + 1;v 变成 2
	fmt.Println(v) // 输出 2
	v--            // 等价方式 v = v - 1;v 变成 1
	fmt.Println(v) // 输出 1
}
func main() { //直接交换x,y的值
	x, y := 3, 4
	fmt.Println("x:", x, "y:", y) // x: 3 y: 4
	x, y = y, x
	fmt.Println("x:", x, "y:", y) // x: 4 y: 3
}

//示例2:元组赋值
func main() { //直接交换a[0], a[1]的值
	a := []int{1, 2, 3, 4, 5}
	a[0], a[1] = a[1], a[0]
	fmt.Println(a) // 输出 [2 1 3 4 5]
}

func gcd(x, y int) int {
	for y != 0 {
		x, y = y, x%y
	}
	return x
}
func main() {
	fmt.Println("48 and 18 的最大公约数:", gcd(48, 18)) // 输出: 6
}

//示例3:多返回值必须用相同数量的变量接收,也就是左右变量数目一致,可以用下划线空白标识符_来丢弃不需要的值
func test() (int, int) {
	return 3, 4
}
func main() {
	a, b := test()
	fmt.Println(a, b) // 输出: 3 4
}


//示例4:显式赋值
func main() {
	medals := []string{"gold", "silver", "bronze"}
	// Medal 1: gold
	// Medal 2: silver
	// Medal 3: bronze
	for i, medal := range medals {
		fmt.Printf("Medal %d: %s\n", i+1, medal)
	}
}
func main() {
	medals := [3]string{}
	medals[0] = "gold"
	medals[1] = "silver"
	medals[2] = "bronze"
	// Medal 1: gold
	// Medal 2: silver
	// Medal 3: bronze
	for i, medal := range medals {
		fmt.Printf("Medal %d: %s\n", i+1, medal)
	}
}

    五、复合数据类型

    1、数组

    数组的长度需要在编译阶段确定,一般是下面这种顺序初始化值

    //声明数组有多种方式
    func main() {
    	//数组a
    	a := []int{1, 2, 3}
    	fmt.Println(a) // [1 2 3]
    	//数组b
    	b := [4]int{1, 2, 3, 4}
    	fmt.Println(b) // [1 2 3 4]
    	//数组c
    	c := [...]int{1, 2, 3, 4}
    	fmt.Println(c) // [1 2 3 4]
    	//数组d
    	d := make([]int, 4)
    	d[0] = 1
    	d[1] = 2
    	d[2] = 3
    	fmt.Println(d) // [1 2 3 0]
    }

    也可以指定一个索引和对应值列表的方式初始化

    type Currency int
    
    const (
    	USD Currency = iota // 美元 跳过0
    	EUR                 // 欧元 应该为1
    	GBP                 // 英镑 应该为2
    	RMB                 // 人民币 应该为3
    )
    
    func main() {
    	symbol := [...]string{USD: "$", EUR: "€", GBP: "£", RMB: "¥"}
    	fmt.Println(RMB, symbol[RMB]) // 输出:"3 ¥"
    }

    初始化索引的顺序是无关紧要的,而且没用到的索引可以省略,和前面提到的规则一样,未指定初始值的元素将用零值初始化

    //定义了一个含有10个元素的数组r,最后一个元素被初始化为-1,其它元素都初始化为0
    func main() {
    	r := [...]int{9: -1}
    	fmt.Println(r) // 输出: [0 0 0 0 0 0 0 0 0 -1]
    }

    当调用一个函数的时候,传递的是参数的副本,并不是原始对象,也就是说Go语言是值传递,并且Go语言传递大数组的效率比较低。所以在Go语言中,传递数组有以下三种方式:

    //1、使用指针
    func modifyArrayByPointer(arr *[3]int) {
    	(*arr)[0] = 100 // 或 arr[0] = 100(语法糖)
    }
    
    func main() {
    	arr := [3]int{1, 2, 3}
    	modifyArrayByPointer(&arr)
    	fmt.Println(arr) // [100 2 3]
    }
    
    //2、使用指针数组
    // 对于非常大的数组, 使用指针传递以避免拷贝开销。这里为了测试设置比较小
    func processLargeArray(arr *[5]int) {
    	// 通过指针操作,避免拷贝
    	for i := range arr {
    		arr[i] = arr[i] * 2
    	}
    }
    
    func main() {
    	arr := [5]int{1, 2, 3}
    	processLargeArray(&arr)
    	fmt.Println(arr) // [2 4 6 0 0]
    }
    
    //3、使用切片slice
    func modifySlice(s []int) {
    	if len(s) > 0 {
    		s[0] = 100 // 修改原始数据
    	}
    }
    
    func main() {
    	arr := [3]int{1, 2, 3}
    	slice := arr[:] // 创建指向数组的切片
    	modifySlice(slice)
    	fmt.Println(arr) // 输出 [100 2 3]
    }

    2、Slice(切片--引用类型)

    切片是一个轻量级的动态数组抽象,它不存储数据本身,而是:

    • 指向底层数组的指针(slice并没有指明序列的长度,而是会隐式地创建一个合适大小的数组,然后slice的指针指向底层的数组,返回的都是原始数组序列的子序列)

    • 长度(当前元素个数)

    • 容量(最大可容纳元素个数)

    • slice是间接引用,因此我们不能使用==操作符来判断两个slice是否含有全部相等元素,唯一合法的比较操作是和nil比较

    func nonempty(strings []string) []string {
    	i := 0
    	for _, s := range strings {
    		if s != "" {
    			strings[i] = s
    			i++
    		}
    	}
    	return strings[:i]
    }
    
    func main() {
    	// 1. 从数组创建
    	arr := [5]int{1, 2, 3, 4, 5}
    	slice1 := arr[1:4]
    	fmt.Println(slice1) // [2, 3, 4]
    
    	// 2. 直接创建
    	slice2 := []int{1, 2, 3}
    	fmt.Println(slice2) // [1 2 3]
    
    	// 3. 使用make函数
    	slice3 := make([]int, 3, 5) // 长度3,容量5
    	fmt.Println(slice3)         // [0 0 0]
    
    	// 4. 从切片创建(共享底层数组)
    	slice4 := slice1[1:3]
    	fmt.Println(slice4) // [3 4]
    
    	// 5. 不同方式创建空切片,并检查其长度和nil状态
    	var slice5 []int         // len(slice5) == 0, slice5 == nil
    	slice5 = nil             // len(slice5) == 0, slice5 == nil
    	slice5 = []int(nil)      // len(slice5) == 0, slice5 == nil
    	slice5 = []int{}         // len(slice5) == 0, slice5 != nil
    	fmt.Println(len(slice5)) // 切片长度为:0
    
    	// 6. slice之间不能比较,不能使用==操作符来判断两个slice是否含有全部相等元素
    	// if slice1 == slice2 { // 编译错误:切片不能比较
    	//     fmt.Println("slice1 和 slice2 相等")
    	// }
    
    	// 7. 使用append函数追加元素
    	slice6 := []int{1, 2, 3}
    	slice6 = append(slice6, 4, 5)
    	fmt.Println(slice6) // [1 2 3 4 5]
    
    	// 8. 使用copy函数复制切片
    	src := []int{1, 2, 3}  // 源切片
    	dest := make([]int, 2) // 目标切片,长度为2
    	n := copy(dest, src)   // 复制元素
    	fmt.Println(n, dest)   // 2 [1 2]
    
    	// 9. 使用nonempty函数过滤空字符串
    	data := []string{"one", "", "three"}
    	// fmt.Printf("%q\n", nonempty(data)) // 输出 ["one" "three"]
    	// fmt.Printf("%q\n", data)           // 输出 ["one" "three" "three"]
    	data = nonempty(data)    // 重新赋值以保留过滤结果
    	fmt.Printf("%q\n", data) // 输出 ["one" "three"]
    
    	// 10. 一个slice可以用append函数来模拟一个stack
    	var stack []int
    	stack = append(stack, 1) // 入栈
    	stack = append(stack, 2) // 入栈
    	fmt.Println(stack)       // [1 2]
    
    	x := stack[len(stack)-1] // 取出栈顶元素
    	stack = stack[:len(stack)-1] // 出栈
    	fmt.Println(x)           // 2
    	fmt.Println(stack)       // [1]
    
    	// 11. 遍历slice
    	slice7 := []int{10, 20, 30}
    	for i, v := range slice7 {
    		fmt.Printf("索引 %d 的值是 %d\n", i, v)
    		// 索引 0 的值是 10
    		// 索引 1 的值是 20
    		// 索引 2 的值是 30
    	}
    }

    3、Map(并发不安全--引用类型)

    • Map是引用类型,零值为nil

    • 键必须是可比较的类型,值可以是任意类型

    • Map不是并发安全的,需要使用sync.Mutex或sync.Map

    • 遍历顺序是随机的

    • 使用delete()删除元素,删除不存在的键不会报错

    • 获取不存在的键值,可能会返回0值,需要先判断是否存在

    • Go的Map底层是哈希表实现:
      - 多个桶(bucket),每个桶存储8个键值对
      - 使用链地址法解决哈希冲突
      - 负载因子达到6.5时触发扩容
      - 扩容时渐进式rehash

    func main() {
    	// 声明方式1:使用make函数
    	var m1 map[string]int     // 声明,此时m1为nil
    	m1 = make(map[string]int) // 初始化
    	m1["key1"] = 100
    	fmt.Println(m1["key1"]) // 输出: 100
    
    	// 声明方式2:字面量初始化
    	m2 := map[string]int{
    		"apple":  5,
    		"banana": 3,
    		"orange": 2,
    	}
    	fmt.Println(m2["banana"]) // 输出: 3
    
    	// 声明方式3:指定初始容量
    	m3 := make(map[string]int, 10) // 预分配容量,提升性能
    	m3["item1"] = 50
    	fmt.Println(m3["item1"]) // 输出: 50
    
    	// 空map可以读取,但不能写入
    	// m4["key"] = 1  // 运行时panic: assignment to entry in nil map
    	var m4 map[string]int
    	fmt.Println(m4 == nil)         // true
    	fmt.Println(len(m4))           // 0
    	fmt.Println(m4["nonexistent"]) // 读取不存在的键,返回零值: 0
    	// m4是一个空指针,没有指向任何有效的hmap结构体。写入操作需要操作这个结构体的内部字段(buckets、count等),而nil指针无法安全访问这些字段。
    	//m4["nonexistent"] = 1 // 运行时panic: assignment to entry in nil map
    
    	// 添加/修改
    	m5 := make(map[string]int)
    	m5["apple"] = 5
    	m5["banana"] = 3
    	m5["apple"] = 10 // 修改已有键的值
    	// 查询
    	value := m5["apple"]
    	fmt.Println(value) // 10
    	// 检查键是否存在
    	value, exists := m5["orange"]
    	if exists {
    		fmt.Println("orange:", value)
    	} else {
    		fmt.Println("orange不存在") // orange不存在
    	}
    	// 删除
    	delete(m5, "banana")
    	fmt.Println(m5)           // map[apple:10]
    	fmt.Println(m5["banana"]) // 0 由于int等基本类型会初始化为0值,导致这里即使删除了banana,依旧返回0,这是不符合预期的
    
    	// 这里用if判断是否存在key
    	if v, exists := m5["banana"]; exists {
    		fmt.Println(v)
    	} else {
    		fmt.Println("m5中不存在key banana")
    	}
    	delete(m5, "nonexistent") // 删除不存在的键不会报错
    	// 获取长度
    	fmt.Println("map长度:", len(m5)) // map长度: 1
    
    	// 遍历
    	m6 := map[string]string{
    		"red":   "#FF0000",
    		"green": "#00FF00",
    		"blue":  "#0000FF",
    	}
    	// 遍历所有键值对(顺序随机)
    	for key, value := range m6 {
    		fmt.Printf("%s: %s\n", key, value) // red: #FF0000  green: #00FF00  blue: #0000FF (顺序可能不同)
    	}
    	// 只遍历键
    	for key := range m6 {
    		fmt.Println("Key:", key) // red, green, blue
    	}
    	// 只遍历值
    	for _, value := range m6 {
    		fmt.Println("Value:", value) // #FF0000, #00FF00, #0000FF
    	}
    }
    
    // 有效的键类型(可比较的类型):
    // - 所有基本类型(除slice、map、function)
    // - 数组(如果元素类型可比较)
    // - 结构体(如果所有字段可比较)
    // - 指针
    // - 接口(如果动态值可比较)
    
    // 无效的键类型(会导致编译错误):
    // - slice
    // - map
    // - function
    // - 包含上述类型的结构体
    
    type Point struct {
    	X, Y int
    }
    
    func main() {
    	m := make(map[Point]string)
    	m[Point{1, 2}] = "位置1"
    	m[Point{3, 4}] = "位置2"
    	println(m[Point{1, 2}]) // 输出: 位置1
    	println(m[Point{3, 4}]) // 输出: 位置2
    }
    // 并发写会出现安全性问题
    func main() {
    	// 创建一个普通的 map
    	m := make(map[int]int)
    
    	// 两个 goroutine 并发写入同一个 map
    	go func() {
    		for i := 0; i < 1000; i++ {
    			m[i] = i
    		}
    	}()
    
    	go func() {
    		for i := 1000; i < 2000; i++ {
    			m[i] = i
    		}
    	}()
    
    	time.Sleep(time.Second)
    	// 可能触发:fatal error: concurrent map writes
    }
    
    // 方案1:使用sync.Mutex(推荐用于复杂场景)
    // 结构体
    type SafeMap struct {
    	sync.RWMutex
    	m map[string]int
    }
    // 构造函数:确保 map 被正确初始化
    func NewSafeMap() *SafeMap {
    	return &SafeMap{
    		m: make(map[string]int),
    	}
    }
    // set函数:设置键值对
    func (sm *SafeMap) Set(key string, value int) {
    	// 写锁保护
    	sm.Lock()
    	// 确保在函数退出时释放锁
    	defer sm.Unlock()
    	// 设置值
    	sm.m[key] = value
    }
    // get函数:获取键对应的值
    func (sm *SafeMap) Get(key string) (int, bool) {
    	// 读锁保护
    	sm.RLock()
    	// 确保在函数退出时释放锁
    	defer sm.RUnlock()
    	// 获取值
    	value, exists := sm.m[key]
    	// 返回值和是否存在
    	return value, exists
    }
    // 示例使用
    func main() {
    	// 创建 SafeMap 实例
    	sm := NewSafeMap()
    	// 设置和获取值
    	sm.Set("test", 42)
    	// 获取值
    	if val, ok := sm.Get("test"); ok {
    		println(val) // 输出 42
    	}
    }
    
    // 方案2:使用sync.Map(适用于读多写少场景)
    func main() {
    	// 创建一个 sync.Map
    	var syncMap sync.Map
    	// 存储
    	syncMap.Store("name", "测试")
    	// 加载
    	value, ok := syncMap.Load("name")
    	fmt.Println(value, ok) // 输出: 测试 true
    	value, ok = syncMap.Load("age")
    	fmt.Println(value, ok) // 输出: <nil> false
    	// 加载或存储
    	actual, loaded := syncMap.LoadOrStore("name", "新值")
    	fmt.Println(actual, loaded) // 输出: 测试 true
    	actual, loaded = syncMap.LoadOrStore("age", 30)
    	fmt.Println(actual, loaded) // 输出: 30 false
    	// 更新
    	syncMap.Store("name", "更新后的值")
    	updatedValue, _ := syncMap.Load("name")
    	fmt.Println(updatedValue) // 输出: 更新后的值
    	// 删除
    	syncMap.Delete("name") // 删除键为 "name" 的项
    	// 遍历
    	syncMap.Range(func(key, value interface{}) bool {
    		fmt.Println("key:", key, "value:", value) // 输出: key: age value: 30
    		return true
    	})
    }
    

    4、结构体(深/浅拷贝)

    • 访问/修改字段:使用点操作符 .
    • 通过指针访问:Go 自动解引用
    • 如果所有字段都可比较(如基本类型、数组等,引用类型不可以),则结构体本身也可比较
    • 结构体不支持传统继承,嵌入实现组合复用(也就是常说的组合大于继承)
    // User 声明一个可导出的结构体
    type User struct {
    	Name    string
    	Age     int
    	Sex     string
    	Address Address // 嵌套结构体
    	Address2        // 匿名成员
    }
    
    type Address struct {
    	City    string
    	Country string
    }
    type Address2 struct {
    	City    string
    	Country string
    }
    
    // DeepCopyJSON 深拷贝函数
    func DeepCopyJSON(dst, src interface{}) error {
    	// 序列化
    	data, err := json.Marshal(src)
    	if err != nil {
    		return err
    	}
    	// 反序列化到新对象
    	err = json.Unmarshal(data, dst)
    	return err
    }
    func main() {
    	// 结构体实例
    	user := User{
    		Name: "张三",
    		Age:  18,
    		Sex:  "男",
    		Address: Address{
    			City:    "北京",
    			Country: "中国",
    		},
    	}
    	fmt.Println(user.Name) // 张三
    	fmt.Println(user.Age)  // 18
    	fmt.Println(user.Sex)  // 男
    	fmt.Println(user.Address.City)
    	// 通过指针访问结构体成员
    	user1 := &user
    	// 这里会修改原始成员变量:原因是默认浅拷贝,指针都指向同一个结构体
    	user1.Age = 19
    	fmt.Println(user1.Name) // 张三
    	fmt.Println(user.Age)   // 19
    	fmt.Println(user1.Age)  // 19
    	//1、手动深拷贝
    	user2 := User{
    		Name: user.Name,
    		Age:  user.Age,
    		Sex:  user.Sex,
    		Address: Address{
    			City:    user.Address.City,
    			Country: user.Address.Country,
    		},
    	}
    	user2.Age = 20
    	fmt.Println(user2.Age)          // 20
    	fmt.Println(user2.Address.City) // 北京
    	//2、使用结构体指针深拷贝
    	user3 := &user
    	user3.Age = 21
    	user3.Address.City = "上海"
    	fmt.Println(user3.Age)          //
    	fmt.Println(user3.Address.City) // 上海
    	//3、序列化深拷贝
    	var user4 User
    	err := DeepCopyJSON(&user4, user)
    	if err != nil {
    		return
    	}
    	user4.Age = 22
    	fmt.Println(user4.Age) // 22
    }

    5、JSON

    • Go 标准库 encoding/json 提供对 JSON 的编码(marshal) 和 解码(unmarshal) 支持
    • 解码时目标必须是指针,且JSON 中多余的字段会被自动忽略
    • 对于 HTTP 响应或大文件,推荐使用 json.Decoder / json.Encoder
    • 对可选字段使用 ,omitempty
    type Product struct {
    	ID          int       `json:"id"`
    	Name        string    `json:"name"`
    	Price       float64   `json:"price"`
    	Tags        []string  `json:"tags,omitempty"` // 为空时忽略,omitempty:当字段为零值(如 false, 0, nil, "", 空 slice)时,不输出该字段
    	CreatedAt   time.Time `json:"created_at"`
    	SecretField string    `json:"-"` // 不序列化
    }
    
    func main() {
    	product := Product{
    		ID:          1,
    		Name:        "product1",
    		Price:       9.99,
    		Tags:        []string{},
    		CreatedAt:   time.Now(),
    		SecretField: "secret",
    	}
    	// 编码
    	marshal, _ := json.Marshal(product)
    	println(string(marshal)) // {"id":1,"name":"product1","price":9.99,"created_at":"2025-12-17T16:57:38.654206+08:00"}
    }

    六、函数

    1、函数声明

    • Go 没有默认参数、不支持函数重载
    • 调用时必须提供所有参数,且顺序固定(不能按名传参)
    • 命名返回值在复杂函数中可提高可读性,但在简单函数中可能冗余
    • 裸返回(return)在函数较长时可能降低可读性,建议谨慎使用
    // 1、基本函数
    func hypot(x, y float64) float64 {
    	return math.Sqrt(x*x + y*y)
    }
    
    // 2、多返回值
    func safeSqrt(x float64) (float64, error) {
    	if x < 0 {
    		return 0, errors.New("negative number")
    	}
    	return math.Sqrt(x), nil
    }
    
    // 3、命名返回值
    func max(a, b int) (result int) {
    	if a > b {
    		result = a
    	} else {
    		result = b
    	}
    	return // 裸返回
    }
    
    // 4、忽略参数
    func alwaysReturnFirst(_ int, y int) int {
    	return y
    }
    
    // 5、引用类型参数可被修改
    func resetSlice(s []int) {
    	for i := range s {
    		s[i] = 0 // 修改原 slice 元素
    	}
    }
    
    //6、递归函数
    func recursion(n int) int {
    	if n <= 1 {
    		return 1
    	}
    	return n * recursion(n-1)
    }
    
    func main() {
    	fmt.Println(hypot(3, 4)) // 输出: 5
    
    	if v, err := safeSqrt(-4); err != nil {
    		fmt.Println("Error:", err) // 输出: Error: negative number
    	} else {
    		fmt.Println(v)
    	}
    
    	fmt.Println(max(10, 20))              // 输出: 20
    	fmt.Println(alwaysReturnFirst(1, 99)) // 输出: 99
    
    	nums := []int{1, 2, 3}
    	resetSlice(nums)
    	fmt.Println(nums) // 输出: [0 0 0]
    
        println(recursion(5)) // 输出:120
    }

    2、匿名函数

    • 可在函数内部定义(闭包)
    • 能捕获外部变量(形成闭包)
    /**
     * 闭包
     * 匿名函数
     * 参数: int类型 n
     * 返回: int类型 的匿名函数
     */
    func makeAdder(n int) func(int) int {
    	// 返回一个匿名函数
    	return func(x int) int {
    		return x + n // 捕获 n
    	}
    }
    func main() {
    	adder := makeAdder(10)
    	fmt.Println(adder(5)) // 15
    }

    3、可变参数函数

    (...T)  表示可以接受多个该类型的函数,实际接收为 []T 切片

    // 可变参数函数
    func sum(nums ...int) int {
    	total := 0
    	// 遍历数组
    	for _, n := range nums {
    		total += n
    	}
    	return total
    }
    func main() {
    	fmt.Println(sum(1, 2, 3, 4)) // 输出:10
    	nums := []int{1, 2, 3}
    	fmt.Println(sum(nums...)) // 输出:6
    }

    4、Deferred函数

    • defer 的注册顺序和输出顺序相反,先进后出(LIFO)

    • defer 注册的是 go 语句时,会直接计算好变量,注册匿名函数时去实时计算变量

    • defer 在临退出函数之前才会执行(常用于资源清理,例如关闭文件等)

    //示例1
    func foo() int {
    	a := 7
    	// 这里defer注册的时候,会直接注册a的值,和匿名函数不一样
    	defer fmt.Println("第一次defer:", a) // 第四个执行,输出:第一次defer 7
    	fmt.Println(a)                    // 第一个执行,输出:7
    	// 这里defer注册的时候,也会直接注册a的值,和匿名函数不一样
    	defer fmt.Println("第二次defer:", a) // 第三个执行,输出:第二次defer 7
    	defer func() {
    		// 匿名函数,这里是等defer函数执行的时候才计算变量值
    		fmt.Println("第三次defer:", a) // 第二个执行,输出:第三次defer:100
    	}()
    	a = 100 // 修改a的值,这行代码执行顺序优先于 第三次defer
    	return a
    }
    func main() {
    	foo()
    }
    
    //示例2
    func readFile(filename string) error {
    	f, err := os.Open(filename)
    	if err != nil {
    		return err
    	}
    
    	// 读取文件 
    	_, err = f.Read(make([]byte, 1024))
    	if err != nil {
    		return err
    	}
    	defer f.Close() // 确保关闭
    	
    	return nil
    }
    func main() {
    	if err := readFile("C:\\Pictures\\清乾隆缂丝秋桃绶带图轴.jpg"); err != nil {
    		panic(err)
    	}
    }

    5、错误(error

    • 错误通过 error 接口返回(非异常)
    • 调用者必须显式检查错误
    • 避免使用 panic 处理常规错误
    • 推荐使用 fmt.Errorf 构造带上下文的错误信息。
      func sqrt(x float64) (float64, error) {
      	if x < 0 {
      		// 推荐使用 fmt.Errorf 构造带上下文的错误信息。
      		return 0, fmt.Errorf("sqrt of negative number: %g", x)
      	}
      	return math.Sqrt(x), nil
      }

    6、Panic异常(会导致main协程挂掉)

    Go的类型系统会在编译时捕获很多错误,但有些错误只能在运行时检查,如数组访问越界、空指针引用等,这些运行时错误会引起panic异常。

    • panic 终止当前函数并向上冒泡

    • 应仅用于不可恢复的严重错误(如程序 bug)

    func mustOpen(name string) *os.File {
        f, err := os.Open(name)
        if err != nil {
            panic(err) // 不推荐用于常规错误
        }
        return f
    }

    7、Recover捕获异常

    • recover 只能在 defer 函数中调用

    • 可捕获 panic 并恢复程序执行

    // safeCall 安全地调用指定的函数,捕获可能发生的恐慌并将其转换为错误返回
    // 参数:
    //   fn - 需要被安全调用的函数
    // 返回值:
    //   err - 如果函数执行过程中发生恐慌则返回包装后的错误,否则返回nil
    func safeCall(fn func()) (err error) {
    	// 使用defer和recover机制来捕获可能发生的恐慌
    	defer func() {
    		if r := recover(); r != nil {
    			err = fmt.Errorf("panic recovered: %v", r)
    		}
    	}()
    	fn()
    	return nil
    }
    
    func main() {
    	safeCall(func() {
    		panic("something went wrong") // 如果上面的函数没有defer和recover,就会报错:panic: something went wrong
    	})
    }

    七、方法和接口

    1、接口

    • 接口只包含方法签名,不包含实现(go有个默认的空接口:interface{})
    • 可以嵌入其他接口(组合)
    • 只要一个类型实现了接口中的所有方法,就说明实现了该接口 — 无需显式声明 implements
    //示例1:空接口
    func main() {
    	var name interface{}
    	name = "你好"
    	fmt.Println(name) // 输出:你好
    }
    
    //示例2:接口多种实现
    /*
    定义接口Speaker,该接口有一个方法Speak()
    在JAVA中需要先Implements实现这个接口,然后才能实现具体的方法。
    在GO中不需要Implements,直接实现接口方法即可
    */
    type Speaker interface {
    	Speak() string
    }
    
    // 定义具体类型1
    type Dog struct{}
    
    // 实现接口1
    func (d Dog) Speak() string { return "汪汪汪!" }
    
    // 定义具体类型2
    type Cat struct{}
    
    // 实现接口2
    func (c Cat) Speak() string { return "喵喵喵!" }
    
    func main() {
    	var s Speaker
    	s = Dog{}
    	fmt.Println(s.Speak()) // 汪汪汪!
    	s = Cat{}
    	fmt.Println(s.Speak()) // 喵喵喵!
    }

    2、方法

    接收者 (Receiver):方法声明时,在关键字 func 和函数名之间包含一个参数,称为接收者。

    • 值接收者 (Value Receiver):操作的是接收者的副本,不影响原对象。(只读操作,适用于小结构体)

    • 指针接收者 (Pointer Receiver):操作的是原对象的引用,操作会影响原对象。(读写操作,适用于大型结构体(避免拷贝开销))

    绑定范围:方法可以绑定到任何同一包内定义的具名类型(Named Type)上,包括结构体、数值、字符串、甚至函数类型。但不能绑定到基础类型(如 int)或其它包中的类型。

    组合与封装:通过在结构体中嵌入(Embedding)另一个类型,可以“继承”该类型的方法。

    /*
    定义接口Speaker,该接口有一个方法Speak()
    在JAVA中需要先Implements实现这个接口,然后才能实现具体的方法。
    在GO中不需要Implements,直接实现接口方法即可
    */
    type Speaker interface {
    	Speak() string
    }
    
    // ----------------------------
    // 1. 值接收者:操作副本,不影响原对象
    // ----------------------------
    type Dog struct {
    	Name  string
    	count int // 用于记录 Speak 被调用次数(但不会真正更新原对象)
    }
    
    // 值接收者:d 是 Dog 的副本
    func (d Dog) Speak() string {
    	d.count++ // 修改的是副本的 count
    	return fmt.Sprintf("%s(汪汪!已叫 %d 次)", d.Name, d.count)
    }
    
    // ----------------------------
    // 2. 指针接收者:操作原对象,会修改它
    // ----------------------------
    type Bird struct {
    	Name  string
    	count int // 用于记录 Speak 被调用次数
    }
    
    // 指针接收者:b 指向原对象
    func (b *Bird) Speak() string {
    	b.count++ // 修改的是原对象的 count
    	return fmt.Sprintf("%s(叽叽!已叫 %d 次)", b.Name, b.count)
    }
    
    func main() {
    	// ====== 测试值接收者(Dog)======
    	fmt.Println("=== Dog(值接收者)===")
    	dog := Dog{Name: "旺财"}
    	fmt.Println("第一次调用:", dog.Speak())     // 第一次调用: 旺财(汪汪!已叫 1 次)
    	fmt.Println("第二次调用:", dog.Speak())     // 第二次调用: 旺财(汪汪!已叫 1 次)
    	fmt.Println("原对象 count 值:", dog.count) // 仍然是 0!
    	fmt.Println("=== Dog只修改了自己方法里面的count,原始值依旧是0 ===")
    
    	// ====== 测试指针接收者(Bird)======
    	fmt.Println("\n=== Bird(指针接收者)===")
    	bird := Bird{Name: "小翠"}
    	fmt.Println("第一次调用:", bird.Speak())     // 第一次调用: 小翠(叽叽!已叫 1 次)
    	fmt.Println("第二次调用:", bird.Speak())     // 第二次调用: 小翠(叽叽!已叫 2 次)
    	fmt.Println("原对象 count 值:", bird.count) // 已变为 2!
    	fmt.Println("=== Bird是指针接收者,修改了原始值变为2 ===")
    
    	// ====== 接口调用验证 ======
    	fmt.Println("\n=== 通过接口调用 ===")
    	var s Speaker
    
    	s = dog                             // 值类型赋值给接口
    	fmt.Println("接口调用 Dog:", s.Speak()) // 接口调用 Dog: 旺财(汪汪!已叫 1 次)
    
    	s = &bird                                 // 注意:必须传指针才能满足接口(因为 Speak 在 *Bird 上)
    	fmt.Println("接口调用 Bird:", s.Speak())      // 接口调用 Bird: 小翠(叽叽!已叫 3 次)
    	fmt.Println("Bird 最终 count:", bird.count) // Bird 最终 count: 3
    }
    
    /**
    === Dog(值接收者)===
    第一次调用: 旺财(汪汪!已叫 1 次)
    第二次调用: 旺财(汪汪!已叫 1 次)
    原对象 count 值: 0
    === Dog只修改了自己方法里面的count,原始值依旧是0 ===
    
    === Bird(指针接收者)===
    第一次调用: 小翠(叽叽!已叫 1 次)
    第二次调用: 小翠(叽叽!已叫 2 次)
    原对象 count 值: 2
    === Bird是指针接收者,修改了原始值变为2 ===
    
    === 通过接口调用 ===
    接口调用 Dog: 旺财(汪汪!已叫 1 次)
    接口调用 Bird: 小翠(叽叽!已叫 3 次)
    Bird 最终 count: 3
    */

    八、Goroutines和Channels

    1、Goroutine(协程)

    • 使用 go 关键字来启动一个协程
    • 轻量级:初始栈仅 2KB,可动态增长;成千上万个 goroutine 可同时运行。
    • 非阻塞:启动后立即返回,不等待函数执行完成。
    • 由 Go 运行时调度,运行在少量 OS 线程上(M:N 调度)。
    • 不要通过共享内存来通信,而应通过通信来共享内存
    // 示例1:简单创建
    func main() { // main协程
    	wg := sync.WaitGroup{} // 定义一个计数器
    	wg.Add(2)              // 设置计数器
    	// go 启动一个子协程来运行匿名函数
    	go func(x int, y int) {
    		defer wg.Done() // 计数器减1
    		t := x + y
    		fmt.Println(t)
    	}(1, 2)
    	go func(x int, y int) {
    		defer wg.Done() // 计数器减1
    		t := x + y
    		fmt.Println(t)
    	}(3, 4)
    	wg.Wait() // 等待所有子协程执行完毕
    }
    
    // 示例2:模拟父子协程(实际上在底层还是并行的)
    func f1() {
    	fmt.Println("f1")
    	go f2()
    }
    func f2() {
    	time.Sleep(1 * time.Second)
    	fmt.Println("f2")
    }
    // main -> f1 -> f2
    func main() {
    	go f1()
    	time.Sleep(3 * time.Second)
    }
    
    // 示例3:recover 函数用来捕获 panic防止main协程崩溃
    func f1() {
    	defer func() {
    		err := recover() // recover 函数用来捕获 panic,防止程序崩溃
    		if err != nil { // 工作中需要捕获panic,且打印错误信息,这里可以打印错误信息,也可以记录错误日志
    			fmt.Printf("发生了panic %s:\n", err)
    		}
    	}()
    	a, b := 3, 0
    	println(a, b)        // 输出:3 0
    	_ = a / b            // 此处发生panic: runtime error: integer divide by zero
    	println("f1 finish") // 不会执行
    }
    
    func main() {
    	go f1()
    	time.Sleep(1 * time.Second)
    	println("main finish")
    }
    

    2、Channel(通道)

    • 类型:chan T,用于在 goroutine 之间传递类型为 T 的值。

    • 同步机制:发送和接收操作默认是阻塞的,天然实现同步

    • 无缓冲通道:发送和接收必须同时就绪(同步点)。

    • 有缓冲通道:缓冲区满时发送阻塞,空时接收阻塞。

    • 关闭后:不能再发送(panic),但可继续接收剩余值,直到返回零值 + false

    ch := make(chan int)      // 无缓冲通道
    ch := make(chan int, 3)   // 有缓冲通道(容量 3)

    3、select 语句(多路复用)

    • 类似 switch,但用于 channel 操作。
    • 可同时监听多个 channel 的发送/接收。
    • default 分支实现非阻塞操作。
    // 该函数演示了Go语言中select语句的使用,用于在多个channel操作之间进行选择
    func main() {
    	// 初始化变量和channel
    	x := 1
    	ch1 := make(chan int, 3)
    	ch2 := make(chan int, 3)
    
    	// 使用select语句在多个channel操作之间进行非阻塞选择
    	// select会随机选择一个可以执行的case,如果没有任何case可以执行则执行default分支
    	select {
    	case v := <-ch1:
    		fmt.Println("Received from ch1:", v)
    	case ch2 <- x:
    		fmt.Println("Sent to ch2")
    	default:
    		fmt.Println("No communication ready")
    	}
    }

    4、总结

    1、常见并发模式:

    1. 生成器(Generator):goroutine 产生数据,通过 channel 输出。
    2. 工作池(Worker Pool):多个 worker 从任务队列(channel)消费。
    3. 扇入/扇出(Fan-in/Fan-out):合并多个输入 / 分发到多个处理者。
    4. 超时控制:结合 time.After 实现
    主 goroutine 退出,所有 goroutine 被强制终止程序不会等待后台 goroutine 完成使用 sync.WaitGroup 或 channel 同步
    向已关闭的 channel 发送 → panic只由发送方关闭 channel
    重复关闭 channel → panic确保只关闭一次
    无缓冲 channel 需要配对操作单独发送或接收会永久阻塞确保有对应的接收/发送方
    忘记读取 channel 导致 goroutine 泄漏使用带缓冲 channel 或确保消费
    在循环中启动 goroutine 捕获循环变量闭包捕获的是变量地址,值可能变化显式传参或复制变量

    2、示例 1:基础 goroutine + channel 同步

    // say函数用于打印指定字符串3次,每次间隔100毫秒,并通过done通道通知完成
    // 参数s: 要打印的字符串
    // 参数done: 用于通知任务完成的布尔类型通道
    func say(s string, done chan bool) {
    	// 循环打印字符串3次
    	for i := 0; i < 3; i++ {
    		fmt.Println(s)
    		time.Sleep(100 * time.Millisecond)
    	}
    	// 向done通道发送true值,表示任务已完成
    	done <- true
    }
    
    
    // main 函数是程序的入口点,演示了 goroutine 的并发执行
    // 通过 channel 实现主 goroutine 和子 goroutine 之间的同步
    func main() {
    	// 创建一个布尔类型的 channel 用于 goroutine 间通信和同步
    	done := make(chan bool)
    	
    	// 启动一个子 goroutine 执行 say 函数,传入 "world" 和 done channel
    	go say("world", done)
    	
    	// 主 goroutine 直接执行 say 函数,传入 "hello" 和 nil
    	say("hello", nil)
    	
    	// 阻塞等待子 goroutine 完成,从 done channel 接收值
    	<-done
    }

    3、示例 2:工作池(Worker Pool)

    // worker 是一个工作协程函数,用于处理作业队列中的任务
    // id: 工作协程的唯一标识符
    // jobs: 只读通道,用于接收待处理的作业任务
    // results: 只写通道,用于发送处理结果
    // wg: 等待组,用于协调所有工作协程的完成
    func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
    	defer wg.Done()
    	// 循环处理作业队列中的任务,直到通道关闭
    	for job := range jobs {
    		fmt.Printf("Worker %d processing job %d\n", id, job)
    		time.Sleep(time.Second) // 模拟耗时
    		results <- job * 2
    	}
    }
    
    // 该函数启动多个worker goroutine来处理作业,并收集处理结果
    func main() {
    	const numJobs = 5
    	jobs := make(chan int, numJobs)
    	results := make(chan int, numJobs)
    
    	var wg sync.WaitGroup
    	// 启动3个worker goroutine来处理作业
    	for w := 1; w <= 3; w++ {
    		wg.Add(1)
    		go worker(w, jobs, results, &wg)
    	}
    
    	// 发送任务
    	for j := 1; j <= numJobs; j++ {
    		jobs <- j
    	}
    	close(jobs)
    
    	// 等待所有 worker 结束
    	wg.Wait()
    	close(results)
    
    	// 收集结果
    	for res := range results {
    		fmt.Println("Result:", res)
    	}
    }

    4、示例 3:超时控制(select + time.After)

    // 该函数演示了带超时机制的goroutine通信
    // 通过channel和select语句实现主goroutine与子goroutine之间的数据传递和超时控制
    func main() {
    	// 创建一个无缓冲的字符串channel,用于goroutine间通信
    	ch := make(chan string)
    
    	// 启动一个匿名goroutine函数
    	go func() {
    		// 子goroutine休眠2秒模拟耗时操作
    		time.Sleep(2 * time.Second)
    		// 向channel发送结果数据
    		ch <- "result"
    	}()
    
    	// 使用select语句实现超时控制
    	// 监听多个channel操作,执行第一个准备好的操作
    	select {
    	// 从ch通道接收数据的情况
    	case res := <-ch:
    		fmt.Println("Got:", res)
    	// 超时情况:After函数返回的通道在指定时间后可读
    	case <-time.After(1 * time.Second):
    		fmt.Println("Timeout!")
    	}
    } // 输出: Timeout!
    

    5、示例4:实现简单聊天室

    package main
    
    import (
    	"bufio"
    	"fmt"
    	"log"
    	"net"
    	"strings"
    )
    
    // 消息类型:包含内容和来源
    type clientMessage struct {
    	text string
    	user string
    }
    
    // 全局广播通道
    var (
    	entering = make(chan *client) // 用户进入
    	leaving  = make(chan *client) // 用户离开
    	messages = make(chan clientMessage) // 消息广播
    )
    
    // 客户端结构
    type client struct {
    	name    string
    	conn    net.Conn
    	message chan<- clientMessage // 发送消息到该客户端的通道(只写)
    }
    
    // 广播协程:管理在线用户列表并转发消息
    func broadcaster() {
    	clients := make(map[*client]bool) // 当前在线用户
    	for {
    		select {
    		case msg := <-messages:
    			// 广播消息给所有客户端
    			for cli := range clients {
    				cli.message <- msg
    			}
    		case cli := <-entering:
    			// 新用户加入
    			clients[cli] = true
    			msg := fmt.Sprintf("当前在线用户 (%d): ", len(clients))
    			var names []string
    			for c := range clients {
    				names = append(names, c.name)
    			}
    			msg += strings.Join(names, ", ")
    			for cli := range clients {
    				cli.message <- clientMessage{text: msg, user: "[系统]"}
    			}
    		case cli := <-leaving:
    			// 用户离开
    			delete(clients, cli)
    			close(cli.message)
    			msg := fmt.Sprintf("[系统] %s 已离开。当前在线: %d 人", cli.name, len(clients))
    			for c := range clients {
    				c.message <- clientMessage{text: msg, user: "[系统]"}
    			}
    		}
    	}
    }
    
    // 客户端处理协程
    func handleConn(conn net.Conn) {
    	defer conn.Close()
    
    	// 设置用户名
    	scanner := bufio.NewScanner(conn)
    	conn.Write([]byte("请输入你的昵称: "))
    	if !scanner.Scan() {
    		return
    	}
    	name := strings.TrimSpace(scanner.Text())
    	if name == "" {
    		name = "匿名用户"
    	}
    
    	// 创建客户端
    	clientChan := make(chan clientMessage) // 用于向该客户端发送消息
    	cli := &client{
    		name:    name,
    		conn:    conn,
    		message: clientChan,
    	}
    
    	// 通知 broadcaster 有新用户加入
    	entering <- cli
    
    	// 启动一个 goroutine 向客户端写消息
    	go func() {
    		for msg := range clientChan {
    			fmt.Fprintf(conn, "[%s] %s\n", msg.user, msg.text)
    		}
    	}()
    
    	// 读取用户输入并广播
    	messages <- clientMessage{text: "已加入聊天室", user: name}
    	input := bufio.NewScanner(conn)
    	for input.Scan() {
    		text := strings.TrimSpace(input.Text())
    		if text == "/quit" {
    			break
    		}
    		messages <- clientMessage{text: text, user: name}
    	}
    
    	// 用户退出
    	leaving <- cli
    	messages <- clientMessage{text: "已离开聊天室", user: name}
    }
    
    func main() {
    	listener, err := net.Listen("tcp", ":8080")
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer listener.Close()
    
    	fmt.Println("聊天服务器启动,监听端口 8080...")
    	go broadcaster()
    
    	for {
    		conn, err := listener.Accept()
    		if err != nil {
    			log.Print(err)
    			continue
    		}
    		go handleConn(conn) // 每个连接一个 goroutine
    	}
    }
    #测试:
    
    # 终端 1
    telnet localhost 8080
    # 输入昵称:Alice
    
    # 终端 2
    telnet localhost 8080
    # 输入昵称:Bob

    九、锁

    1、竞争条件(Race Condition)

    • 定义:多个 goroutine 并发读写同一变量,结果依赖于执行时序。
    • 后果:程序行为不可预测(崩溃、数据错乱、静默错误)。
    • 检测工具go run -race(竞态检测器)
    var balance = 100
    
    // Deposit 向账户中存入指定金额
    // amount: 存入的金额
    // wg: 用于同步的等待组,函数执行完毕后会调用Done方法
    func Deposit(amount int, wg *sync.WaitGroup) {
    	defer wg.Done()
    	// 多个 goroutine 同时修改balance变量,存在竞态条件风险
    	balance += amount
    }
    
    // 该函数创建两个goroutine来执行存款操作,并等待它们完成
    // 最后打印出最终的余额,但由于竞态条件,结果是不确定的
    func main() {
    	// 创建WaitGroup用于等待所有goroutine完成
    	var wg sync.WaitGroup
    	
    	// 设置需要等待的goroutine数量为2
    	wg.Add(2)
    	
    	// 启动第一个goroutine,存入10元
    	go Deposit(10, &wg)
    	
    	// 启动第二个goroutine,存入20元
    	go Deposit(20, &wg)
    	
    	// 等待所有goroutine完成
    	wg.Wait()
    	
    	// 打印最终余额,由于存在竞态条件,结果可能是110、120或130等不确定值
    	fmt.Println("Final balance:", balance) // 可能是 110、120、130?不确定!
    }

    2、sync.Mutex 互斥锁

    • 最基本的同步原语:同一时间只允许一个 goroutine 访问临界区
    • 方法:Lock() / Unlock()
    • 必须配对使用,推荐用 defer
    // Deposit 向账户中存入指定金额
    // 参数:
    //	amount: 要存入的金额
    //	wg: 用于同步的WaitGroup指针,函数执行完毕后会调用Done方法
    func Deposit(amount int, wg *sync.WaitGroup) {
    	defer wg.Done()
    	// 加锁保护共享资源balance
    	mu.Lock()
    	defer mu.Unlock()
    	balance += amount
    }
    
    // 该函数创建两个goroutine来执行存款操作,并等待它们完成
    // 最后打印出最终的余额,但由于竞态条件,结果是不确定的
    func main() {
    	// 创建WaitGroup用于等待所有goroutine完成
    	var wg sync.WaitGroup
    
    	// 设置需要等待的goroutine数量为2
    	wg.Add(2)
    
    	// 启动第一个goroutine,存入10元
    	go Deposit(10, &wg)
    
    	// 启动第二个goroutine,存入20元
    	go Deposit(20, &wg)
    
    	// 等待所有goroutine完成
    	wg.Wait()
    
    	// 打印最终余额,现在使用了互斥锁,确保了结果是正确的
    	fmt.Println("Final balance:", balance) // Final balance: 130
    }

    3、sync.RWMutex 读写锁

    • 适用于 读多写少 场景。
    • 允许多个 reader 同时读,但 writer 必须独占。
    • 方法:
    • RLock() / RUnlock()(读锁)
    • Lock() / Unlock()(写锁)
    type SafeCache struct {
    	mu    sync.RWMutex
    	cache map[string]string
    }
    
    // Get 从缓存中获取与给定键关联的值
    // 该方法是并发安全的,因为它使用了读锁
    //
    // 参数:
    //   - key: 要在缓存中查找的字符串键
    //
    // 返回值:
    //   - string: 与键关联的值,如果未找到键则返回空字符串
    func (c *SafeCache) Get(key string) string {
    	// 获取读锁以允许多个goroutine同时读取
    	c.mu.RLock()
    	defer c.mu.RUnlock()
    	return c.cache[key]
    }
    
    // Set 在缓存中设置键值对
    // 该方法是线程安全的,使用互斥锁确保并发访问安全
    //
    // 参数:
    //   key:   用于存储值的字符串键
    //   value: 要存储在缓存中的字符串值
    func (c *SafeCache) Set(key, value string) {
    	c.mu.Lock()
    	defer c.mu.Unlock()
    	c.cache[key] = value
    }

    4、内存同步(Memory Synchronization)

    • 关键点:锁不仅保护临界区,还保证内存可见性
    • 没有同步的情况下,一个 goroutine 的写可能对另一个不可见(CPU 缓存、编译器优化)
    • sync 包的操作(如 mutex、channel)会建立 “happens-before” 关系,确保内存同步
    • 不要用“volatile”或“原子读”来替代锁 —— Go 没有 volatile,普通读写不保证可见性

    5、sync.Once 惰性初始化

    • 确保某段代码只执行一次,即使被多个 goroutine 调用
    • 常用于单例、配置加载、连接池初始化
    var (
    	once   sync.Once
    	dbConn *DB
    )
    
    func GetDB() *DB {
    	once.Do(func() {
    		dbConn = connectToDB() // 只会执行一次
    	})
    	return dbConn
    }

    6、竞争条件检测

    • 使用 -race 标志编译/运行

    • 只能检测已发生的竞争,不能证明无竞争

    • 性能开销大(2~20x),仅用于开发/测试

    go run -race main.go
    go test -race ./...
    go build -race

    7、示例:并发的非阻塞缓存

    结合 map + mutex + 函数闭包 实现带缓存的函数(memoization

    package main
    
    import (
    	"fmt"
    	"sync"
    	"time"
    )
    
    type Memo struct {
    	f     Func
    	cache map[string]*entry
    	mu    sync.RWMutex
    }
    
    type Func func(key string) (interface{}, error)
    
    type entry struct {
    	res   result
    	ready chan struct{} // 用于等待结果就绪
    }
    
    type result struct {
    	value interface{}
    	err   error
    }
    
    // New 创建一个新的 Memo 实例,使用给定的函数进行初始化
    // 它会初始化缓存映射表,用于存储记忆化的结果
    //
    // 参数:
    //   f: 需要被记忆化的函数
    //
    // 返回值:
    //   指向新创建的 Memo 实例的指针
    func New(f Func) *Memo {
    	return &Memo{f: f, cache: make(map[string]*entry)}
    }
    
    // Get 是线程安全的
    // Get 从缓存中获取指定键的值,如果键不存在则执行慢函数计算结果并缓存
    // key: 要获取值的键
    // value: 获取到的值
    // err: 获取过程中可能发生的错误
    func (memo *Memo) Get(key string) (value interface{}, err error) {
    	memo.mu.RLock()
    	e := memo.cache[key]
    	memo.mu.RUnlock()
    
    	if e != nil {
    		// 已存在:等待结果就绪(防止重复计算)
    		<-e.ready
    		return e.res.value, e.res.err
    	}
    
    	// 不存在:加写锁,双重检查
    	memo.mu.Lock()
    	e = memo.cache[key]
    	if e == nil {
    		// 第一次创建 entry
    		e = &entry{ready: make(chan struct{})}
    		memo.cache[key] = e
    		memo.mu.Unlock()
    
    		// 执行慢函数
    		e.res.value, e.res.err = memo.f(key)
    		close(e.ready) // 通知所有等待者
    	} else {
    		// 其他 goroutine 刚刚创建了 entry
    		memo.mu.Unlock()
    		<-e.ready
    	}
    	return e.res.value, e.res.err
    }
    
    // 参数:
    //   key - 用于生成结果的输入字符串
    // 返回值:
    //   interface{} - 基于输入key生成的格式化结果字符串
    //   error - 错误信息,当前实现始终返回nil
    func slowFunc(key string) (interface{}, error) {
    	// 模拟耗时操作,延迟1秒
    	time.Sleep(1 * time.Second)
    	return fmt.Sprintf("result for %s", key), nil
    }
    
    //Memoization模式的并发使用
    // 该函数创建了一个带缓存的函数调用器,并通过多个goroutine并发调用
    // 来展示缓存机制如何避免重复计算
    func main() {
    	memo := New(slowFunc)
    
    	// 启动多个goroutine并发调用memo.Get方法
    	// 通过WaitGroup等待所有goroutine执行完成
    	// 所有goroutine都使用相同的key"test"来测试缓存效果
    	var wg sync.WaitGroup
    	for i := 0; i < 3; i++ {
    		wg.Add(1)
    		go func(key string) {
    			defer wg.Done()
    			res, _ := memo.Get(key)
    			fmt.Println(res)
    		}("test")
    	}
    	wg.Wait()
    }
    

    十、反射

    反射:在运行时检查变量的类型和值,并能动态调用方法、修改字段

    Go 的反射基于两个核心类型:

    • reflect.Type:表示类型的元信息(如名称、字段、方法等)
    • reflect.Value:表示值的可操作封装(可读、可写、可调用)

     修改变量的值时必须传递指针(reflect.ValueOf(x).SetInt(20) → panic(不可寻址))

    func main() {
    	var x = 10
    	t := reflect.TypeOf(x) // Type: int
    	// ValueOf 返回的是副本,若想修改原变量,必须传指针
    	v := reflect.ValueOf(x) // Value: 10
    
    	fmt.Println(t.Name()) // 输出:"int"
    	fmt.Println(v.Int())  // 输出:10
    	// Elem() 解引用指针,使用指针修改变量值
    	v1 := reflect.ValueOf(&x).Elem() // Value: 10
    	if v1.CanSet() {
    		v1.SetInt(20)
    	}
    	// 简写:reflect.ValueOf(&x).Elem().SetInt(20)
    	fmt.Println(x) // 输出:20
    }

    使用反射遍历结构体字段(含标签)

    type User struct {
    	Name string `json:"name" validate:"required"`
    	Age  int    `json:"age"`
    }
    
    // 通过反射获取其字段名、值和JSON标签
    func main() {
    	u := User{"Alice", 30}
    	v := reflect.ValueOf(u)
    	t := reflect.TypeOf(u)
    
    	// 遍历结构体的所有字段,输出字段名、值和JSON标签
    	for i := 0; i < v.NumField(); i++ {
    		field := v.Field(i)
    		fieldType := t.Field(i)
    		fmt.Printf("字段名: %s\n", fieldType.Name)
    		fmt.Printf("值: %v\n", field.Interface())
    		fmt.Printf("JSON 标签: %s\n", fieldType.Tag.Get("json"))
    	}
    }

    使用反射动态调用方法

    type Greeter struct{}
    
    func (g Greeter) SayHello(name string) string {
    	return "Hello, " + name
    }
    
    // 创建Greeter实例,通过反射获取并调用其SayHello方法,最后输出方法调用结果
    func main() {
    	g := Greeter{}
    	v := reflect.ValueOf(g)
    
    	// 获取指定名称的方法
    	method := v.MethodByName("SayHello")
    	if !method.IsValid() {
    		panic("方法不存在")
    	}
    
    	// 准备方法调用的参数并执行方法调用
    	args := []reflect.Value{reflect.ValueOf("Go")}
    	results := method.Call(args)
    
    	// 输出方法返回的结果
    	fmt.Println(results[0].String()) // 输出:Hello, Go
    }

    创建新实例(类似 new(T))

    type User struct {
    	Name string `json:"name" validate:"required"`
    	Age  int    `json:"age"`
    }
    
    func main() {
    	var u User
    	t := reflect.TypeOf(u)
    
    	// 创建指针:相当于 new(User)
    	ptr := reflect.New(t)
    	elem := ptr.Elem() // 获取 *User 指向的 User 值
    
    	// 设置字段
    	elem.FieldByName("Name").SetString("Bob")
    	elem.FieldByName("Age").SetInt(25)
    
    	// 转回 interface{}
    	newUser := ptr.Interface().(*User)
    	fmt.Printf("%+v\n", *newUser) // {Name:Bob Age:25}
    }

    十一、常用包

    1、时间(time)

    const (
    	DATE_FORMAT      = "2006-01-02"          // YYYY-mm-dd
    	DATE_TIME_FORMAT = "2006-01-02 15:04:05" // YYYY-mm-dd
    )
    
    func main() {
    	t0 := time.Now()       // 获取当前时间
    	fmt.Println(t0.Unix()) // 输出时间戳:1766111307
    	// 模拟代码执行
    	time.Sleep(10 * time.Second) // 睡眠10秒
    	t1 := time.Now()             // 获取当前时间
    	println(t1.Unix())           // 输出时间戳:1766111317
    	// 时间相减:time - time =  duration
    	diff := t1.Sub(t0)                    // 计算时间差,返回duration数据类型
    	fmt.Println(diff.Seconds())           // 输出时间差:10.0005009
    	fmt.Println(time.Since(t0).Seconds()) // 输出时间差:10.0005009,这行代码等价于上面两行代码
    	// 时间相加:time + duration = time
    	diff2 := 5 * time.Second
    	t2 := t1.Add(diff2)    // 时间加上duration
    	fmt.Println(t2.Unix()) // 输出相加时间戳:1766111322
    	// 时间格式化
    	fmt.Println(t0.Format(DATE_FORMAT))      // 输出时间格式化:2025-12-19
    	fmt.Println(t0.Format(DATE_TIME_FORMAT)) // 输出时间格式化:2025-12-19 10:38:52
    	// 服务器未必是东八区。所以这里设定时区
    	loc, _ := time.LoadLocation("Asia/Shanghai")
    	fmt.Println(t0.In(loc).Format(DATE_TIME_FORMAT)) // 输出时间格式化:2025-12-19 10:38:52
    }

    2、读写文件

    (1)os

    // os.O_RDONLY    只读
    // os.O_WRONLY    只写
    // os.O_RDWR      读写
    // os.O_APPEND    追加写入
    // os.O_CREATE    不存在则创建
    // os.O_TRUNC     存在则清空
    // os.O_EXCL      配合 O_CREATE,文件存在时报错(原子创建)
    权限码含义说明
    0600-rw-------仅所有者可读写(私密文件,如密钥)
    0644-rw-r--r--所有者读写,组和其他人只读(最常用
    0666-rw-rw-rw-所有人可读写(受 umask 限制,通常实际为 0644)
    0755-rwxr-xr-x所有者读写执行,其他人读+执行(用于脚本/程序)
    0777-rwxrwxrwx所有人全权限(不推荐
    /**
    权限码	含义	说明
    0600	-rw-------	仅所有者可读写(私密文件,如密钥)
    0644	-rw-r--r--	所有者读写,组和其他人只读(最常用)
    0666	-rw-rw-rw-	所有人可读写(受 umask 限制,通常实际为 0644)
    0755	-rwxr-xr-x	所有者读写执行,其他人读+执行(用于脚本/程序)
    0777	-rwxrwxrwx	所有人全权限(不推荐)
    */
    
    // ReadFile 读取文件
    func ReadFile() {
    	file, err := os.Open("C:\\Users\\31809\\Desktop\\读文件.txt") // 打开文件
    	if err != nil {
    		fmt.Println("打开文件失败:", err)
    		return
    	}
    	// defer 关闭文件,防止内存泄漏等情况
    	defer func(file *os.File) {
    		err := file.Close()
    		if err != nil {
    			return
    		}
    	}(file)
    	content := make([]byte, 100)     // 创建一个字节切片,用于存储文件内容
    	n, err := file.Read(content)     // 读取文件内容,n是字节长度
    	fmt.Println(n)                   // 打印文件内容字节长度
    	fmt.Println(string(content[:n])) // 打印有效内容
    	fmt.Println(file.Name())         // 打印文件名
    }
    
    // WriteFile 读取文件
    func WriteFile() {
    	// os.O_CREATE: 如果文件不存在则创建 | os.O_TRUNC: 如果文件存在清空文件 | os.O_RDWR: 读写
    	file, err := os.OpenFile("写文件.txt", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644) // 打开文件
    	if err != nil {
    		fmt.Println("打开文件失败:", err)
    		return
    	}
    	// defer 关闭文件,防止内存泄漏等情况
    	defer func(file *os.File) {
    		err := file.Close()
    		if err != nil {
    			return
    		}
    	}(file)
    	write, err := file.Write([]byte("测试写入"))
    	if err != nil {
    		return
    	}
    	fmt.Println(write) // 打印写入的字节长度
    }
    
    func main() {
    	// os.Open: 打开绝对路径的文件
    	// os.OpenFile: 打开相对路径的文件
    	ReadFile()  //读取文件
    	WriteFile() //写入文件
    }

    (2)bufio

    方法说明
    Write(p []byte) (n int, err error)写入缓冲区(实现 io.Writer
    WriteString(s string) (n int, err error)写入字符串
    WriteByte(c byte) error写一个字节
    Flush() error强制将缓冲区内容刷入底层 Writer(非常重要!)
    Available() int返回缓冲区剩余空间
    Reset(w io.Writer)重置 Writer,绑定新的底层 Writer
    // ReadFile 读取大文件
    func ReadFile() {
    	file, err := os.Open("C:\\Users\\31809\\Downloads\\改命记实录.txt") // 打开大文件
    	if err != nil {
    		fmt.Println("打开文件失败:", err)
    		return
    	}
    	// defer 关闭文件,防止内存泄漏等情况
    	defer func(file *os.File) {
    		err := file.Close()
    		if err != nil {
    			return
    		}
    	}(file)
    	reader := bufio.NewReader(file)
    	for {
    		line, err := reader.ReadString('\n')
    		if err != nil {
    			if err == io.EOF {
    				break
    			} else {
    				fmt.Println("读取文件发生错误:", err)
    				return
    			}
    		} else {
    			fmt.Print(line)
    		}
    	}
    }

    (3)总结

    场景推荐方案
    读小文件os.ReadFile()(简单直接)
    读大文件(逐行)os.Open() + bufio.Scanner
    读大文件(随机/块读)os.Open() + bufio.Reader
    写小文件os.WriteFile()
    写大文件/高频写os.Create() + bufio.Writer + defer Flush()
    需要 Peek/Discardbufio.Reader

    3、atomic

    Go 语言的 sync/atomic 包提供了 底层原子操作(atomic operations),用于在多 goroutine 并发访问共享变量时,无需加锁即可保证操作的原子性和内存可见性。它是实现高性能无锁(lock-free)并发结构的基础。

    特性说明
    目的无锁、高性能并发访问单个变量
    核心操作Load, Store, Add, CompareAndSwap (CAS)
    推荐用法Go 1.19+ 优先使用 atomic.Int64atomic.Bool 等新类型
    适用场景计数器、开关标志、配置热更新、无锁数据结构
    不适用场景复杂状态同步、多变量一致性
    func foo() {
    	for i := 0; i < 1000; i++ {
    		// n++
    		atomic.AddInt64(&n, 1) // 原子操作
    	}
    	fmt.Printf("n=%d\n", n)
    }
    
    func main() {
    	wg := sync.WaitGroup{}
    	wg.Add(2)
    	go func() {
    		defer wg.Done()
    		foo()
    	}()
    	go func() {
    		defer wg.Done()
    		foo()
    	}()
    	wg.Wait()
    	fmt.Printf("main:%d\n", n)
    }

    4、channel(环形队列)

    mc := make(chan struct{})
    创建了一个 无缓冲的、元素类型为 struct{} 的通道(channel)。这是 Go 并发编程中一种非常常见且高效的模式,通常用于信号通知(signal/notification),而不是传递数据。
    
    func main() {
    	ch := make(chan int, 100) // 创建一个容量为100的channel
    	wg := sync.WaitGroup{}
    	wg.Add(2)
    
    	// 两个生产者,向channel中写入元素
    	go func() {
    		defer wg.Done()
    		for i := 0; i < 10; i++ {
    			ch <- i
    		}
    	}()
    	go func() {
    		defer wg.Done()
    		for i := 0; i < 10; i++ {
    			ch <- i
    		}
    	}()
    
    	// 创建一个无缓冲的、元素类型为 struct{} 的通道
    	mc := make(chan struct{})
    	//一个消费者
    	go func() {
    		sum := 0
    		for {
    			a, ok := <-ch
    			if !ok { // 如果channel被关闭且为空,则ok为false,直接退出死循环
    				break
    			} else {
    				sum += a
    			}
    		}
    		fmt.Println(sum) // 输出90
    		// 告诉消费者,已经消费完毕
    		mc <- struct{}{}
    	}()
    	wg.Wait()
    	close(ch) // 关闭channel,但是channel中的元素仍然会继续被消费,只是不允许继续写入元素了
    	// 向channel写入元素来使main阻塞
    	<-mc
    }
    // main协程和子协程都因为channel管道阻塞发生死锁:fatal error: all goroutines are asleep - deadlock!
    func main() {
    	ch := make(chan int)
    	go func() {
    		ch <- 1 // 写阻塞
    		fmt.Println("结束")
    	}()
    	ch <- 1 // 写阻塞
    }
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值