最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

詳解Golang中使用map時(shí)的注意問(wèn)題

 更新時(shí)間:2024年06月27日 09:32:16   作者:劉鑄緯  
Golang中的map是一種數(shù)據(jù)結(jié)構(gòu),它允許你使用鍵值對(duì)的形式存儲(chǔ)和訪問(wèn)數(shù)據(jù),map在Go中是非排序的,提供了高效查找、插入和刪除元素的能力,特別是當(dāng)鍵是不可變類型,本文給大家詳細(xì)介紹了Golang中使用map時(shí)的注意問(wèn)題,需要的朋友可以參考下

1. 將value定義為struct節(jié)省內(nèi)存

1. 消除指針引用

當(dāng) map 的 value 是 struct 類型時(shí),數(shù)據(jù)會(huì)直接存儲(chǔ)在 map 中,而不是通過(guò)指針引用。這可以減少內(nèi)存分配的開(kāi)銷和 GC(垃圾回收)的負(fù)擔(dān)。

type User struct {
    ID   int
    Name string
}

m := make(map[string]User)
m["user1"] = User{ID: 1, Name: "John"}

// Example with pointer to struct
m2 := make(map[string]*User)
m2["user1"] = &User{ID: 1, Name: "John"}

在第二個(gè)示例中,map 中存儲(chǔ)的是指向 User 結(jié)構(gòu)體的指針,這意味著除了存儲(chǔ)指針本身外,還需要額外的內(nèi)存來(lái)存儲(chǔ) User 結(jié)構(gòu)體,并且會(huì)增加 GC 的負(fù)擔(dān)。

2. 避免內(nèi)存碎片化

存儲(chǔ)指針時(shí),由于指針可能指向堆中的不同位置,這會(huì)導(dǎo)致內(nèi)存碎片化,增加了內(nèi)存使用的不確定性。而存儲(chǔ) struct 使得數(shù)據(jù)更緊湊,減少了碎片化。

3. 更高的緩存命中率

由于 struct 的數(shù)據(jù)是緊湊存儲(chǔ)的,相對(duì)于存儲(chǔ)指針,struct 的數(shù)據(jù)更可能在相鄰的內(nèi)存位置。這增加了 CPU 緩存的命中率,從而提高了性能。

示例:節(jié)約內(nèi)存

下面是一個(gè)示例,展示了如何通過(guò)定義 struct 類型來(lái)節(jié)約內(nèi)存:

package main

import (
	"fmt"
	"runtime"
)

type User struct {
	ID   int
	Name string
}

func main() {
	// 使用 struct 作為 value
	users := make(map[string]User)
	for i := 0; i < 1000000; i++ {
		users[fmt.Sprintf("user%d", i)] = User{ID: i, Name: fmt.Sprintf("Name%d", i)}
	}

	printMemUsage("With struct values")

	// 使用指針作為 value
	userPtrs := make(map[string]*User)
	for i := 0; i < 1000000; i++ {
		userPtrs[fmt.Sprintf("user%d", i)] = &User{ID: i, Name: fmt.Sprintf("Name%d", i)}
	}

	printMemUsage("With pointer values")
}

func printMemUsage(label string) {
	var m runtime.MemStats
	runtime.ReadMemStats(&m)
	fmt.Printf("%s: Alloc = %v MiB\n", label, bToMb(m.Alloc))
}

func bToMb(b uint64) uint64 {
	return b / 1024 / 1024
}

4. set實(shí)現(xiàn)對(duì)比

map[int]bool{}

在這種情況下,map 的 value 類型是 bool。每個(gè)鍵會(huì)占用一個(gè) bool 類型的空間(通常是一個(gè)字節(jié))。

set := make(map[int]bool)
set[1] = true
set[2] = true

map[int]struct{}{}

在這種情況下,map 的 value 類型是空的 struct。空的 struct 不占用任何內(nèi)存,因此每個(gè)鍵只占用鍵本身的內(nèi)存。

