日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Go第三篇之大话容器

發布時間:2023/12/20 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Go第三篇之大话容器 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Go語言數組

數組(Array)是一段固定長度的連續內存區域
在 Go 語言中,數組從聲明時就確定,使用時可以修改數組成員,但是數組大小不可變化

Go 語言數組的聲明

數組的寫法如下:

var 數組變量名 [元素數量]T

其中:

  • 數組變量名:數組聲明及使用時的變量名。
  • 元素數量:數組的元素數量。可以是一個表達式,但最終通過編譯期計算的結果必須是整型數值。也就是說,元素數量不能含有到運行時才能確認大小的數值。
  • T 可以是任意基本類型,包括 T 為數組本身。但類型為數組本身時,可以實現多維數組。

下面是一段數組的演示例子:

var team [3]string team[0] = "hammer" team[1] = "soldier" team[2] = "mum" fmt.Println(team)

輸出結果:

[hammer soldier mum]

代碼說明如下:

  • 第 1 行,將 team 聲明為包含 3 個元素的字符串數組。
  • 第 2~4 行,為 team 的元素賦值。

Go語言數組的初始化

數組可以在聲明時使用初始化列表進行元素設置,參考下面的代碼:

var team = [3]string{"hammer", "soldier", "mum"}

這種方式編寫時,需要保證大括號后面的元素數量與數組的大小一致。但一般情況下,這個過程可以交給編譯器,讓編譯器在編譯時,根據元素個數確定數組大小。

var team = [...]string{"hammer", "soldier", "mum"}

...表示讓編譯器確定數組大小。上面例子中,編譯器會自動為這個數組設置元素個數為 3。

遍歷數組(訪問每一個數組元素)

遍歷數組也和遍歷切片類似,看下面代碼:

var team [3]string team[0] = "hammer" team[1] = "soldier" team[2] = "mum" for k, v := range team { fmt.Println(k, v) }

代碼輸出結果:

hammer soldier mum

代碼說明如下:

    • 第 6 行,使用 for 循環,遍歷 team 數組,遍歷出的鍵 k 為數組的索引,值 v 為數組的每個元素值。
    • 第 7 行,將每個鍵值打印出來。

Go語言切片

切片(Slice)是一個擁有相同類型元素的可變長度的序列。Go 語言切片的內部結構包含地址、大小和容量。切片一般用于快速地操作一塊數據集合。如果將數據集合比作切糕的話,切片就是你要的“那一塊”。切的過程包含從哪里開始(這個就是切片的地址)及切多大(這個就是切片的大小)。容量可以理解為裝切片的口袋大小,如下圖所示。


圖:切片結構和內存分配

從數組或切片生成新的切片

切片默認指向一段連續內存區域,可以是數組,也可以是切片本身。

從連續內存區域生成切片是常見的操作,格式如下:

slice [開始位置:結束位置]

  • slice 表示目標切片對象。
  • 開始位置對應目標切片對象的索引。
  • 結束位置對應目標切片的結束索引。

從數組生成切片,代碼如下:

var a = [3]int{1, 2, 3} fmt.Println(a, a[1:2])

a 是一個擁有 3 個整型元素的數組,被初始化數值 1 到 3。使用 a[1:2] 可以生成一個新的切片。代碼運行結果如下:

[1 2 3] ?[2]

[2] 就是 a[1:2] 切片操作的結果。

從數組或切片生成新的切片擁有如下特性:

  • 取出的元素數量為:結束位置-開始位置。
  • 取出元素不包含結束位置對應的索引,切片最后一個元素使用 slice[len(slice)] 獲取。
  • 當缺省開始位置時,表示從連續區域開頭到結束位置。
  • 當缺省結束位置時,表示從開始位置到整個連續區域末尾。
  • 兩者同時缺省時,與切片本身等效。
  • 兩者同時為0時,等效于空切片,一般用于切片復位。


