Famousmai's blog Famousmai's blog
首页
👍 网站介绍
💯 编程分享
✍️ 生活感悟
🎮 游戏人生
📈 网站动态
💌 收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Famous Mai

爱玩辅助的后端小哥
首页
👍 网站介绍
💯 编程分享
✍️ 生活感悟
🎮 游戏人生
📈 网站动态
💌 收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 算法

  • 学习笔记

    • Golang学习笔记
      • 常用命令说明
        • go get
        • go mod
      • 包、工作区、GOPATH
        • 环境变量说明
        • 依赖管理发展
        • 程序实体的访问权限
        • 包冲突解决
      • 类型断言
        • 类型断言表达式
      • 类型转换
        • 注意点
        • 别名类型
      • 数组和切片
        • 区别
        • 长度和容量分析
        • ⭐️ Slice扩容规则
        • 扩容步骤(1.17版本)
        • 1.18版本后
        • 源码对比
        • 改动的原因
      • container包中的那些容器
        • 为什么链表可以做到开箱即用?
        • Ring与List的区别
      • 时间处理
    • Docker学习笔记
    • Consul学习笔记
    • Elk学习笔记
    • 数据结构与算法之美
    • Go实操项目学习笔记
    • Paypal对接学习笔记
  • 踩坑记录

  • 面试分享

  • 技术方案文章梳理

  • 设计模式

  • 编程分享
  • 学习笔记
famousmai
2023-02-17
目录

Golang学习笔记

# 学习笔记

# 常用命令说明

# go get

Go 源码的安装

# go mod

  • init:初始化,创建 go.mod 文件

  • download:下载模块到本地缓存

  • tidy:增加需要的依赖,删除不需要的依赖

    实际开发中

    尽量提交之前执行下 go tidy ,减少构建时无效依赖包的拉取。

# 包、工作区、GOPATH

# 环境变量说明

  • GOROOT:Go 语言安装根目录的路径,也就是 GO 语言的安装路径。

  • GOPATH:若干工作区目录的路径。是我们自己定义的工作空间。

  • GOBIN:GO 程序生成的可执行文件(executable file)的路径。

  • GOPROXY: 是一个 Go Proxy 站点 URL 列表。

# 查看命令
$ go env | grep -E "GOPATH|GOROOT"
GOPATH="/Users/mym/go"
GOROOT="/opt/homebrew/Cellar/go/1.19.1/libexec"
1
2
3
4

# 依赖管理发展

Go语言依赖管理文章参考说明:一文搞懂Go语言的最新依赖管理 (opens new window)

# 程序实体的访问权限

三种

  1. 包级私有
  2. 模块级私有
  3. 公开的

使用说明

  • 通过名称,Go 语言自然地把程序实体的访问权限划分为了包级私有(首字母小写)的和公开(首字母大写)的

  • 在 Go 1.5 及后续版本中,我们可以通过创建internal代码包让一些程序实体仅仅能被当前模块中的其他代码引用。

    image-20230129214526922

# 包冲突解决

  1. 给包名设置别名

    import customerFlag "go_intro/flag" 
    
    1
  2. 默认导入整个包

    import . "go_intro/flag"
    
    1
  3. 如果非要用到包中的函数或者变量,可以隐式导入

     import _ "go_intro/flag" 
    
    1
  4. 直接采用不同的包声明!

# 类型断言

# 类型断言表达式

  • 语法形式是x.(T)。
  • 其中的x代表要被判断类型的值。这个值当下的类型必须是接口类型的,不过具体是哪个接口类型其实是无所谓的。
  • 这里的具体语法是interface{}(x),例如前面展示的interface{}(container)。

img

# 类型转换