set := make(map[int]struct{})
set[1] = struct{}{}
set[2] = struct{}{}

內(nèi)存使用對(duì)比

map[int]bool{} 會(huì)比 map[int]struct{}{} 使用更多的內(nèi)存,因?yàn)?bool 類型需要存儲(chǔ)一個(gè)字節(jié)(在實(shí)際應(yīng)用中可能會(huì)有額外的內(nèi)存對(duì)齊和管理開(kāi)銷),而 struct{} 是空的,不會(huì)增加任何內(nèi)存開(kāi)銷。

示例代碼對(duì)比內(nèi)存使用

以下是一個(gè)示例代碼,比較這兩種 map 類型的內(nèi)存使用情況:

package main

import (
	"fmt"
	"runtime"
)

func main() {
	// 使用 bool 作為 value
	boolMap := make(map[int]bool)
	for i := 0; i < 1000000; i++ {
		boolMap[i] = true
	}

	printMemUsage("With bool values")

	// 使用 struct 作為 value
	structMap := make(map[int]struct{})
	for i := 0; i < 1000000; i++ {
		structMap[i] = struct{}{}
	}

	printMemUsage("With struct values")
}

func printMemUsage(label string) {
	var m runtime.MemStats
	runtime.ReadMemStats(&m)
	fmt.Printf("%s: Alloc = %v MiB\n", label, bToMb(m.Alloc))
}

func bToMb(b uint64) uint64 {
	return b / 1024 / 1024
}

結(jié)果

運(yùn)行上述代碼,你會(huì)發(fā)現(xiàn)使用 struct 作為 value 的內(nèi)存使用量明顯小于使用指針作為 value 的內(nèi)存使用量。這是因?yàn)椋?/p>

  • 減少了指針的存儲(chǔ)開(kāi)銷。
  • 減少了額外的堆內(nèi)存分配
  • 降低了 GC 的負(fù)擔(dān),因?yàn)?struct 的內(nèi)存管理更簡(jiǎn)單,不涉及指針的追蹤和回收。

2. 哈希分桶的結(jié)構(gòu)

1. 哈希計(jì)算

當(dāng)我們向map中插入一個(gè)鍵值對(duì),首先對(duì)鍵進(jìn)行哈希計(jì)算。Go內(nèi)置了哈希函數(shù)來(lái)計(jì)算鍵的哈希值。哈希值是一個(gè)64位的整數(shù)。

2. 分桶依據(jù)

Go 中的 map 是分成多個(gè)桶 (bucket) 來(lái)存儲(chǔ)的。桶的數(shù)量通常是 2 的冪次,這樣可以方便地通過(guò)位運(yùn)算來(lái)定位到具體的桶。哈希值的高八位和低八位分別用于分桶和桶內(nèi)定位:

  • 高八位 (top 8 bits):用于決定哈希表中的桶位置。
  • 低八位 (low 8 bits):用于桶內(nèi)查找。

3. 桶 (Bucket) 結(jié)構(gòu)

每個(gè)桶中可以存儲(chǔ) 8 個(gè)鍵值對(duì)。當(dāng)某個(gè)桶中的元素超過(guò) 8 個(gè)時(shí),Go 會(huì)使用溢出桶來(lái)存儲(chǔ)額外的鍵值對(duì)。桶的結(jié)構(gòu)如下:

type bmap struct {
    tophash [bucketCnt]uint8
    keys    [bucketCnt]keyType
    values  [bucketCnt]valueType
    overflow *bmap
}

tophash:存儲(chǔ)鍵的哈希值的高八位。

keys:存儲(chǔ)鍵。

values:存儲(chǔ)對(duì)應(yīng)的值。

overflow:指向溢出桶的指針。

4. 插入過(guò)程