根據索引位置取切片 slice 元素值時,取值范圍是(0~len(slice)-1),超界會報運行時錯誤。生成切片時,結束位置可以填寫 len(slice) 但不會報錯。

下面在具體的例子中熟悉切片的特性。

1) 從指定范圍中生成切片

切片和數組密不可分。如果將數組理解為一棟辦公樓,那么切片就是把不同的連續樓層出租給使用者。出租的過程需要選擇開始樓層和結束樓層,這個過程就會生成切片。示例代碼如下:

var highRiseBuilding [30]int for i := 0; i < 30; i++ { highRiseBuilding[i] = i + 1 } // 區間 fmt.Println(highRiseBuilding[10:15]) // 中間到尾部的所有元素 fmt.Println(highRiseBuilding[20:]) // 開頭到中間的所有元素 fmt.Println(highRiseBuilding[:2])

代碼輸出如下:

[11 12 13 14 15] [21 22 23 24 25 26 27 28 29 30] [1 2]

代碼中構建了一個 30 層的高層建筑。數組的元素值從 1 到 30,分別代表不同的獨立樓層。輸出的結果是不同租售方案。
代碼說明如下:

  • 第 8 行,嘗試出租一個區間樓層。
  • 第 11 行,出租 20 層以上。
  • 第 14 行,出租 2 層以下,一般是商用鋪面。

切片有點像C語言里的指針。指針可以做運算,但代價是內存操作越界。切片在指針的基礎上增加了大小,約束了切片對應的內存區域,切片使用中無法對切片內部的地址和大小進行手動調整,因此切片比指針更安全、強大。

2) 表示原有的切片

生成切片的格式中,當開始和結束都范圍都被忽略,則生成的切片將表示和原切片一致的切片,并且生成的切片與原切片在數據內容上是一致的,代碼如下:

a := []int{1, 2, 3} fmt.Println(a[:])

a 是一個擁有 3 個元素的切片。將 a 切片使用 a[:] 進行操作后,得到的切片與 a 切片一致,代碼輸出如下:

[1 2 3]

3) 重置切片,清空擁有的元素

把切片的開始和結束位置都設為 0 時,生成的切片將變空,代碼如下:

a := []int{1, 2, 3} fmt.Println(a[0:0])

代碼輸出如下:

[]

直接聲明新的切片

除了可以從原有的數組或者切片中生成切片,你也可以聲明一個新的切片。每一種類型都可以擁有其切片類型,表示多個類型元素的連續集合。因此切片類型也可以被聲明。切片類型聲明格式如下:

var name []T

  • name 表示切片類型的變量名。
  • T 表示切片類型對應的元素類型。

下面代碼展示了切片聲明的使用過程:

// 聲明字符串切片 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) fmt.Println(numList == nil) fmt.Println(numListEmpty == nil)

代碼輸出結果:

[] [] []
0 0 0
true
true
false

代碼說明如下:

  • 第 2 行,聲明一個字符串切片,切片中擁有多個字符串。
  • 第 5 行,聲明一個整型切片,切片中擁有多個整型數值。
  • 第 8 行,將 numListEmpty 聲明為一個整型切片。本來會在{}中填充切片的初始化元素,這里沒有填充,所以切片是空的。但此時 numListEmpty 已經被分配了內存,但沒有元素。
  • 第 11 行,切片均沒有任何元素,3 個切片輸出元素內容均為空。
  • 第 14 行,沒有對切片進行任何操作,strList 和 numList 沒有指向任何數組或者其他切片。
  • 第 17 行和第 18 行,聲明但未使用的切片的默認值是 nil。strList 和 numList 也是 nil,所以和 nil 比較的結果是 true。
  • 第 19 行,numListEmpty 已經被分配到了內存,但沒有元素,因此和 nil 比較時是 false。


切片是動態結構,只能與nil判定相等,不能互相判等時。

聲明新的切片后,可以使用 append() 函數來添加元素。

使用 make() 函數構造切片

如果需要動態地創建一個切片,可以使用 make() 內建函數,格式如下:

