一、熟悉Go语言运行环境、包管理工具(手动装一下Go环境并从零运行一个依赖第三方库的Go程序)
go modules是官方提倡的新的包管理,乃至项目管理机制,可以不再需要GOPATH的存在。
1、go module 的初始化
golang提供了一个 环境变量“GO111MODULE”,默认值为auto,如果当前目录里有go.mod文件,就使用 go modules, 否则就使用旧的GOPATH和vendor机制,因为在modules机制下 go get 只会下载 go modules,这一行为会在以后版本中成为默认值,这里我们保持auto即可,如果你想直接使用modules而不需要从GOPATH过度,那么把“GO111MODULE”设置为 on 。
modules和传统的GOPATH不同,不需要包含例如src,bin这样的子目录,一个源代码目录甚至是空目录都可以作为module,只要其中包含有go.mod文件。
在新建的文件夹中写好依赖了第三方包的go程序,即 main.go
在该文件夹下 通过 ls 命令,可以查看到 目前只有 main.go 文件
(1)初始化modules,需要用到如下命令(前提是已经安装配置好golang1.11)
go mod init {module name}
我们的module叫test,所以
go mod init test
初始化完成后会在目录下生成一个 go.mod 文件,里面内容只有一行“ module test”。
(2)包管理 当我们使用 go build,go test 和 go list 时,go 会自动更新 go.mod 文件,将依赖关系写入其中。
使用如下命令会自动更新依赖关系,并将包下载放入 cache。
go mod tidy
运行完这个命令后,文件夹中会多一个 go.sum 文件。
go.sum文件中主要是我们直接引用的package和它自身需要所依赖的版本记录,go modules 就是根据这些去找到需要的packages的。
btw,如果我们不做任何修改,默认会使用最新的包版本,如果包打过tag,那么就会使用最新的那个tag对应的版本。
**(3)使用 go build 来编译我们的代码
go build -mod=readonly
在这个模式下任何会导致依赖关系变动的情况都将导致build失败,前面提到过build能查找并更新依赖关系,使用这个选项可以检查依赖关系的变动。
同时,当运行完这个命令以后,文件夹中会生成一个 test 的执行文件,
至此,main.go 的代码已经成功完成构建,包管理都由 go modules 替我们完成了。
btw,go build -mod=vendor
的意思是忽略cache里的包,只使用vendor目录里面的版本。
2、包的版本控制
包管理的另外一项重要功能就是包的版本控制
。modules同样可以做到。
btw,在介绍版本控制之前,我们要先明确一点,如果上层目录和下层目录的go.mod里有相同的package规则,那么上层目录的无条件覆盖下层目录,目的是为了main module的构建不会被依赖的package所影响。
go.mod 文件内容如下:
module test
require github.com/chromedp/chromedp v0.1.2
前面部分是包的名字,也就是import时需要写的部分,而空格之后的是版本号,版本号遵循如下规律:
vX.Y.Z-pre.0.yyyymmddhhmmss-abcdefabcdef
vX.0.0-yyyymmddhhmmss-abcdefabcdef
vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdefabcdef
vX.Y.Z
即 版本号+时间戳+hash
我们自己指定版本时只需要制定版本号即可,没有版本tag的则需要找到对应commit的时间和hash值。
默认使用最新版本的package。
如果我们需要修改包的版本号(比如想使用chromedp 的v0.1.0版本)
1、只需要如下命令:
go mod edit -require="github.com/chromedp/chromedp@v0.1.0"
@后面加上你需要的版本号。go.mod已经修改了:
module test
require github.com/chromedp/chromedp v0.1.0
2、还需要让go modules 更新依赖,这里我们手动执行go mod tidy
命令,即可切换到v0.1.0版本。
[golang包管理解决之道——go modules初探
再探go modules:使用与细节
用 golang 1.11 module 做项目版本管理
二、熟悉Go语言以下内容:
#### 1) 数据类型(string、slice、map)
Go 语言有 3 种数据结构可以让用户管理集合数据:数组、切片和映射。了解这些数据结构,一般会从数组开始,因为数组是切片和映射的基础数据结构。数组存储的类型可以是内置类型,如整型或者字符串,也可以是某种结构类型。
##### 数组
如果使用...替代数组的长度, Go 语言会根据初始化时数组元素的数量来确定该数组的长度。
// 声明一个整型数组
// 用具体值初始化每个元素// 容量由初始化值的数量决定
array := [...]int{10, 20, 30, 40, 50}
二维数组
// 声明一个二维整型数组,两个维度分别存储 4 个元素和 2 个元素
var array [4][2]int
// 使用数组字面量来声明并初始化一个二维整型数组
array := [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}
// 声明并初始化外层数组中索引为 1 个和 3 的元素
array := [4][2]int{1: {20, 21}, 3: {40, 41}}
// 声明并初始化外层数组和内层数组的单个元素
array := [4][2]int{1: {0: 20}, 3: {1: 41}}
只要类型一致,就可以将多维数组互相赋值,
// 将 array1 的索引为 1 的维度复制到一个同类型的新数组里
var array3 [2]int = array1[1]
// 将外层数组的索引为 1、内层数组的索引为 0 的整型值复制到新的整型变量里
var value int = array1[1][0]
使用指针在函数间传递大数组
//使用值传递,在函数间传递大数组
//声明一个需要 8 MB 的数组
var array [1e6]int
// 将数组传递给函数 foo
foo(array)// 函数 foo 接受一个 100 万个整型值的数组
func foo(array [1e6]int) {
...
}
//使用指针在函数间传递大数组
// 分配一个需要 8 MB 的数组
var array [1e6]int
// 将数组的地址传递给函数 foo
foo(&array)
// 函数 foo 接受一个指向 100 万个整型值的数组的指针
func foo(array *[1e6]int) {
...
}
##### 切片
切片是围绕动态数组的概念构建的,可以按需自动增长和缩小。切片的动态增长是通过内置函数 append
来实现的。这个函数可以快速且高效地增长切片。还可以通过对切片再次切片来缩小一个切片的大小。因为切片的底层内存也是在连续块中分配的,所以切片还能获得索引、迭代以及为垃圾回收优化的好处。
内部实现
切片有3个字段的数据结构,这些数据结构包含 Go 语言需要操作底层数组的元数据。这 3 个字段分别是指向底层数组的指针、切片访问的元素的个数(即长度)和切片允许增长到的元素个数(即容量)。
是否能提前知道切片需要的容量通常会决定要如何创建切片。
方法一:使用内置的make函数
- 使用长度声明一个字符串切片
// 创建一个字符串切片
// 其长度和容量都是 5 个元素
slice := make([]string, 5)
//如果只指定长度,那么切片的容量和长度相等。
2)使用长度和容量声明整型切片
// 创建一个整型切片
// 其长度为 3 个元素,容量为 5 个元素
slice := make([]int, 3, 5)
方法二:通过切片字面量来声明切片
// 创建字符串切片
// 其长度和容量都是 5 个元素
slice := []string{"Red", "Blue", "Green", "Yellow", "Pink"}
// 创建一个整型切片
// 其长度和容量都是 3 个元素
slice := []int{10, 20, 30}
使用索引声明切片
// 创建字符串切片
// 使用空字符串初始化第 100 个元素
slice := []string{99: ""}
如果在[]运算符里指定了一个值,那么创建的就是数组而不是切片。只有不指定值
的时候,才会创建切片.
创建nil切片
空切片在底层数组包含 0 个元素,也没有分配任何存储空间。想表示空集合时空切片很有用,例如,数据库查询返回 0 个查询结果时
// 声明空切片
// 使用 make 创建空的整型切片
slice := make([]int, 0)
// 使用切片字面量创建空的整型切片
slice := []int{}
使用切片创建切片
// 创建一个整型切片
// 其长度和容量都是 5 个元素
slice := []int{10, 20, 30, 40, 50}
// 创建一个新切片
// 其长度为 2 个元素,容量为 4 个元素
newSlice := slice[1:3]
对底层数组容量是 k 的切片 slice[i:j]来说
长度: j - i
容量: k - i
btw,共享同一底层数组的切片会导致如果修改了其中一个切片的索引的数据,则另外一个对应切片的数据也会被修改。
// 创建一个整型切片
// 其长度和容量都是 5 个元素
slice := []int{10, 20, 30, 40, 50}
// 创建一个新切片
// 其长度是 2 个元素,容量是 4 个元素
newSlice := slice[1:3]
// 修改 newSlice 索引为 1 的元素
// 同时也修改了原来的 slice 的索引为 2 的元素
newSlice[1] = 35
对于 slice[i:j:k] 或 [2:3:4]
长度: j – i 或 3 - 2 = 1
容量: k – i 或 4 - 2 = 2
切片只能访问到其长度内的元素。切片有额外的容量是很好,但是如果不能把这些容量合并到切片的长度里,这些容量就没有用处。
##### 映射
映射是一种数据结构,用于存储一系列无序的键值对。
映射里基于键来存储值。
映射功能强大的地方是,能够基于键快速检索数据。键就像索引一样,指向该键关联的值。
1.数组是构造切片和映射的基石。
2.Go 语言里切片经常用来处理数据的集合,映射用来处理具有键值对结构的数据。
3.函数 make 可以创建切片和映射,并指定原始的长度和容量。也可以直接使用切片和映射字面量,或者使用字面量作为变量的初始值。
4.有容量限制,不过可以使用内置的 append 函数扩展容量。
5.的增长没有容量或者任何限制。
6.函数 len 可以用来获取切片或者映射的长度。
7.函数 cap 只能用于切片。
8.组合,可以创建多维数组和多维切片。也可以使用切片或者其他映射作为映射的值。但是切片不能用作映射的键。
9.片或者映射传递给函数成本很小,并且不会复制底层的数据结构
#### 2) 并发编程(goroutine、channel)
当一个函数创建为 goroutine时, Go 会将其视为一个独立的工作单元。这个单元会被调度到可用的逻辑处理器上执行。
Go 语言的并发同步模型来自一个叫作通信顺序进程(Communicating Sequential Processes, CSP)的范型(paradigm)。 CSP 是一种消息传递模型,通过在 goroutine 之间传递数据来传递消息,而不是对数据进行加锁来实现同步访问。
用于在 goroutine 之间同步和传递数据的关键数据类型叫作通道(channel)。
创建goroutine的例子
01// 这个示例程序展示如何创建 goroutine
02 // 以及调度器的行为
03 package main
04
05 import (
06 "fmt"
07 "runtime"
08 "sync"
09 )
10
11 // main 是所有 Go 程序的入口
12 func main() {
13 // 分配一个逻辑处理器给调度器使用
14 runtime.GOMAXPROCS(1)
15
16 // wg 用来等待程序完成
17 // 计数加 2,表示要等待两个 goroutine
18 var wg sync.WaitGroup
19 wg.Add(2)
20
21 fmt.Println("Start Goroutines")
22
23 // 声明一个匿名函数,并创建一个 goroutine
24 go func() {
25 // 在函数退出时调用 Done 来通知 main 函数工作已经完成
26 defer wg.Done()
27
28 // 显示字母表 3 次
29 for count := 0; count < 3; count++ {
30 for char := 'a'; char < 'a'+26; char++ {
31 fmt.Printf("%c ", char)
32 }
33 }
34 }()
35
36 // 声明一个匿名函数,并创建一个 goroutine
37 go func() {
38 // 在函数退出时调用 Done 来通知 main 函数工作已经完成
39 defer wg.Done()
40
41 // 显示字母表 3 次
42 for count := 0; count < 3; count++ {
43 for char := 'A'; char < 'A'+26; char++ {
44 fmt.Printf("%c ", char)
45 }
46 }
47 }()
48
49 // 等待 goroutine 结束
50 fmt.Println("Waiting To Finish")
51 wg.Wait()
52
53 fmt.Println("\nTerminating Program")
54 }
关于该程序中涉及到的defer
Defer is used to ensure that a function call is performed later in a program’s execution, usually for purposes of cleanup. defer is often used where e.g. ensure and finally would be used in other languages.
defer的思想类似于C++中的析构函数,不过Go语言中“析构”的不是对象,而是函数,defer就是用来添加函数结束时执行的语句。注意这里强调的是添加,而不是指定,因为不同于C++中的析构函数是静态的,Go中的defer是动态的。
Go 标准库的 runtime 包里有一个名为GOMAXPROCS
的函数,通过它可以指定调度器可用的逻辑处理器的数量。用这个函数,可以给每个可用的物理处理器在
运行的时候分配一个逻辑处理器。
#### 3) socket编程(net包)
#### 4) http编程(http包)
#### 5) json解析(json包以及其它第三方包)
json的简单介绍
[JSON](JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式。它基于 [ECMAScript] (欧洲计算机协会制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。
#### 6) 流处理(binary包)