當(dāng)插入一個(gè)鍵值對(duì)時(shí),過(guò)程如下:

  1. 計(jì)算哈希值:對(duì)鍵進(jìn)行哈希計(jì)算得到哈希值 hash
  2. 定位桶:通過(guò) hash >> (64 - B)B 是桶的數(shù)量的對(duì)數(shù))得到桶的索引 index。
  3. 桶內(nèi)查找:通過(guò) hash & (bucketCnt - 1) 得到桶內(nèi)索引。然后通過(guò)對(duì)比 tophash 數(shù)組中的值來(lái)定位到具體的鍵值對(duì)存儲(chǔ)位置。
  4. 存儲(chǔ)鍵值對(duì):將鍵值對(duì)存儲(chǔ)到相應(yīng)的位置,如果當(dāng)前桶已滿,則分配新的溢出桶來(lái)存儲(chǔ)額外的鍵值對(duì)。

5. 查找過(guò)程

查找的過(guò)程與插入類似:

查找的過(guò)程與插入類似:

  1. 計(jì)算哈希值:對(duì)鍵進(jìn)行哈希計(jì)算得到哈希值 hash
  2. 定位桶:通過(guò) hash >> (64 - B) 得到桶的索引 index。
  3. 桶內(nèi)查找:通過(guò) hash & (bucketCnt - 1) 得到桶內(nèi)索引,然后在相應(yīng)的 bmap 中查找 tophash 和 keys 數(shù)組中匹配的鍵。如果在當(dāng)前桶中沒(méi)有找到,則繼續(xù)查找溢出桶。

3. map擴(kuò)容過(guò)程

1. 擴(kuò)容觸發(fā)條件

擴(kuò)容通常在以下兩種情況下觸發(fā):

擴(kuò)容通常在以下兩種情況下觸發(fā):

  1. 裝載因子過(guò)高:裝載因子(load factor)是 map 中元素?cái)?shù)量與桶數(shù)量的比值。Go 語(yǔ)言中的裝載因子閾值通常為 6.5,當(dāng)裝載因子超過(guò)這個(gè)值時(shí)會(huì)觸發(fā)擴(kuò)容。
  2. 溢出桶過(guò)多:當(dāng)溢出桶的數(shù)量過(guò)多時(shí),也會(huì)觸發(fā)擴(kuò)容。

2. 擴(kuò)容過(guò)程的具體步驟

  1. 初始化新的桶數(shù)組: 在需要擴(kuò)容時(shí),Go 會(huì)分配一個(gè)新的桶數(shù)組,其大小通常是舊桶數(shù)組的兩倍,并設(shè)置相關(guān)的元數(shù)據(jù)以指示 map 正在進(jìn)行擴(kuò)容。
  2. 標(biāo)記遷移狀態(tài): 在 map 的內(nèi)部結(jié)構(gòu)中,會(huì)有一個(gè)標(biāo)志位(rehash index)指示當(dāng)前已經(jīng)遷移的桶位置。初始值為 0。
  3. 遷移部分?jǐn)?shù)據(jù): 在每次對(duì) map 進(jìn)行插入或查找操作時(shí),會(huì)順便遷移一部分舊桶中的數(shù)據(jù)到新桶中。每次遷移一個(gè)或多個(gè)桶,具體數(shù)量取決于操作的復(fù)雜度。
  4. 更新 rehash index: 遷移完成后,更新 rehash index,以便下次操作繼續(xù)遷移下一個(gè)桶中的數(shù)據(jù)。
  5. 完成擴(kuò)容: 當(dāng)所有舊桶的數(shù)據(jù)都遷移到新桶后,更新 map 的元數(shù)據(jù),指向新的桶數(shù)組,并將擴(kuò)容狀態(tài)標(biāo)志位清除。

4. recover map的panic