make( []T, size, cap )

  • T:切片的元素類型。
  • size:就是為這個類型分配多少個元素。
  • cap:預分配的元素數量,這個值設定后不影響 size,只是能提前分配空間,降低多次分配空間造成的性能問題。


示例如下:

a := make([]int, 2) b := make([]int, 2, 10) fmt.Println(a, b) fmt.Println(len(a), len(b))

代碼輸出如下:

[0 0] [0 0]
2 2

a 和 b 均是預分配 2 個元素的切片,只是 b 的內部存儲空間已經分配了 10 個,但實際使用了 2 個元素。

容量不會影響當前的元素個數,因此 a 和 b 取 len 都是 2。

溫馨提示

使用 make() 函數生成的切片一定發生了內存分配操作。但給定開始與結束位置(包括切片復位)的切片只是將新的切片結構指向已經分配好的內存區域,設定開始與結束位置,不會發生內存分配操作。

切片不一定必須經過 make() 函數才能使用。生成切片、聲明后使用 append() 函數均可以正常使用切片。

使用append()為切片添加元素

Go 語言的內建函數 append() 可以為切片動態添加元素。每個切片會指向一片內存空間,這片空間能容納一定數量的元素。當空間不能容納足夠多的元素時,切片就會進行“擴容”。“擴容”操作往往發生在 append() 函數調用時。

切片在擴容時,容量的擴展規律按容量的 2 倍數擴充,例如 1、2、4、8、16……,代碼如下:

var numbers []int for i := 0; i < 10; i++ { numbers = append(numbers, i) fmt.Printf("len: %d cap: %d pointer: %p\n", len(numbers), cap(numbers), numbers) }

代碼輸出如下:
len: 1? cap: 1 pointer: 0xc0420080e8
len: 2? cap: 2 pointer: 0xc042008150
len: 3? cap: 4 pointer: 0xc04200e320
len: 4? cap: 4 pointer: 0xc04200e320
len: 5? cap: 8 pointer: 0xc04200c200
len: 6? cap: 8 pointer: 0xc04200c200
len: 7? cap: 8 pointer: 0xc04200c200
len: 8? cap: 8 pointer: 0xc04200c200
len: 9? cap: 16 pointer: 0xc042074000
len: 10? cap: 16 pointer: 0xc042074000

代碼說明如下:

  • 第 1 行,聲明一個整型切片。
  • 第 4 行,循環向 numbers 切片添加10個數。
  • 第 5 行中,打印輸出切片的長度、容量和指針變化。使用 len() 函數查看切片擁有的元素個數,使用 cap() 函數查看切片的容量情況。


通過查看代碼輸出,有一個有意思的規律:len() 函數并不等于 cap。

往一個切片中不斷添加元素的過程,類似于公司搬家。公司發展初期,資金緊張,人員很少,所以只需要很小的房間即可容納所有的員工。隨著業務的拓展和收入的增加就需要擴充工位,但是辦公地的大小是固定的,無法改變。因此公司選擇搬家,每次搬家就需要將所有的人員轉移到新的辦公點。

  • 員工和工位就是切片中的元素。
  • 辦公地就是分配好的內存。
  • 搬家就是重新分配內存。
  • 無論搬多少次家,公司名稱始終不會變,代表外部使用切片的變量名不會修改。
  • 因為搬家后地址發生變化,因此內存“地址”也會有修改。


append() 函數除了添加一個元素外,也可以一次性添加很多元素

var car []string // 添加1個元素 car = append(car, "OldDriver") // 添加多個元素 car = append(car, "Ice", "Sniper", "Monk") // 添加切片 team := []string{"Pig", "Flyingcake", "Chicken"} car = append(car, team...) fmt.Println(car)

代碼輸出如下:
[OldDriver Ice Sniper Monk Pig Flyingcake Chicken]

