Golang學習-CH2 Go語言容器
- 3.1 Go語言陣列
- 3.2 Go語言多維陣列
- 3.3 Go語言切片
- 3.4 Go語言append()為切片新增元素
- 3.5 Go語言copy():切片複製
- 3.6 Go語言從切片中刪除元素
- 3.7 Go語言range關鍵字:迴圈迭代切片
- 3.8 Go語言多維切片
- 3.9 Go語言map(對映)
- 3.10 Go語言遍歷map
- 3.11 Go語言map元素的刪除和清空
- 3.12 Go語言map的多鍵索引——多個數值條件可以同時查詢
- 3.13 Go語言sync.Map(在併發環境中使用的map)
- 3.14 Go語言list(列表)
- 3.15 Go語言nil:空值/零值
- 3.16 Go語言make和new關鍵字的區別及實現原理
3.1 Go語言陣列
陣列是一個由固定長度的特定型別元素組成的序列。和陣列對應的型別是 Slice(切片),Slice 是可以增長和收縮的動態序列。
Go語言陣列的宣告:
var 陣列變數名 [元素數量]Type
var a [3]int // 定義三個整數的陣列 var q [3]int = [3]int{1, 2, 3} //字面初始化 var r [3]int = [3]int{1, 2} //初始全為0,1 2 0 q := [...]int{1, 2, 3} //省略號代表陣列長度根據初始化值個數計算 fmt.Printf("%T\n", q) // "[3]int"
- Go中陣列可以進行賦值,但是需要是同類型的陣列。[3]int 和 [4]int 是兩種不同的陣列型別
- 同樣也可以進行判斷陣列是否相等或者不等,
==
和!=
。判斷的標準是所有元素是否對應相等,前提仍是型別相同 - 遍歷陣列
for k,v := range team{
fmt.Println(k,v)
}
3.2 Go語言多維陣列
var array_name [size1][size2]...[sizen] array_type //示例 // 宣告一個二維整型陣列,兩個維度的長度分別是 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}}
3.3 Go語言切片
切片(slice)是對陣列的一個連續片段的引用,所以切片是一個引用型別。Go語言中切片的內部結構包含地址、大小和容量。
從陣列或切片生成新的切片
切片預設指向一段連續記憶體區域,可以是陣列,也可以是切片本身。
var a = [3]int{1, 2, 3}
fmt.Println(a, a[1:2]) //左閉右開
- 當預設開始位置時,表示從連續區域開頭到結束位置;
- 當預設結束位置時,表示從開始位置到整個連續區域末尾;
- 兩者同時預設時,與切片本身等效;
- 兩者同時為 0 時,等效於空切片,一般用於切片復位。
直接宣告新的切片
除了可以從原有的陣列或者切片中生成切片外,也可以宣告一個新的切片。
var name []Type //中括號內為空
實際體感上:從陣列中生成切片更像Python中的切片應用,後者的新切片更像C++中vector的作用
實際示例:
// 宣告字串切片
var strList []string
// 宣告整型切片
var numList []int
// 宣告一個空切片
var numListEmpty = []int{}
// 輸出3個切片
fmt.Println(strList, numList, numListEmpty)
// 輸出3個切片大小
fmt.Println(len(strList), len(numList), len(numListEmpty))
// 切片判定空的結果
fmt.Println(strList == nil) //true
fmt.Println(numList == nil) //true
fmt.Println(numListEmpty == nil) //true
切片是動態結構,只能與 nil 判定相等,不能互相判定相等。
使用make函式構造切片
make( []Type, size, cap ) //切片型別,分配元素數,預分配數量(容量)
使用 make() 函式生成的切片一定發生了記憶體分配操作,但給定開始與結束位置(包括切片復位)的切片只是將新的切片結構指向已經分配好的記憶體區域,設定開始與結束位置,不會發生記憶體分配操作。
3.4 Go語言append()為切片新增元素
Go語言的內建函式 append() 可以為切片動態新增元素。
不過需要注意的是,在使用 append() 函式為切片動態新增元素時,如果空間不足以容納足夠多的元素,切片就會進行“擴容”,此時新切片的長度會發生改變。切片在擴容時,容量的擴充套件規律是按容量的 2 倍數進行擴充
示例:
var a []int
a = append(a, 1) // 追加1個元素
a = append(a, 1, 2, 3) // 追加多個元素, 手寫解包方式
a = append(a, []int{1,2,3}...) // 追加一個切片, 切片需要解包
因為 append 函式返回新切片(臨時切片)的特性,所以切片也支援鏈式操作,我們可以將多個 append 操作組合起來,實現在切片中間插入元素:
var a []int
a = append(a[:i], append([]int{x}, a[i:]...)...) // 在第i個位置插入x
a = append(a[:i], append([]int{1,2,3}, a[i:]...)...) // 在第i個位置插入切片
3.5 Go語言copy():切片複製
Go語言的內建函式 copy() 可以將一個數組切片複製到另一個數組切片中。copy() 函式的使用格式如下:
copy( destSlice, srcSlice []T) int //返回值為實際發生複製的個數
目標切片必須分配過空間且足夠承載複製的元素個數(不會自動擴容),並且來源和目標的型別必須一致(切片),copy() 函式的返回值表示實際發生複製的元素個數。
複製和引用的不同影響:對於引用而言,本質上還是同一塊記憶體空間,動一發而牽全身
func main() {
// 設定元素數量為1000
const elementCount = 1000
// 預分配足夠多的元素切片
srcData := make([]int, elementCount)
// 將切片賦值
for i := 0; i < elementCount; i++ {
srcData[i] = i
}
// 引用切片資料
refData := srcData
// 預分配足夠多的元素切片
copyData := make([]int, elementCount)
// 將資料複製到新的切片空間中
copy(copyData, srcData)
// 修改原始資料的第一個元素
srcData[0] = 999
// 列印引用切片的第一個元素
fmt.Println(refData[0])
// 列印複製切片的第一個和最後一個元素
fmt.Println(copyData[0], copyData[elementCount-1])
}
3.6 Go語言從切片中刪除元素
Go語言並沒有對刪除切片元素提供專用的語法或者介面,需要使用切片本身的特性來刪除元素,根據要刪除元素的位置有三種情況:從開頭位置刪除、從中間位置刪除和從尾部刪除,其中刪除切片尾部的元素速度最快。
從開頭位置刪除:
- 直接移動資料指標
a = []int{1, 2, 3}
a = a[1:] // 刪除開頭1個元素
a = a[N:] // 刪除開頭N個元素
- 不移動陣列指標,後續資料前移,append原地完成
a = []int{1, 2, 3}
a = append(a[:0], a[1:]...) // 刪除開頭1個元素
a = append(a[:0], a[N:]...) // 刪除開頭N個元素
- 使用copy函式:copy對a產生了影響,再切片
a = []int{1, 2, 3}
a = a[:copy(a, a[1:])] // 刪除開頭1個元素
a = a[:copy(a, a[N:])] // 刪除開頭N個元素
從中間位置刪除:
a = []int{1, 2, 3, ...}
a = append(a[:i], a[i+1:]...) // 刪除中間1個元素
a = append(a[:i], a[i+N:]...) // 刪除中間N個元素
a = a[:i+copy(a[i:], a[i+1:])] // 刪除中間1個元素
a = a[:i+copy(a[i:], a[i+N:])] // 刪除中間N個元素
從尾部刪除:
a = []int{1, 2, 3}
a = a[:len(a)-1] // 刪除尾部1個元素
a = a[:len(a)-N] // 刪除尾部N個元素
當需要大量進行中間位置刪除操作時,可以考慮使用雙鏈表等其他容器。
3.7 Go語言range關鍵字:迴圈迭代切片
Go語言有個特殊的關鍵字 range,它可以配合關鍵字 for 來迭代切片裡的每一個元素:
// 建立一個整型切片,並賦值
slice := []int{10, 20, 30, 40}
// 迭代每一個元素,並顯示其值
for index, value := range slice {
fmt.Printf("Index: %d Value: %d\n", index, value)
}
- 實際上range返回的返回的是每個元素的副本,而不是直接返回對該元素的引用
- 關鍵字 range 總是會從切片頭部開始迭代。如果想對迭代做更多的控制,則可以使用傳統的 for 迴圈
- range 關鍵字不僅僅可以用來遍歷切片,它還可以用來遍歷陣列、字串、map 或者通道等
3.8 Go語言多維切片
示例
//宣告一個二維切片
var slice [][]int
//為二維切片賦值
slice = [][]int{{10}, {100, 200}}
// 宣告一個二維整型切片並賦值
slice := [][]int{{10}, {100, 200}}
// 為第一個切片追加值為 20 的元素
slice[0] = append(slice[0], 20)
3.9 Go語言map(對映)
Go語言中 map 是一種特殊的資料結構,一種元素對(pair)的無序集合,pair 對應一個 key(索引)和一個 value(值),所以這個結構也稱為關聯陣列或字典,這是一種能夠快速尋找值的理想結構,給定 key,就可以迅速找到對應的 value。
值型別與引用型別
map是引用型別
https://www.cnblogs.com/aresxin/p/GO-zhi-lei-xing-yu-yin-yong-lei-xing.html
map定義和使用
var mapname map[keytype]valuetype
在宣告的時候不需要知道 map 的長度,因為 map 是可以動態增長的,未初始化的 map 的值是 nil,使用函式 len() 可以獲取 map 中 pair 的數目。
func main() {
var mapLit map[string]int
var mapAssigned map[string]int
mapLit = map[string]int{"one": 1, "two": 2}
mapCreated := make(map[string]float32)
mapAssigned = mapLit
mapCreated["key1"] = 4.5
mapCreated["key2"] = 3.14159
mapAssigned["two"] = 3
fmt.Printf("Map literal at \"one\" is: %d\n", mapLit["one"])
fmt.Printf("Map created at \"key2\" is: %f\n", mapCreated["key2"])
fmt.Printf("Map assigned at \"two\" is: %d\n", mapLit["two"])
fmt.Printf("Map literal at \"ten\" is: %d\n", mapLit["ten"])
}
- mapAssigned 是 mapList 的引用,對 mapAssigned 的修改也會影響到 mapLit 的值。
- 可以使用 make(),但不能使用 new() 來構造 map,如果錯誤的使用 new() 分配了一個引用物件,會獲得一個空引用的指標,相當於聲明瞭一個未初始化的變數並且取了它的地址
- map容量:map 可以根據新增的 key-value 動態的伸縮,因此它不存在固定長度或者最大限制。當 map 增長到容量上限的時候,如果再增加新的 key-value,map 的大小會自動加 1,所以出於效能的考慮,對於大的 map 或者會快速擴張的 map,即使只是大概知道容量,也最好先標明。
- 使用切片作為map的值,一個key對應多個value
mp1 := make(map[int][]int)
mp2 := make(map[int]*[]int)
3.10 Go語言遍歷map
map 的遍歷過程使用 for range 迴圈完成,遍歷時,可以同時獲得鍵和值。
- 如果只遍歷值:使用匿名變數
- 如果只遍歷鍵:使用匿名遍歷或者忽略值
for k, v := range scene {
fmt.Println(k, v)
}
for _, v := range scene {
}
for k := range scene {
}
注意:map無序性,遍歷輸出元素的順序與填充順序無關,不能期望 map 在遍歷時返回某種期望順序的結果。如果需要特定順序的遍歷結果,正確的做法是先排序
scene := make(map[string]int)
// 準備map資料
scene["route"] = 66
scene["brazil"] = 4
scene["china"] = 960
// 宣告一個切片儲存map資料
var sceneList []string
// 將map資料遍歷複製到切片中
for k := range scene {
sceneList = append(sceneList, k)
}
// 對切片進行排序
sort.Strings(sceneList)
// 輸出
fmt.Println(sceneList)
3.11 Go語言map元素的刪除和清空
Go語言提供了一個內建函式 delete(),用於刪除容器內的元素。
刪除特定的鍵值對:
scene := make(map[string]int)
// 準備map資料
scene["route"] = 66
scene["brazil"] = 4
scene["china"] = 960
delete(scene, "brazil")
for k, v := range scene {
fmt.Println(k, v)
}
清空map元素:
Go語言中並沒有為 map 提供任何清空所有元素的函式、方法,清空 map 的唯一辦法就是重新 make 一個新的 map,不用擔心垃圾回收的效率,Go語言中的並行垃圾回收效率比寫一個清空函式要高效的多。
3.12 Go語言map的多鍵索引——多個數值條件可以同時查詢
https://www.tqwba.com/x_d/jishu/22711.html
以struct結構體作為key
// 查詢鍵
type queryKey struct {
Name string
Age int
}
// 建立查詢鍵到資料的對映
var mapper = make(map[queryKey]*Profile)
// 根據原始資料構建查詢索引
func buildIndex(list []*Profile) {
// 遍歷所有資料
for _, profile := range list {
// 構建查詢鍵
key := queryKey{
Name: profile.Name,
Age: profile.Age,
}
// 儲存查詢鍵
mapper[key] = profile
}
}
// 根據條件查詢資料
func queryData(name string, age int) {
// 根據查詢條件構建查詢鍵
key := queryKey{name, age}
// 根據鍵值查詢資料
result, ok := mapper[key]
// 找到資料打印出來
if ok {
fmt.Println(result)
} else {
fmt.Println("no found")
}
}
3.13 Go語言sync.Map(在併發環境中使用的map)
Go語言中的 map 在併發情況下,只讀是執行緒安全的,同時讀寫是執行緒不安全的。
需要併發讀寫時,一般的做法是加鎖,但這樣效能並不高,Go語言在 1.9 版本中提供了一種效率較高的併發安全的 sync.Map,sync.Map 和 map 不同,不是以語言原生形態提供,而是在 sync 包下的特殊結構。
sync.Map 有以下特性:
- 無須初始化,直接宣告即可。
- sync.Map 不能使用 map 的方式進行取值和設定等操作,而是使用 sync.Map 的方法進行呼叫,Store 表示儲存,Load 表示獲取,Delete 表示刪除。
- 使用 Range 配合一個回撥函式進行遍歷操作,通過回撥函式返回內部遍歷出來的值,Range 引數中回撥函式的返回值在需要繼續迭代遍歷時,返回 true,終止迭代遍歷時,返回 false。
- sync.Map 沒有提供獲取 map 數量的方法,替代方法是在獲取 sync.Map 時遍歷自行計算數量
- sync.Map 為了保證併發安全有一些效能損失,因此在非併發情況下,使用 map 相比使用 sync.Map 會有更好的效能。
package main
import (
"fmt"
"sync"
)
func main() {
var scene sync.Map
// 將鍵值對儲存到sync.Map
scene.Store("greece", 97)
scene.Store("london", 100)
scene.Store("egypt", 200)
// 從sync.Map中根據鍵取值
fmt.Println(scene.Load("london"))
// 根據鍵刪除對應的鍵值對
scene.Delete("london")
// 遍歷所有sync.Map中的鍵值對
scene.Range(func(k, v interface{}) bool {
fmt.Println("iterate:", k, v)
return true
})
}
3.14 Go語言list(列表)
列表是一種非連續的儲存容器,由多個節點組成,節點通過一些變數記錄彼此之間的關係,列表有多種實現方法,如單鏈表、雙鏈表等。
在Go語言中,列表使用 container/list 包來實現,內部的實現原理是雙鏈表,列表能夠高效地進行任意位置的元素插入和刪除操作。
初始化列表
list 的初始化有兩種方法:分別是使用 New() 函式和 var 關鍵字宣告,兩種方法的初始化效果都是一致的。
- 通過 container/list 包的 New() 函式初始化 list
變數名 := list.New()
- 通過 var 關鍵字宣告初始化 list
var 變數名 list.List
列表與切片和 map 不同的是,列表並沒有具體元素型別的限制,因此,列表的元素可以是任意型別,這既帶來了便利,也引來一些問題,例如給列表中放入了一個 interface{} 型別的值,取出值後,如果要將 interface{} 轉換為其他型別將會發生宕機。
列表中插入元素
雙鏈表支援從佇列前方或後方插入元素,分別對應的方法是 PushFront
和 PushBack
。
這兩個方法都會返回一個 *list.Element
結構,如果在以後的使用中需要刪除插入的元素,則只能通過 *list.Element
配合Remove()
方法進行刪除。
l := list.New()
l.PushBack("fist")
l.PushFront(67)
列表插入元素方法:
方 法 | 功 能 |
---|---|
InsertAfter(v interface {}, mark * Element) * Element | 在 mark 點之後插入元素,mark 點由其他插入函式提供 |
InsertBefore(v interface {}, mark * Element) *Element | 在 mark 點之前插入元素,mark 點由其他插入函式提供 |
PushBackList(other *List) | 新增 other 列表元素到尾部 |
PushFrontList(other *List) | 新增 other 列表元素到頭部 |
列表中刪除元素
列表插入函式的返回值會提供一個 *list.Element 結構,這個結構記錄著列表元素的值以及與其他節點之間的關係等資訊,從列表中刪除元素時,需要用到這個結構進行快速刪除。
package main
import "container/list"
func main() {
l := list.New()
// 尾部新增
l.PushBack("canon")
// 頭部新增
l.PushFront(67)
// 尾部新增後儲存元素控制代碼
element := l.PushBack("fist")
// 在fist之後新增high
l.InsertAfter("high", element)
// 在fist之前新增noon
l.InsertBefore("noon", element)
// 使用
l.Remove(element)
}
遍歷列表
遍歷雙鏈表需要配合 Front() 函式獲取頭元素,遍歷時只要元素不為空就可以繼續進行,每一次遍歷都會呼叫元素的 Next() 函式
l := list.New()
// 尾部新增
l.PushBack("canon")
// 頭部新增
l.PushFront(67)
for i := l.Front(); i != nil; i = i.Next() {
fmt.Println(i.Value)
}
3.15 Go語言nil:空值/零值
在Go語言中,布林型別的零值(初始值)為 false,數值型別的零值為 0,字串型別的零值為空字串""
,而指標、切片、對映、通道、函式和介面的零值則是 nil。
nil 是Go語言中一個預定義好的識別符號:
nil
識別符號不能和自身比較nil
不是關鍵字或保留字nil
沒有型別
func main() {
fmt.Printf("%T", nil) //編譯報錯:use of untyped nil
print(nil)
}
- 不同型別的
nil
不能進行比較,雖然地址都是0x0
func main() {
var m map[int]string
var ptr *int
fmt.Printf(m == ptr) //編譯報錯
}
- nil 是 map、slice、pointer、channel、func、interface 的零值
- 不同型別的 nil 值佔用的記憶體大小可能是不一樣的
package main
import (
"fmt"
"unsafe"
)
func main() {
var p *struct{}
fmt.Println( unsafe.Sizeof( p ) ) // 8
var s []int
fmt.Println( unsafe.Sizeof( s ) ) // 24
var m map[int]bool
fmt.Println( unsafe.Sizeof( m ) ) // 8
var c chan string
fmt.Println( unsafe.Sizeof( c ) ) // 8
var f func()
fmt.Println( unsafe.Sizeof( f ) ) // 8
var i interface{}
fmt.Println( unsafe.Sizeof( i ) ) // 16
}
3.16 Go語言make和new關鍵字的區別及實現原理
new 只分配記憶體,而 make 只能用於 slice、map 和 channel 的初始化。
// The new built-in function allocates memory. The first argument is a type,
// not a value, and the value returned is a pointer to a newly
// allocated zero value of that type.
func new(Type) *Type
從上面的程式碼可以看出,new 函式只接受一個引數,這個引數是一個型別,並且返回一個指向該型別記憶體地址的指標。同時 new 函式會把分配的記憶體置為零,也就是型別的零值。
make 也是用於記憶體分配的,但是和 new 不同,它只用於 chan、map 以及 slice 的記憶體建立,而且它返回的型別就是這三個型別本身,而不是他們的指標型別,因為這三種類型就是引用型別,所以就沒有必要返回他們的指標了。
// The make built-in function allocates and initializes an object of type
// slice, map, or chan (only). Like new, the first argument is a type, not a
// value. Unlike new, make's return type is the same as the type of its
// argument, not a pointer to it. The specification of the result depends on
// the type:
// Slice: The size specifies the length. The capacity of the slice is
// equal to its length. A second integer argument may be provided to
// specify a different capacity; it must be no smaller than the
// length, so make([]int, 0, 10) allocates a slice of length 0 and
// capacity 10.
// Map: An empty map is allocated with enough space to hold the
// specified number of elements. The size may be omitted, in which case
// a small starting size is allocated.
// Channel: The channel's buffer is initialized with the specified
// buffer capacity. If zero, or the size is omitted, the channel is
// unbuffered.
func make(t Type, size ...IntegerType) Type
Go語言中的 new 和 make 主要區別如下:
- make 只能用來分配及初始化型別為 slice、map、chan 的資料。new 可以分配任意型別的資料;
- new 分配返回的是指標,即型別 *Type。make 返回引用,即 Type;
- new 分配的空間被清零。make 分配空間後,會進行初始化;
實現原理
make
在編譯期的型別檢查階段,Go語言其實就將代表 make 關鍵字的 OMAKE 節點根據引數型別的不同轉換成了 OMAKESLICE、OMAKEMAP 和 OMAKECHAN 三種不同型別的節點,這些節點最終也會呼叫不同的執行時函式來初始化資料結構。
new