panic 和 recover 的工作機(jī)制

  1. panic
    • panic 用于引發(fā)一個(gè)恐慌,通常在遇到無(wú)法恢復(fù)的嚴(yán)重錯(cuò)誤時(shí)使用。
    • 當(dāng) panic 被調(diào)用時(shí),程序的正常執(zhí)行流程會(huì)被中斷,并開(kāi)始沿著調(diào)用棧向上展開(kāi),逐層調(diào)用函數(shù)的 defer 語(yǔ)句,直到遇到 recover 或者程序崩潰。
  2. recover
    • recover 用于恢復(fù)程序的正常執(zhí)行,通常在 defer 函數(shù)中調(diào)用。
    • 如果在 defer 語(yǔ)句中調(diào)用了 recover,并且當(dāng)前棧幀處于恐慌狀態(tài),那么 recover 會(huì)捕獲這個(gè)恐慌,停止棧的展開(kāi),并返回傳給 panic 的值。
    • 如果不在恐慌狀態(tài)下調(diào)用 recover,它會(huì)返回 nil,不做任何處理。

在 Go 語(yǔ)言中,panic 和 recover 是用來(lái)處理異常情況和錯(cuò)誤恢復(fù)的兩種機(jī)制。理解它們的工作原理對(duì)于編寫健壯的 Go 代碼非常重要。以下是對(duì) panic 和 recover 機(jī)制的詳細(xì)解釋以及它們?cè)?nbsp;map 中的應(yīng)用。

panic 和 recover 的工作機(jī)制

  1. panic
    • panic 用于引發(fā)一個(gè)恐慌,通常在遇到無(wú)法恢復(fù)的嚴(yán)重錯(cuò)誤時(shí)使用。
    • 當(dāng) panic 被調(diào)用時(shí),程序的正常執(zhí)行流程會(huì)被中斷,并開(kāi)始沿著調(diào)用棧向上展開(kāi),逐層調(diào)用函數(shù)的 defer 語(yǔ)句,直到遇到 recover 或者程序崩潰。
  2. recover
    • recover 用于恢復(fù)程序的正常執(zhí)行,通常在 defer 函數(shù)中調(diào)用。
    • 如果在 defer 語(yǔ)句中調(diào)用了 recover,并且當(dāng)前棧幀處于恐慌狀態(tài),那么 recover 會(huì)捕獲這個(gè)恐慌,停止棧的展開(kāi),并返回傳給 panic 的值。
    • 如果不在恐慌狀態(tài)下調(diào)用 recover,它會(huì)返回 nil,不做任何處理。

在 map 中使用 panic 和 recover

在 Go 的 map 中,某些操作(如并發(fā)讀寫未加鎖的 map)會(huì)引發(fā) panic。這些 panic 可以被 recover 捕獲和處理,以防止程序崩潰。

package main

import (
    "fmt"
)

func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered from panic:", r)
        }
    }()

    // 創(chuàng)建一個(gè) map
    m := make(map[string]string)

    // 引發(fā) panic 的操作
    causePanic(m)

    fmt.Println("This line will be executed because panic was recovered.")
}

func causePanic(m map[string]string) {
    // 這里嘗試并發(fā)訪問(wèn) map,可能會(huì)引發(fā) panic
    // 模擬并發(fā)問(wèn)題,直接引發(fā) panic
    panic("simulated map access panic")
}

5. map是如何檢測(cè)到自己處于競(jìng)爭(zhēng)狀態(tài)

在 Go 語(yǔ)言中,map 的競(jìng)爭(zhēng)狀態(tài)(concurrent access)指的是多個(gè) goroutine 同時(shí)讀寫同一個(gè) map 而沒(méi)有適當(dāng)?shù)耐奖Wo(hù)。Go 內(nèi)置的 map 類型在并發(fā)讀寫時(shí)會(huì)引發(fā) panic,以防止數(shù)據(jù)競(jìng)爭(zhēng)和未定義行為。這種檢測(cè)主要是通過(guò) Go 編譯器和運(yùn)行時(shí)的實(shí)現(xiàn)來(lái)完成的,而不是底層硬件直接支持的功能。