代碼說明如下:

    • 第 1 行,聲明一個字符串切片。
    • 第 4 行,往切片中添加一個元素。
    • 第 7 行,使用 append() 函數向切片中添加多個元素。
    • 第 10 行,聲明另外一個字符串切片
    • 第 11 行,在team后面加上了...,表示將 team 整個添加到 car 的后面。

Go語言切片復制

使用 Go 語言內建的 copy() 函數,可以迅速地將一個切片的數據復制到另外一個切片空間中,copy() 函數的使用格式如下:

copy( destSlice, srcSlice []T) int

  • srcSlice 為數據來源切片。
  • destSlice 為復制的目標。目標切片必須分配過空間且足夠承載復制的元素個數。來源和目標的類型一致,copy 的返回值表示實際發生復制的元素個數。


下面的代碼將演示對切片的引用和復制操作后對切片元素的影響

package main import "fmt" 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]) // 復制原始數據從4到6(不包含) copy(copyData, srcData[4:6]) for i := 0; i < 5; i++ { fmt.Printf("%d ", copyData[i]) } }

代碼說明如下:

    • 第 8 行,定義元素總量為 1000。
    • 第 11 行,預分配擁有 1000 個元素的整型切片,這個切片將作為原始數據。
    • 第 14~16 行,將 srcData 填充 0~999 的整型值。
    • 第 19 行,將 refData 引用 srcData,切片不會因為等號操作進行元素的復制。
    • 第 22 行,預分配與 srcData 等大(大小相等)、同類型的切片 copyData。
    • 第 24 行,使用 copy() 函數將原始數據復制到 copyData 切片空間中。
    • 第 27 行,修改原始數據的第一個元素為 999。
    • 第 30 行,引用數據的第一個元素將會發生變化。
    • 第 33 行,打印復制數據的首位數據,由于數據是復制的,因此不會發生變化。
    • 第 36 行,將 srcData 的局部數據復制到 copyData 中。
    • 第 38~40 行,打印復制局部數據后的 copyData 元素。

Go語言從切片中刪除元素

Go 語言并沒有對刪除切片元素提供專用的語法或者接口,需要使用切片本身的特性來刪除元素。示例代碼如下

seq := []string{"a", "b", "c", "d", "e"} // 指定刪除位置 index := 2 // 查看刪除位置之前的元素和之后的元素 fmt.Println(seq[:index], seq[index+1:]) // 將刪除點前后的元素連接起來 seq = append(seq[:index], seq[index+1:]...) fmt.Println(seq)

代碼輸出結果:
[a b] [d e]
[a b d e]

  • 第 1 行,聲明一個整型切片,保存含有從 a 到 e 的字符串。
  • 第 4 行,為了演示和講解方便,使用 index 變量保存需要刪除的元素位置。
  • 第 7 行中:seq[:index] 表示的就是被刪除元素的前半部分,值為:
    [1 2]
    seq[index+1:] 表示的是被刪除元素的后半部分,值為:
    [4 5]
  • 第 10 行使用 append() 函數將兩個切片連接起來。
  • 第 12 行,輸出連接好的新切片。此時,索引為 2 的元素已經被刪除。


代碼的刪除過程可以使用下圖來描述。


圖:切片刪除元素的操作過程


Go 語言中切片刪除元素的本質是:以被刪除元素為分界點,將前后兩個部分的內存重新連接起來。

提示

Go 語言中切片元素的刪除過程并沒有提供任何的語法糖或者方法封裝,無論是初學者學習,還是實際使用都是極為麻煩的。

連續容器的元素刪除無論是在任何語言中,都要將刪除點前后的元素移動到新的位置。隨著元素的增加,這個過程將會變得極為耗時。因此,當業務需要大量、頻繁地從一個切片中刪除元素時,如果對性能要求較高,就需要反思是否需要更換其他的容器(如雙鏈表等能快速從刪除點刪除元素)

Go語言map(映射)

在業務和算法中需要使用任意類型的關聯關系時,就需要使用到映射,如學號和學生的對應、名字與檔案的對應等。

