一、熟悉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包)