競(jìng)爭(zhēng)檢測(cè)機(jī)制

  1. 編譯器插樁
    • 在編譯時(shí),Go 編譯器會(huì)在對(duì) map 進(jìn)行讀寫操作的代碼位置插入特定的檢測(cè)代碼。這些檢測(cè)代碼在運(yùn)行時(shí)檢查 map 是否處于并發(fā)訪問(wèn)狀態(tài)。
  2. 運(yùn)行時(shí)檢查
    • 運(yùn)行時(shí)的檢測(cè)代碼會(huì)追蹤 map 的訪問(wèn)。當(dāng)檢測(cè)到多個(gè) goroutine 同時(shí)對(duì) map 進(jìn)行讀寫操作時(shí),會(huì)引發(fā) panic。具體來(lái)說(shuō),Go 運(yùn)行時(shí)會(huì)記錄每個(gè) map 的訪問(wèn)情況,如果檢測(cè)到并發(fā)訪問(wèn)沒(méi)有通過(guò)同步機(jī)制(如 sync.Mutex),就會(huì)引發(fā) panic。
package main

import (
    "fmt"
    "sync"
)

func main() {
    m := make(map[int]int)
    var wg sync.WaitGroup
    var mu sync.Mutex

    // 啟動(dòng)多個(gè) goroutine 并發(fā)寫 map,未加鎖保護(hù)會(huì)引發(fā) panic
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            // 取消注釋以下行,查看未加鎖保護(hù)的并發(fā)寫操作
            // m[i] = i

            // 使用互斥鎖保護(hù)并發(fā)寫操作
            mu.Lock()
            m[i] = i
            mu.Unlock()
        }(i)
    }

    wg.Wait()

    // 打印 map 內(nèi)容
    mu.Lock()
    for k, v := range m {
        fmt.Printf("key: %d, value: %d\n", k, v)
    }
    mu.Unlock()
}

6. sync.Map和map加鎖的區(qū)別

    • 使用場(chǎng)景
      • sync.Map 適用于讀多寫少的并發(fā)場(chǎng)景,簡(jiǎn)單且高效。
      • 使用 sync.Mutex 或 sync.RWMutex 保護(hù)普通 map 適用于需要復(fù)雜并發(fā)控制或?qū)懖僮鬏^多的場(chǎng)景。
    • 性能
      • sync.Map 在讀多寫少的情況下性能優(yōu)越,但在寫操作頻繁時(shí)性能可能不如使用互斥鎖保護(hù)的普通 map。
      • 使用 sync.Mutex 或 sync.RWMutex 可以在讀寫操作間提供更好的性能平衡,尤其是在寫操作較多時(shí)。
    • 復(fù)雜性
      • sync.Map 封裝了并發(fā)控制,使用簡(jiǎn)單,不需要手動(dòng)加鎖。
      • 使用 sync.Mutex 或 sync.RWMutex 需要手動(dòng)加鎖解鎖,代碼相對(duì)復(fù)雜,但更靈活。
    • 方法支持
      • sync.Map 提供了一些特殊的方法(如 LoadOrStoreRange),方便特定場(chǎng)景下的使用。
      • 使用 sync.Mutex 或 sync.RWMutex 保護(hù)的普通 map 可以自由定義自己的方法,更靈活,但需要更多的代碼。