Go 語言提供的映射關系容器為 map,map使用散列表(hash)實現。

提示

大多數語言中映射關系容器使用兩種算法:散列表和平衡樹。

散列表可以簡單描述為一個數組(俗稱“桶”),數組的每個元素是一個列表。根據散列函數獲得每個元素的特征值,將特征值作為映射的鍵。如果特征值重復,表示元素發生碰撞。碰撞的元素將被放在同一個特征值的列表中進行保存。散列表查找復雜度為 O(1),和數組一致。最壞的情況為 O(n),n 為元素總數。散列需要盡量避免元素碰撞以提高查找效率,這樣就需要對“桶”進行擴容,每次擴容,元素需要重新放入桶中,較為耗時。

平衡樹類似于有父子關系的一棵數據樹,每個元素在放入樹時,都要與一些節點進行比較。平衡樹的查找復雜度始終為 O(log n)。

添加關聯到 map 并訪問關聯和數據

Go 語言中 map 的定義是這樣的:

map[KeyType]ValueType

  • KeyType為鍵類型。
  • ValueType是鍵對應的值類型。


一個 map 里,符合 KeyType 和 ValueType 的映射總是成對出現。

下面代碼展示了 map 的基本使用環境

scene := make(map[string]int) scene["route"] = 66 fmt.Println(scene["route"]) v := scene["route2"] fmt.Println(v)

代碼輸出如下:
66
0

代碼說明如下:

  • 第 1 行 map 是一個內部實現的類型,使用時,需要手動使用 make 創建。如果不創建使用 map 類型,會觸發宕機錯誤。
  • 第 3 行向 map 中加入映射關系。寫法與使用數組一樣,key 可以使用除函數以外的任意類型。
  • 第 5 行查找 map 中的值。
  • 第 7 行中,嘗試查找一個不存在的鍵,那么返回的將是 ValueType 的默認值。


某些情況下,需要明確知道查詢中某個鍵是否在 map 中存在,可以使用一種特殊的寫法來實現,看下面的代碼:

v, ok := scene["route"]

在默認獲取鍵值的基礎上,多取了一個變量 ok,可以判斷鍵 route 是否存在于 map 中。

map 還有一種在聲明時填充內容的方式,代碼如下:

m := map[string]string{ "W": "forward", "A": "left", "D": "right", "S": "backward", }

例子中并沒有使用 make,而是使用大括號進行內容定義,就像 JSON 格式一樣,冒號的左邊是 key,右邊是值,鍵值對之間使用逗號分隔。

Go語言遍歷map

map 的遍歷過程使用 for range 循環完成,代碼如下

scene := make(map[string]int) scene["route"] = 66 scene["brazil"] = 4 scene["china"] = 960 for k, v := range scene { fmt.Println(k, v) }

