1. 程式人生 > 其它 >Golang學習-CH2 Go語言容器

Golang學習-CH2 Go語言容器

目錄

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 關鍵字宣告,兩種方法的初始化效果都是一致的。

  1. 通過 container/list 包的 New() 函式初始化 list
變數名 := list.New()
  1. 通過 var 關鍵字宣告初始化 list
var 變數名 list.List

列表與切片和 map 不同的是,列表並沒有具體元素型別的限制,因此,列表的元素可以是任意型別,這既帶來了便利,也引來一些問題,例如給列表中放入了一個 interface{} 型別的值,取出值後,如果要將 interface{} 轉換為其他型別將會發生宕機

列表中插入元素

雙鏈表支援從佇列前方或後方插入元素,分別對應的方法是 PushFrontPushBack

這兩個方法都會返回一個 *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

http://c.biancheng.net/view/5722.html