Go学习笔记

Go学习笔记

前言

如果你要放弃Golang,你的理由是什么? - 知乎

为什么要使用 Go 语言?Go 语言的优势在哪里? - 知乎

为什么 Go 语言能在中国这么火? - 知乎

golang设计哲学

  • 约定优于配置(convention over configuration)
  • 正交设计
  • 每种特性仅提供一种方法
  • 简单是核心,降低复杂度
  • 面向消息编程

String

反引号包含多行,不会转义

双引号时,只能通过 \n 进行换行

Go 中字符串类型是不能修改的,多个字符串底层都可以共用一段内存

修改字符串:

  1. 整体修改

  2. 转为slice,再转回来

func main() {
        a := "hello world"
        fmt.Println(a)
        a = "Hello world"
        fmt.Println(a)
}

func main() {
        a := "hello world 中国"
        b := []rune(a)
        b[0] = 'H'
        a = string(b)
        fmt.Printf("%v", a)
}

如果要修改像中文字符之类超过 3 个字节的字符,需要转换为 rune 而不是 byte

遍历string需要用for range,不能用for len

Array

  • 数组是固定长度的。使用前必须指定数组长度。

  • 支持多维数组

  • 下标从0开始

var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type

a = [3][4]int{
        {0, 1, 2, 3},   /*  第一行索引为 0 */
        {4, 5, 6, 7},   /*  第二行索引为 1 */
        {8, 9, 10, 11}, /*  第三行索引为 2 */
}

Slice

可以理解为基于array的视图。

type slice struct {
        array unsafe.Pointer
        len   int
        cap   int
}

新增元素,尽量采用append的方式。

删除元素,用:和append,没有erase接口。

拷贝时,不会触发扩容,多余的数据丢弃,不够的话补齐默认值。

扩容时,如果触发内存分配,原array的值不会改变。

扩容规则:

  • 如果新容量超过原容量2倍,以新容量为准

  • 如果切片的容量小于1024个元素,那么扩容的时候slice的cap就翻番,乘以2;超过1024个元素,每次增加容量的四分之一,直到超过新容量。100 1001.25 1001.25*1.25这样子。

  • 如果扩容之后,还没有触及原数组的容量,那么,切片中的指针指向的位置,就还是原数组,如果扩容之后,超过了原数组的容量,那么,Go就会开辟一块新的内存,把原来的值拷贝过来,这种情况丝毫不会影响到原数组。

        newcap := old.cap
        doublecap := newcap + newcap
        if cap > doublecap {
                newcap = cap
        } else {
                if old.len < 1024 {
                        newcap = doublecap
                } else {
                        // Check 0 < newcap to detect overflow
                        // and prevent an infinite loop.
                        for 0 < newcap && newcap < cap {
                                newcap += newcap / 4
                        }
                        // Set newcap to the requested cap when
                        // the newcap calculation overflowed.
                        if newcap <= 0 {
                                newcap = cap
                        }
                }
        }

扩容后会有内存对齐,内存对齐之后,扩容的倍数就会 >= 2 或则 1.25 了。

为什么要内存对齐?

  1. 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

  2. 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

Heap

container/heap包仅封装了堆的操作,没有封装堆的数据结构,需要自己实现堆的数据结构。

需要实现5个接口:

push pop len less swap

heap提供的操作接口:

fix init pop push remove

List

双向链表

type Element struct {
        next, prev *Element
        list *List
        Value interface{}
}

type List struct {
        root Element
        len  int
}

可以存储任何值

元素类型不必一致

提供操作接口:添加 移动 访问 移除 获取长度 遍历

注意range不能作用于list,遍历list写法,为什么没有c++的for auto的写法?

for e := l.Front(); nil != e; e = e.Next() {
        fmt.Println(e.Value)
}

Ring

环形链表

type Ring struct {
        next, prev *Ring
        Value      interface{}
}

提供操作接口

Next prev move new link unlink len do

Map

hash接口

创建

var m map[string]int = map[string]int{"hunter":12,"tony":10}
var m2 map[string]int = make(map[string]int)

取值 [] 没有的话,返回零值,不会往原map中插入元素,和c++不同

改值 [] 有的话修改,没有的话,插入

删除 delete(m, "tony2")

If m is nil or there is no such element, delete is a no-op.

遍历 range 不能期望 map 在遍历时返回某种期望顺序 和lua类似

清空 make一个新的,老的map的内存交给go gc

多键索引 可以提高查询效率,少写代码

type queryKey struct {
        Name string
        Age  int
}
type Profile struct {
        Name    string
        Age     int
        Address string
}
var mapper = make(map[queryKey]*Profile)
func queryData(name string, age int) {
        key := queryKey{Name: name, Age: age}
        result, ok := mapper[key]
        if ok {
                fmt.Println(result)
        } else {
                fmt.Println("没有找到对应的数据")
        }
 
}