遍歷對于 Go 語言的很多對象來說都是差不多的,直接使用 for range 語法。遍歷時,可以同時獲得鍵和值。如只遍歷值,可以使用下面的形式:

  • for _, v := range scene {
  • 將不需要的鍵改為匿名變量形式。

    只遍歷鍵時,使用下面的形式:

  • for k := range scene {
  • 無須將值改為匿名變量形式,忽略值即可。

    注意:遍歷輸出元素的順序與填充順序無關。不能期望 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)

    代碼輸出如下:
    [brazil china route]

    代碼說明如下:

    • 第 1 行,創建一個 map 實例,鍵為字符串,值為整型。
    • 第 4~6 行,將 3 個鍵值對寫入 map 中。
    • 第 9 行,聲明 sceneList 為字符串切片,以緩沖和排序 map 中的所有元素。
    • 第 12 行,將 map 中元素的鍵遍歷出來,并放入切片中。
    • 第 17 行,對 sceneList 字符串切片進行排序。排序時,sceneList 會被修改。
    • 第 20 行,輸出排好序的 map 的鍵。

    sort.Strings 的作用是對傳入的字符串切片進行字符串字符的升序排列。排序接口的使用將在后面的章節中介紹。

    map元素的刪除和清空

    使用 delete() 函數從 map 中刪除鍵值對

    使用 delete() 內建函數從 map 中刪除一組鍵值對,delete() 函數的格式如下:

    delete(map, 鍵)

    • map 為要刪除的 map 實例。
    • 鍵為要刪除的 map 鍵值對中的鍵。


    從 map 中刪除一組鍵值對可以通過下面的代碼來完成

    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) }

    代碼輸出如下:
    route 66
    china 960

    這個例子中使用 delete() 函數將 brazil 從 scene 這個 map 中刪除了。

    清空 map 中的所有元素

    有意思的是,Go 語言中并沒有為 map 提供任何清空所有元素的函數、方法。清空 map 的唯一辦法就是重新 make 一個新的 map。不用擔心垃圾回收的效率,Go 語言中的并行垃圾回收效率比寫一個清空函數高效多了。

    Go語言sync.Map

    Go 語言中的 map 在并發情況下,只讀是線程安全的,同時讀寫線程不安全。

    下面來看下并發情況下讀寫 map 時會出現的問題,代碼如下

    // 創建一個int到int的映射 m := make(map[int]int) // 開啟一段并發代碼 go func() { // 不停地對map進行寫入 for { m[1] = 1 } }() // 開啟一段并發代碼 go func() { // 不停地對map進行讀取 for { _ = m[1] } }() // 無限循環, 讓并發程序在后臺執行 for { }

    運行代碼會報錯,輸出如下:
    fatal error: concurrent map read and map write

    運行時輸出提示:并發的 map 讀寫。也就是說使用了兩個并發函數不斷地對 map 進行讀和寫而發生了競態問題。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?演示代碼如下

    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 }) }

    代碼輸出如下:
    100 true
    iterate: egypt 200
    iterate: greece 97

    代碼說明如下:

    • 第 10 行,聲明 scene,類型為 sync.Map。注意,sync.Map 不能使用 make 創建。
    • 第 13~15 行,將一系列鍵值對保存到 sync.Map 中,sync.Map 將鍵和值以 interface{} 類型進行保存。
    • 第 18 行,提供一個 sync.Map 的鍵給 scene.Load() 方法后將查詢到鍵對應的值返回。
    • 第 21 行,sync.Map 的 Delete 可以使用指定的鍵將對應的鍵值對刪除。
    • 第 24 行,Range() 方法可以遍歷 sync.Map,遍歷需要提供一個匿名函數,參數為 k、v,類型為 interface{},每次 Range() 在遍歷一個元素時,都會調用這個匿名函數把結果返回。


    sync.Map 沒有提供獲取 map 數量的方法,替代方法是獲取時遍歷自行計算數量。sync.Map 為了保證并發安全有一些性能損失,因此在非并發情況下,使用 map 相比使用 sync.Map 會有更好的性能。

    Go語言list(列表)

    列表是一種非連續存儲的容器,由多個節點組成,節點通過一些變量記錄彼此之間的關系。列表有多種實現方法,如單鏈表、雙鏈表等。

    列表的原理可以這樣理解:假設 A、B、C 三個人都有電話號碼,如果 A 把號碼告訴給 B,B 把號碼告訴給 C,這個過程就建立了一個單鏈表結構,如下圖所示。


    圖:三人單向通知電話號碼形成單鏈表結構


    如果在這個基礎上,再從 C 開始將自己的號碼給自己知道號碼的人,這樣就形成了雙鏈表結構,如下圖所示。


    圖:三人相互通知電話號碼形成雙鏈表結構


    那么如果需要獲得所有人的號碼,只需要從 A 或者 C 開始,要求他們將自己的號碼發出來,然后再通知下一個人如此循環。這個過程就是列表遍歷。

    如果 B 換號碼了,他需要通知 A 和 C,將自己的號碼移除。這個過程就是列表元素的刪除操作,如下圖所示。


    圖:從雙鏈表中刪除一人的電話號碼


    在 Go 語言中,將列表使用 container/list 包來實現,內部的實現原理是雙鏈表。列表能夠高效地進行任意位置的元素插入和刪除操作。

    初始化列表

    list 的初始化有兩種方法:New 和聲明。兩種方法的初始化效果都是一致的。

    1) 通過 container/list 包的 New 方法初始化 list

    變量名 := list.New()


    2) 通過聲明初始化list

    var 變量名 list.List


    列表與切片和 map 不同的是,列表并沒有具體元素類型的限制。因此,列表的元素可以是任意類型。這既帶來遍歷,也會引來一些問題。給一個列表放入了非期望類型的值,在取出值后,將 interface{} 轉換為期望類型時將會發生宕機。

    在列表中插入元素

    雙鏈表支持從隊列前方或后方插入元素,分別對應的方法是 PushFront 和 PushBack。

    提示

    這兩個方法都會返回一個 *list.Element 結構。如果在以后的使用中需要刪除插入的元素,則只能通過 *list.Element 配合 Remove() 方法進行刪除,這種方法可以讓刪除更加效率化,也是雙鏈表特性之一。

    下面代碼展示如何給list添加元素

    l := list.New() l.PushBack("fist") l.PushFront(67)

    代碼說明如下:

    • 第 1 行,創建一個列表實例。
    • 第 3 行,將 fist 字符串插入到列表的尾部,此時列表是空的,插入后只有一個元素。
    • 第 4 行,將數值 67 放入列表。此時,列表中已經存在 fist 元素,67 這個元素將被放在 fist 的前面。


    列表插入元素的方法如下表所示。

    方 ?法 功 ?能
    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) }

    代碼說明如下:
    第 6 行,創建列表實例。
    第 9 行,將 canon 字符串插入到列表的尾部。
    第 12 行,將 67 數值添加到列表的頭部。
    第 15 行,將 fist 字符串插入到列表的尾部,并將這個元素的內部結構保存到 element 變量中。
    第 18 行,使用 element 變量,在 element 的位置后面插入 high 字符串。
    第 21 行,使用 element 變量,在 element 的位置前面插入 noon 字符串。
    第 24 行,移除 element 變量對應的元素。

    下表中展示了每次操作后列表的實際元素情況。

    列表元素操作的過程 操作內容 列表元素
    l.PushBack("canon") canon
    l.PushFront(67) 67,?canon
    element := l.PushBack("fist") 67, canon, fist
    l.InsertAfter("high", element) 67, canon, fist, high
    l.InsertBefore("noon", element) 67, canon, noon, fist, high
    l.Remove(element) 67, canon, noon, high

    遍歷列表——訪問列表的每一個元素

    遍歷雙鏈表需要配合 Front() 函數獲取頭元素,遍歷時只要元素不為空就可以繼續進行。每一次遍歷調用元素的 Next,如代碼中第 9 行所示

    l := list.New() // 尾部添加 l.PushBack("canon") // 頭部添加 l.PushFront(67) for i := l.Front(); i != nil; i = i.Next() { fmt.Println(i.Value) }

    代碼輸出如下:
    67
    canon

    代碼說明如下:

      • 第 1 行,創建一個列表實例。
      • 第 4 行,將 canon 放入列表尾部。
      • 第 7 行,在隊列頭部放入 67。
      • 第 9 行,使用 for 語句進行遍歷,其中 i:=l.Front() 表示初始賦值,只會在一開始執行一次;每次循環會進行一次 i!=nil 語句判斷,如果返回 false,表示退出循環,反之則會執行 i=i.Next()。
      • 第 10 行,使用遍歷返回的 *list.Element 的 Value 成員取得放入列表時的原值。

    轉載于:https://www.cnblogs.com/596014054-yangdongsheng/p/10231412.html

    總結

    以上是生活随笔為你收集整理的Go第三篇之大话容器的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。