/golang-base

- 理解编译器的词法与语法解析、类型检查、中间代码生成以及机器码生成过程; - 理解数组、切片、哈希表和字符串等数据结构的内部表示以及常见操作的原理; - 理解 Go 语言中的函数、方法以及反射等语言特性; - 理解常见并发原语 `Mutex`、`WaitGroup` 以及扩展原语的使用和原理; - 理解 make、new、defer、select、for 和 range 等关键字的实现; - 理解运行时中的调度器、网络轮询器、内存分配器、垃圾收集器的实现原理; - 理解 HTTP、RPC、JSON 等标准库的设计与原理;

Primary LanguageGo

golang-base

liwenzhou个人博主学习站

一个个人的学习站,博主在每一期都录制了视频
https://www.liwenzhou.com/archives/
视频地址

欧长坤 个人博主学习站

https://golang.design/

一个字节同学的个人博主学习站

Go 语言设计与实现的作者
https://draveness.me/golang/

  • 理解编译器的词法与语法解析、类型检查、中间代码生成以及机器码生成过程;
  • 理解数组、切片、哈希表和字符串等数据结构的内部表示以及常见操作的原理;
  • 理解 Go 语言中的函数、方法以及反射等语言特性;
  • 理解常见并发原语 MutexWaitGroup 以及扩展原语的使用和原理;
  • 理解 make、new、defer、select、for 和 range 等关键字的实现;
  • 理解运行时中的调度器、网络轮询器、内存分配器、垃圾收集器的实现原理;
  • 理解 HTTP、RPC、JSON 等标准库的设计与原理;

一个阿里同学的个人博主学习站

lianglianglee这个博主的知识面很宽而且在每个领域都有一定深度的认识,最起码文章上面写的不是流水账,很推荐
https://learn.lianglianglee.com/%E4%B8%93%E6%A0%8F/22%20%E8%AE%B2%E9%80%9A%E5%85%B3%20Go%20%E8%AF%AD%E8%A8%80-%E5%AE%8C

-12步通关求职面试-完 -22 讲通关 Go 语言-完 -24讲吃透分布式数据库-完 -300分钟吃透分布式缓存-完 -CNCF X 阿里巴巴云原生技术公开课 -DDD 微服务落地实战 -DDD实战课 -Dubbo源码解读与实战-完 -ElasticSearch知识体系详解 -JVM 核心技术 32 讲(完) -Java 业务开发常见错误 100 例 -Java 并发编程 78 讲-完 -Java 性能优化实战-完 -Java并发编程实战 -Kafka核心技术与实战 -Kubernetes 从上手到实践 -Kubernetes 实践入门指南 -MySQL实战45讲 -MySQL实战宝典 -Netty 核心原理剖析与 RPC 实践-完 -OKR组织敏捷目标和绩效管理-完 -Redis 核心原理与实战 -RocketMQ 实战与进阶(完) -Serverless 技术公开课(完) -ShardingSphere 核心原理精讲-完 -Spring Boot 实战开发 -Spring Security 详解与实操 -SpringCloud微服务实战(完) -ZooKeeper源码分析与实战-完 -互联网消费金融高并发领域设计 -全解网络协议 -分布式中间件实践之路(完) -分布式技术原理与实战45讲-完 -分布式链路追踪实战-完 -前端工程化精讲-完 -容器实战高手课 -左耳听风 -微服务质量保障 20 讲-完 -架构设计面试精讲 -案例上手 Spring Boot WebFlux(完) -消息队列高手课 -深入剖析 MyBatis 核心原理-完 -深入拆解Java虚拟机 -深入浅出 Docker 技术栈实践课(完) -深入浅出 Java 虚拟机-完 -深入浅出计算机组成原理 -深入理解 Sentinel(完) -由浅入深吃透 Docker-完 -白话设计模式 28 讲(完) -程序员的数学课 -说透性能测试 -软件工程之美 -透视HTTP协议 -重学操作系统-完 -重学数据结构与算法-完 -领域驱动设计实践(完) -高并发系统设计40问