多维map少用,每一维使用前都要make

Go语言中的 map 在并发情况下,只读是线程安全的,同时读写是线程不安全的。

Sync.map

import "sync"

无须初始化,直接声明即可

Store 表示存储,Load 表示获取,Delete 表示删除 Range表示遍历

sync.Map 为了保证并发安全有一些性能损失 按需使用

为了提升性能,load、delete、store等操作尽量使用只读的read

sync.Map是通过冗余的两个数据结构(read、dirty),实现性能的提升

为了提高read的key击中概率,采用动态调整,将dirty数据提升为read

对于数据的删除,采用延迟标记删除法,只有在提升dirty的时候才删除

可以参考 这篇文章

Range

迭代array、slice、channel map string

不能遍历list

遍历时,可以同时获得键和值,如只遍历值可以用_占位 和lua类似

不建议在range的时候 增加和删除元素 需要谨慎

range时修改数据用下标

Concurrency and Parallelism

这个要FQ

https://talks.golang.org/2012/waza.slide#1

这个是别人的翻译

https://www.cnblogs.com/concurrency/p/3925951.html

Concurrency是一种问题分解和组织方法,而Parallelism一种执行方式。

两者不同,但是有一定的关系。

Channel

这个ppt写的很好,清晰易懂

https://speakerdeck.com/kavya719/understanding-channels

Goroutine

特点

  1. 用户空间 避免了内核态和用户态的切换导致的成本
  2. 可以由语言和框架层进行调度
  3. 更小的栈空间允许创建大量的实例

goroutine是在golang层面提供了调度器,并且对网络IO库进行了封装,屏蔽了复杂的细节,对外提供统一的语法关键字支持,简化了并发程序编写的成本。

程序启动后,会默认开启一个主routine,可以用go关键字开启新的routine(子routine)

主routine和子routine只有一个区别:栈默认大小 主routine很大,子routine默认2K

Context

方便管理goroutine之间的关系。

MPG模型

定义

M(Machine):一个M直接关联了一个内核线程。

P(processor):代表了M所需的上下文环境,也是处理用户级代码逻辑的处理器。G(Goroutine):其实本质上也是一种轻量级的线程。

img

img

CSP 模型

请记住go并发的真理:

Do not communicate by sharing memory; instead, share memory by

communicating.

不要以共享内存的方式来通信,相反,要通过通信来共享内存。

CSP 模型由并发执行的实体(线程或者进程)所组成,实体之间通过发送消息进行通信,这里发送消息时使用的就是通道,或者叫 channel。CSP 模型的关键是关注 channel,而不关注发送消息的实体。

Go 语言实现了 CSP 部分理论。即理论中的 Process/Channel(对应到语言中的 goroutine/channel)。

Interface

在 Golang 中,interface 是一种抽象类型(abstract type),相对于抽象类型的是具体类型(concrete type),如int string等。

将interface理解为一种约定。

interface 是一组 method 的集合,是 duck-type programming 的一种体现。

不关心implement,只关心method 。

Go interface具有非侵入式特性,这是其他语言不具备的。

将interface作为函数参数时,会带来性能的降低。 interface 作为函数参数,runtime 的时候会动态的确定行为。而使用 struct 作为参数,编译期间就可以确定了。

使用接口的情况:

  • 用户 API 需要提供实现细节的时候。
  • API 的内部需要维护多种实现。
  • 可以改变的 API 部分已经被识别并需要解耦。

不使用接口的情况:

  • 为了使用接口而使用接口。
  • 推广算法。
  • 当用户可以定义自己的接口时。

GC

https://www.cnblogs.com/hezhixiong/p/9577199.html

https://studygolang.com/articles/27243?fr=sidebar

内存分配

Golang 内存管理 - 知乎

详解Go语言的内存模型及堆的分配管理 - 知乎

Escape Analysis 逃逸分析

逃逸分析是解决指针作用范围的编译优化方法

把变量合理地分配到它该去的地方,“找准自己的位置”,尽量把那些不需要分配到堆上的变量直接分配到栈上,堆上的变量少了,会减轻分配堆内存的开销,同时也会减少gc的压力,提高程序的运行速度。

原则是:如果一个函数返回对一个变量的引用,那么它就会发生逃逸。编译器确定。

Sync.Mutex

资源临界区

Sync.RWMutex

读写锁

  • 多个读锁可以同时操作
  • 写锁一旦加锁,不管是读操作还是写操作都不能被执行
  • 读锁加锁了之后,只要读锁还没有解锁,写操作是不能被执行的

零散知识点

  • 短声明变量”:=”为什么不能放在函数外?

At the top level, every declaration begins with a keyword. This simplifies parsing.

posted @ 2022-03-09 16:53  天下太平  阅读(40)  评论(0编辑  收藏  举报