一文讀透GO語言的雜湊表

2021-10-03 16:36:33 字數 3723 閱讀 4080

最近一直遠端辦公,所以經常會從各種群郵件,釘釘等軟體上收到一些重複的檔案,突然發現原本不大的筆記本硬碟也會經常被這些檔案所佔據,而針對windows的重複檔案清理軟體如duplicate cleaner等筆者親測也會帶有一些**式的安裝,而其實這個需求沒有那麼難以解決,簡單來說只需要建立 乙個以檔名為索引,檔案大小為鍵值的雜湊表,然後遍歷所有檔案就能完成,在看過了所有典型的雜湊表之後筆者發現go語言的雜湊表非常優秀,接下來就帶大家一起來認識一下其中的奧秘,接下來筆者還會帶大家一起來用go語言實現乙個檔案去重的小工具。

在現代的系統研發尤其是

web類系統中一般都使用負載均衡裝置加分布式應用伺服器的方式來完成,而同一客戶的

session

一般都要保證始終發給一台應用伺服器處理,負載裝置每次需要通過

session id

迅速定位到伺服器,那麼

session id

和應用伺服器的

ip位址就是

map的典型應用。 go

語言中的

map原始碼非常值得一讀下面給大家做一下簡要介紹

1.map

的資料結構

可以看到

hmap

是map

的基礎結構其中,其中記錄了

map的元素的數量,和

bucket

的數量,以及

map現在是否正在遷移等狀態資訊詳見以下注釋說明。

type hmap struct 

// 在桶溢位的時候會用到extra

map建立時會初始化乙個

hmap

結構體,在訪問

map中的

pair

時,先計算

key的雜湊值,其中雜湊的值的低

8位定位到具體的桶(

bucket

),通過高

8位在桶內定位到具體的位置。

2.map

的建立

我們再來看

makemap

函式,如果首先看元素的個數,計算乙個

b值,如果

map只需要乙個桶(

bucket

)就可以直接建立在棧上,而不是在堆上如果

h.buckets

其指向的桶(

bucket

)可以作為第乙個桶(

bucket

)來使用。詳見以下**注釋

func makemap(t *maptype, hint int, h *hmap) *hmap ); sz != 8+5*sys.ptrsize 

if hint < 0 || hint > int(maxslicecap(t.bucket.size))

// 初始化 hmap

if h == nil

h.hash0 = fastrand()

// 按照提供的元素個數,找乙個可以放得下這麼多元素的 b 值

b := uint8(0)

for overloadfactor(hint, b)

h.b = b

// 分配初始的 hash table

// 如果 b == 0,buckets 欄位會由 mapassign 來 lazily 分配

// 因為如果 hint 很大的話,對這部分記憶體歸零會花比較長時間

if h.b != 0

}return h

}

3.map

中元素的訪問

再來看訪問元素的函式

mapaccess

,可以看到該函式會根據不同的

key型別來選擇不同的雜湊演算法,而且從這個函式也可以看到,

map並不能保證併發安全的,因為在對外訪問的時候

map還可能正在擴容,一旦在擴容時發生併發訪問可能會有潛在的問題,不過在go的

sync

包下也有乙個

map的實現,這個

map是協程安全的。

mapaccess

**及注釋如下

func mapaccess(t *maptype, h *hmap, key unsafe.pointer) (unsafe.pointer, bool) 

if h.flags&hashwriting != 0

alg := t.key.alg

// 不同型別的 key,所用的 hash 演算法是不一樣的

// 具體可以參考 algarray

hash := alg.hash(key, uintptr(h.hash0))

// 如果 b = 3,那麼結果用二進位制表示就是 111

// 如果 b = 4,那麼結果用二進位制表示就是 1111

m := bucketmask(h.b)

// 按位 &,可以 select 出對應的 bucket

b := (*bmap)(unsafe.pointer(uintptr(h.buckets) + (hash&m)*uintptr(t.bucketsize)))

// 會用到 h.oldbuckets 時,說明 map 發生了擴容

// 這時候,新的 buckets 裡可能還沒有老的內容

// 所以一定要在老的裡面找,否則有可能發生「消失」的詭異現象

if c := h.oldbuckets; c != nil

oldb := (*bmap)(unsafe.pointer(uintptr(c) + (hash&m)*uintptr(t.bucketsize)))

if !evacuated(oldb)

}// tophash 取其高 8bit 的值

top := tophash(hash)

for ; b != nil; b = b.overflow(t)

k := add(unsafe.pointer(b), dataoffset+i*uintptr(t.keysize))

if t.indirectkey

if alg.equal(key, k)

return v, true}}

}// 所有 bucket 都沒有找到,返回零值和 false

return unsafe.pointer(&zeroval[0]), false

}

3.通過分析源**筆者有以下結論 首先

map底層是

hash

實現,資料結構為

hash

陣列+

桶每個桶儲存最多8個

key-value

對,如果遇溢位,暨乙個桶被分配到的元素不止

8個,則加入溢位桶的操作。其次

map查詢是通過

key的

hash

值的低8

位定位到桶,再從桶中定位到得到具體的

key。而且

go map

不支援在併發。插入、刪除、搬遷等操作可能會使

map進行遷移,這時併發訪問會產生

panic。

從時間複雜度上分析,

map正常的時間複雜度是

o(1)

,如果所有資料都被集中到乙個桶及溢位桶內,那麼時間複雜度退化為

o(n)

一文說透https中的s是什麼?

一 http 與 https 有哪些區別?1.http 是超文字傳輸協議,資訊是明文傳輸,存在安全風險。https 是在 tcp 和網路層之間加入了 ssl tls 安全協議,也就是安全套接字層,使得報文能夠加密傳輸。2.http 連線建立相對簡單,tcp 三次握手建立之後便可進行 http 的報文...

一文說透WordPress的自定義文章型別

丘壑部落格 從2004年的1.0版本算起,wordpress在14年間已經迭代開發到了5.x版。如果說這中間哪個版本是乙個質的提公升的話,那應該算是2010年發布的代號為thelonious 的3.0版。這個版本發布了很多重要的功能,比如多站點 主題api等等,其中乙個就是 custom post ...

一文說透WordPress的自定義文章型別

丘壑部落格 從2004年的1.0版本算起,wordpress在14年間已經迭代開發到了5.x版。如果說這中間哪個版本是乙個質的提公升的話,那應該算是2010年發布的代號為thelonious 的3.0版。這個版本發布了很多重要的功能,比如多站點 主題api等等,其中乙個就是 custom post ...