基础

slice 和map 声明, 初始化方式, 初始化 空切片和空map

// 声明切片类型
var name []T
    var a []string              //声明一个字符串切片
    var b = []int{}             //声明一个整型切片并初始化
// 上面都是基于数组来创建的切片,如果需要动态的创建一个切片,我们就需要使用内置的make()函数
make([]T, size, cap)
    s1 := make([]int, 3) //[0 0 0]

// map格式定义
map[KeyType]ValueType
// map类型的变量默认初始值为nil,需要使用make()函数来分配内存
make(map[KeyType]ValueType, [cap])
    scoreMap := make(map[string]int, 8)
    scoreMap["张三"] = 90

// slice遍历
        for index, value := range s {
                fmt.Println(index, value)
        }        
// map遍历
        for k, v := range scoreMap {
                fmt.Println(k, v)
        }

slice 和 map 的循环遍历(有兴趣可以了解为什么map循环无序)

源码:
// decide where to start
r := uintptr(fastrand())
结果:
因此每次重新 for range map,你见到的结果都是不一样的。那是因为它的起始位置根本就不固定!

根本原因:
你想问为什么要这么做?当然是官方有意为之,因为官方在 Go 早期的时候,发现很多工程师都较依赖 map 的遍历迭代顺序。但这将会导致可移植性存在问题。因此,改之。也请不要依赖...
  • append 实现两个slice 合并方式

    • 进阶可以了解 append 内存扩容机制,内存对齐情况下的扩容
    • 声明cap的好处
Go语言的内建函数append()可以为切片动态添加元素。 可以一次添加一个元素,可以添加多个元素,也可以添加另一个切片中的元素(后面加…)。

var citySlice []string
// 追加一个元素
citySlice = append(citySlice, "北京")
// 追加多个元素
citySlice = append(citySlice, "上海", "广州", "深圳")
// 追加切片
a := []string{"成都", "重庆"}
citySlice = append(citySlice, a...) // //[北京 上海 广州 深圳 成都 重庆]

首先判断,如果新申请容量(cap)大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)。
否则判断,如果旧切片的长度小于1024,则最终容量(newcap)就是旧容量(old.cap)的两倍,即(newcap=doublecap),
否则判断,如果旧切片长度大于等于1024,则最终容量(newcap)从旧容量(old.cap)开始循环增加原来的1/4,即(newcap=old.cap,for {newcap += newcap/4})直到最终容量(newcap)大于等于新申请的容量(cap),即(newcap >= cap)
如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)。

结构体和结构体方法使用

类型别名规定:TypeAlias只是Type的别名,本质上TypeAlias与Type是同一个类型。

//类型定义
type NewInt int
//类型别名
type MyInt = int

使用type和struct关键字来定义结构体,结构体本身也是一种类型,我们可以像声明内置类型一样使用var关键字声明结构体类型。
type person struct {
        name string
        city string
        age  int8
}
在定义一些临时数据结构等场景下还可以使用匿名结构体
使用new关键字对结构体进行实例化,方法定义格式,接收者的概念就类似于其他语言中的this或者 self,方法与函数的区别是,函数不属于任何类型,方法属于特定的类型。
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
    函数体
}
结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)。

struct 和 json 之间转换(tag使用, omitempty 使用)

//JSON序列化:结构体-->JSON格式的字符串
data, err := json.Marshal(c)
if err != nil {
        fmt.Println("json marshal failed")
        return
}