# 注意点

  1. 整数类型值、整数常量之间的类型转换

    • int16转换为int8。请看下面这段代码:

      var srcInt = int16(-255)
      dstInt := int8(srcInt)
      
      1
      2
    • 整数在 Go 语言以及计算机中都是以补码的形式存储

    • int16类型的值-255的补码是1111111100000001

    • 较高位置(或者说最左边位置)上的 8 位二进制数直接截掉,从而得到 00000001

    • 又由于其最左边一位是0,表示它是个正整数,以及正整数的补码就等于其原码,所以dstInt的值就是 1

  2. 整数值转换为一个string类型的值

    • 被转换的整数值需要是一个有效的Unicode代码点,否则转换的结果将会是"�"
    • 字符'�'的 Unicode 代码点是U+FFFD
  3. string类型与各种切片类型之间的互转的

    • string类型向**[]byte类型**转换时代表着以 UTF-8 编码的字符串会被拆分成零散、独立的字节。
    • string类型向**[]rune类型**转换时代表着字符串会被拆分成一个个 Unicode 字符。

# 别名类型

别名类型与其源类型的区别恐怕只是在名称上,它们是完全相同的。

两个别名类型。byte是uint8的别名类型,而rune是int32的别名类型。

img

# 数组和切片

# 区别

  1. **长度:**数组 长度是固定的,切片 是可变长的
  2. 类型:数组 是值类型,切片 是引用类型
  3. 切片可以看作是对数组的一层简单的封装,切片也可以被看作是对数组的某个连续片段的引用。
  4. 数组的容量 cap 永远等于其长度 len ,都是不可变的。切片的容量却不是这样,并且它的变化是有规律可寻的

# 长度和容量分析

s3 := []int{1, 2, 3, 4, 5, 6, 7, 8}
s4 := s3[3:6]
fmt.Printf("The length of s4: %d\n", len(s4))
fmt.Printf("The capacity of s4: %d\n", cap(s4))
fmt.Printf("The value of s4: %d\n", s4)
1
2
3
4
5
  • [3:6]要表达的就是透过新窗口能看到的s3中元素的索引范围是从3到5(注意,不包括6)
  • s4的长度:6减去3
  • s4的容量:其底层数组的长度8, 减去上述切片表达式中的那个起始索引3,即5
  • 切片代表的窗口是无法向左扩展的
  • 把切片的窗口向右扩展到最大的方法。对于s4来说,切片表达式 s4[0:cap(s4)] 就可以做到

# ⭐️ Slice扩容规则

题外话:以前一直看到扩容规则后的老版本,直到有一天想拜读一下源码,才发现扩容规则已经改了

看源码和关注GitHub go版本更新才是最重要的呀

# 扩容步骤(1.17版本)

  1. 预估扩容后的容量 newCap
    • 如果扩容前容量翻倍还是小于所需最小容量,那么预估容量就等于最小容量 oldCap * 2 < cap,newCap = cap
    • 否则,判断元素个数
      • 元素个数 oldLen < 1024 翻倍 newCap = oldCap * 2
      • 元素个数 oldLen >= 1024 扩 1/4 newCap = oldCap * 1.25
  2. newCap 个元素占用多大内存
    • 预估分配到内存 = 预估容量 * 元素类型大小
  3. 内存对齐:匹配合适的内存规格
    • go语言内置的内存管理模块,提前向操作系统申请一批内存,分成常用规格管理起来,如8、16、32、48
    • 扩容时,会帮我们自动匹配到足够大且最接近的内存

# 1.18版本后

预估 newCap 的规则改变了,主要有两个

  1. 1024 改为了 值为 256 的 threshold
  2. 扩 1/4 改成了,扩 (newcap + 3*threshold) / 4

# 源码对比

查看改动的提交 (opens new window)