以上就是詳解Golang中使用map時(shí)的注意問(wèn)題的詳細(xì)內(nèi)容,更多關(guān)于Golang使用map的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 如何使用騰訊云go sdk 查詢對(duì)象存儲(chǔ)中最新文件

    如何使用騰訊云go sdk 查詢對(duì)象存儲(chǔ)中最新文件

    這篇文章主要介紹了使用騰訊云go sdk 查詢對(duì)象存儲(chǔ)中最新文件,這包括如何創(chuàng)建COS客戶端,如何逐頁(yè)檢索對(duì)象列表,并如何對(duì)結(jié)果排序以找到最后更新的對(duì)象,我們還展示了如何優(yōu)化用戶體驗(yàn),通過(guò)實(shí)時(shí)進(jìn)度更新和檢索多個(gè)文件來(lái)改進(jìn)程序,需要的朋友可以參考下
    2024-03-03
  • golang 實(shí)現(xiàn)Location跳轉(zhuǎn)方式

    golang 實(shí)現(xiàn)Location跳轉(zhuǎn)方式

    這篇文章主要介紹了golang 實(shí)現(xiàn)Location跳轉(zhuǎn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-05-05
  • Golang自動(dòng)追蹤GitHub上熱門AI項(xiàng)目

    Golang自動(dòng)追蹤GitHub上熱門AI項(xiàng)目

    這篇文章主要為大家介紹了Golang自動(dòng)追蹤GitHub上熱門AI項(xiàng)目,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • PHP與Go語(yǔ)言之間的通信詳解

    PHP與Go語(yǔ)言之間的通信詳解

    相信大家都知道不同語(yǔ)言之間的通信方式有很多種,這篇文章詳細(xì)的介紹了PHP與Go語(yǔ)言之間如何通信,有需要的朋友們可以參考借鑒,下面來(lái)一起看看吧。
    2016-10-10
  • Golang實(shí)現(xiàn)按行讀取文件的方法小結(jié)

    Golang實(shí)現(xiàn)按行讀取文件的方法小結(jié)

    按行讀取文件相較于一次性載入,有著很多優(yōu)勢(shì),如內(nèi)存效率高、處理速度快、實(shí)時(shí)性高等,本文主要介紹了Golang按行讀取文件的相關(guān)方法,希望對(duì)大家有所幫助
    2024-02-02
  • Golang分布式應(yīng)用定時(shí)任務(wù)示例詳解

    Golang分布式應(yīng)用定時(shí)任務(wù)示例詳解

    這篇文章主要為大家介紹了Golang分布式應(yīng)用定時(shí)任務(wù)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-07-07
  • Go錯(cuò)誤處理之errors包全面解析與最佳實(shí)踐

    Go錯(cuò)誤處理之errors包全面解析與最佳實(shí)踐

    標(biāo)準(zhǔn)庫(kù)errors包是Go提供的基礎(chǔ)錯(cuò)誤處理工具,它不僅允許我們創(chuàng)建和包裝錯(cuò)誤,還支持豐富的功能,下面小編將從基礎(chǔ)到高級(jí),帶你深入掌握?errors?包的使用,快跟隨小編一起學(xué)習(xí)起來(lái)吧
    2026-03-03
  • go?logger不侵入業(yè)務(wù)代碼使用slog替換zap并實(shí)現(xiàn)callerSkip詳解

    go?logger不侵入業(yè)務(wù)代碼使用slog替換zap并實(shí)現(xiàn)callerSkip詳解

    這篇文章主要為大家介紹了go?logger不侵入業(yè)務(wù)代碼使用slog替換zap并實(shí)現(xiàn)callerSkip詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • golang編程入門之http請(qǐng)求天氣實(shí)例

    golang編程入門之http請(qǐng)求天氣實(shí)例

    這篇文章主要介紹了golang編程入門之http請(qǐng)求天氣實(shí)例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-08-08
  • go中普通map和sync.map的區(qū)別小結(jié)

    go中普通map和sync.map的區(qū)別小結(jié)

    本文主要介紹了go中普通map和sync.map的區(qū)別小結(jié),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2025-09-09

最新評(píng)論

阿荣旗| 巴中市| 蒙自县| 丰台区| 聂荣县| 德钦县| 屏东市| 鹤壁市| 万宁市| 聊城市| 宝坻区| 新乡市| 永安市| 佳木斯市| 贵定县| 随州市| 宣城市| 珲春市| 横山县| 平原县| 民乐县| 中山市| 分宜县| 西昌市| 客服| 徐闻县| 阜城县| 和平县| 金堂县| 唐山市| 泰来县| 札达县| 报价| 伊通| 马边| 任丘市| 河北省| 隆安县| 蓬溪县| 旬邑县| 江永县|