//JSON反序列化:JSON格式的字符串-->结构体
str := `{"Title":"101","Students":[{"ID":0,"Gender":"男","Name":"stu00"},{"ID":1,"Gender":"男","Name":"stu01"},{"ID":2,"Gender":"男","Name":"stu02"}]}`
c1 := &Class{}
err = json.Unmarshal([]byte(str), c1)

Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来。 Tag在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:
`key1:"value1" key2:"value2"`
//Student 学生
type Student struct {
        ID     int    `json:"id"` //通过指定tag实现json序列化该字段时的key
        Gender string //json序列化是默认使用字段名作为key
        name   string //私有不能被json包访问
}
如果想要在序列序列化时忽略这些没有值的字段时,可以在对应字段添加omitempty tag。对象、引用和零值用指针可以破解
type Dog struct {
        Breed    string
        // dog的weight是未知,而不是0,并不是我们想要的结果
        WeightKg int `json:",omitempty"`
}
  • 了解go的控制结构

    • if-else
    • for
    • switch
    • goto
    • break
    • continue
if 表达式1 {
    分支1
} else if 表达式2 {
    分支2
} else{
    分支3
}
// for循环可以通过break、goto、return、panic语句强制退出循环。
for 初始语句;条件表达式;结束语句{
    循环体语句
}
Go语言中可以使用for range遍历数组、切片、字符串、map 及通道(channel)。 通过for range遍历的返回值有以下规律:
    数组、切片、字符串返回索引和值。
    map返回键和值。
    通道(channel)只返回通道内的值。
// fallthrough语法可以执行满足条件的case的下一个case,是为了兼容C语言中的case设计的。
func switchDemo5() {
        s := "a"
        switch {
        case s == "a":
                fmt.Println("a")
                fallthrough
        case s == "b":
                fmt.Println("b")
        case s == "c":
                fmt.Println("c")
        default:
                fmt.Println("...")
        }
}
// goto语句通过标签进行代码间的无条件跳转。goto语句可以在快速跳出循环、避免重复退出上有一定的帮助。Go语言中使用goto语句能简化一些代码的实现过程。
func gotoDemo2() {
        for i := 0; i < 10; i++ {
                for j := 0; j < 10; j++ {
                        if j == 2 {
                                // 设置退出标签
                                goto breakTag
                        }
                        fmt.Printf("%v-%v\n", i, j)
                }
        }
        return
        // 标签
breakTag:
        fmt.Println("结束for循环")
}

break语句可以结束for、switch和select的代码块。
continue语句可以结束当前循环,开始下一次的循环迭代过程,仅限在for循环内使用。

func 参数传递和实现不定参数的方法

func 函数名(参数)(返回值){
    函数体
}

函数可以作为类型、变量、参数、返回值

// 匿名函数
func(参数)(返回值){
    函数体
}
// 闭包
func adder() func(int) int {
        var x int
        return func(y int) int {
                x += y
                return x
        }
}
func main() {
        var f = adder()
        fmt.Println(f(10)) //10
        fmt.Println(f(20)) //30
        fmt.Println(f(30)) //60
}

interface接口定义和使用场景

type 接口类型名 interface{
    方法名1( 参数列表1 ) 返回值列表1
    方法名2( 参数列表2 ) 返回值列表2
    …
}

defer 方法使用及场景

Go语言中的defer语句会将其后面跟随的语句进行延迟处理。在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行,也就是说,先被defer的语句最后被执行,最后被defer的语句,最先被执行。
由于defer语句延迟调用的特性,所以defer语句能非常方便的处理资源释放问题。比如:资源清理、文件关闭、解锁及记录时间等。
在Go语言的函数中return语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步。而defer语句执行的时机就在返回值赋值操作后,RET指令执行前。

recover()必须搭配defer使用。
defer一定要在可能引发panic的语句之前定义。

_, ok 模式的使用场景

我们在使用Golang编程时,经常会使用到Golang比较特殊的一种写法,即:", OK"
使用场景:在一个表达式返回2个参数的时候使用,第一个参数是一个值或者nil,第二个参数是true/false或者一个错误error
在一个需要赋值的if条件语句中,使用这种模式去检测第二个参数值会让代码显得优雅简洁。

在函数返回时检测错误
检测映射中是否存在一个键值
检测一个接口类型变量 varI 是否包含了类型 T:类型断言
检测一个通道 ch 是否关闭