// 1.17
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
  newcap = cap
} else {
  if old.cap < 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
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 1.18 
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
  newcap = cap
} else {
  const threshold = 256
  if old.cap < threshold {
    newcap = doublecap
  } else {
    // Check 0 < newcap to detect overflow
    // and prevent an infinite loop.
    for 0 < newcap && newcap < cap {
      // Transition from growing 2x for small slices
      // to growing 1.25x for large slices. This formula
      // gives a smooth-ish transition between the two.
      newcap += (newcap + 3*threshold) / 4
    }
    // Set newcap to the requested cap when
    // the newcap calculation overflowed.
    if newcap <= 0 {
      newcap = cap
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 改动的原因

  • 旧版本的扩容规则,触发大于1024增长1.25倍的条件时,算出来的新容量要小于之前计算出的容量

    Go slice新的扩容机制 - 你背的八股文过时啦

  • 新版本的扩容规则,随着切片容量的变大,增长比例逐渐向着1.25进行靠拢。

    img

参考文章

https://blog.csdn.net/weixin_52690231/article/details/124827013

https://juejin.cn/post/7136774425415794719

# container包中的那些容器

# 为什么链表可以做到开箱即用?

延迟初始化,你可以理解为把初始化操作延后,仅在实际需要的时候才进行。延迟初始化的优点在于“延后”,它可以分散初始化操作带来的计算量和存储空间消耗。

每次操作先去判断链表是否已经被初始化,程序的性能将会降低

但一些方法是无需对是否初始化做判断

  • Front方法和Back方法,一旦发现链表的长度为0, 直接返回nil就好了。
  • 用于删除元素、移动元素,以及一些用于插入元素的方法中,只要判断一下传入的元素中指向所属链表的指针,是否与当前链表的指针相等就可以了
  • 如果不相等,就一定说明传入的元素不是这个链表中的,后续的操作就不用做了。反之,就一定说明这个链表已经被初始化了

# Ring与List的区别

  1. 表示方式
    • Ring:由它自身即可代表
    • List:需要由它以及Element类型联合表示
  2. 表示维度
    • Ring:只代表了其所属的循环链表中的一个元素
    • List:代表了一个完整的链表
  3. 初始化指定元素数量(New函数在功能上的不同)
    • Ring:可以指定它包含的元素的数量,一旦被创建,其长度是不可变的
    • List:却不能这样做(也没有必要这样做)
  4. 初始化时的长度
    • Ring:声明时,长度为1的循环链表
    • List:声明时,一个长度为0的链表,List中的根元素不会持有实际元素值
  5. Len方法的时间复杂度
    • Ring:O(N)
    • List:O(1)

# 时间处理

// ConvTime 格式化微博时间
func ConvTime(timeStr, currentYear string) string {
	nowTime := time.Now()
	if strings.Contains(timeStr, "分钟前") {
		min, _ := strconv.Atoi(ReParse(`^(\d+)分钟`, timeStr))
		createdTime := nowTime.Add(-time.Duration(min) * time.Minute)
		return createdTime.Format(TimeLayout)
	}
	if strings.Contains(timeStr, "小时前") {
		hour, _ := strconv.Atoi(ReParse(`^(\d+)小时`, timeStr))
		createdTime := nowTime.Add(-time.Duration(hour) * time.Hour)
		return createdTime.Format(TimeLayout)
	}
	if strings.Contains(timeStr, "今天") {
		return strings.Replace(timeStr, "今天", nowTime.Format("2006-01-02"), -1) + ":00"
	}
	if strings.Contains(timeStr, "昨天") {
		createdTime := nowTime.Add(-24 * time.Hour)
		return strings.Replace(timeStr, "昨天", createdTime.Format("2006-01-02"), -2) + ":00"
	}
	if strings.Contains(timeStr, "月") {
		rp := strings.NewReplacer("月", "-", "日", "")
		timeStr = currentYear + "-" + timeStr + ":00"
		return rp.Replace(timeStr)
	}
	return timeStr
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
编辑 (opens new window)
#Go
上次更新: 2023/02/19, 00:03:59
04.排序
Docker学习笔记

← 04.排序 Docker学习笔记→

最近更新
01
策略模式
03-13
02
单例模式
03-05
03
设计模式介绍
03-05
更多文章>
Theme by Vdoing | Copyright © 2022-2023 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式