mysql存儲map數據結構_map數據結構

Go map實現原理 - 戀戀美食的個人空間 - OSCHINA - 中文開源技術交流社區 https://my.oschina.net/renhc/blog/2208417

// A header for a Go map.

type hmap struct {

// Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go.

// Make sure this stays in sync with the compiler's definition.

count int // # live cells == size of map. Must be first (used by len() builtin)

flags uint8

B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items)

noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details

hash0 uint32 // hash seed

buckets unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.

oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing

nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacuated)

extra *mapextra // optional fields

}

Golang的map使用哈希表作為底層實現,一個哈希表里可以有多個哈希表節點,也即bucket,而每個bucket就保存了map中的一個或一組鍵值對。

map數據結構由runtime/map.go:hmap定義:

type hmapstruct{

countint//當前保存的元素個數

...

B???????? uint8

...

bucketsunsafe.Pointer// bucket數組指針,數組的大小為2^B

...

}

下圖展示一個擁有4個bucket的map:

551baa31e47f0ff66d4c45d9e5c2452b.png

本例中,?hmap.B=2, 而hmap.buckets長度是2^B為4. 元素經過哈希運算后會落到某個bucket中進行存儲。查找過程類似。

bucket很多時候被翻譯為桶,所謂的哈希桶實際上就是bucket。

2. bucket數據結構

bucket數據結構由runtime/map.go:bmap定義:

type bmapstruct{

tophash [8]uint8//存儲哈希值的高8位

databyte[1]//key value數據:key/key/key/.../value/value/value...

overflow *bmap//溢出bucket的地址

}

每個bucket可以存儲8個鍵值對。

tophash是個長度為8的數組,哈希值相同的鍵(準確的說是哈希值低位相同的鍵)存入當前bucket時會將哈希值的高位存儲在該數組中,以方便后續匹配。

data區存放的是key-value數據,存放順序是key/key/key/…value/value/value,如此存放是為了節省字節對齊帶來的空間浪費。

overflow 指針指向的是下一個bucket,據此將所有沖突的鍵連接起來。

注意:上述中data和overflow并不是在結構體中顯示定義的,而是直接通過指針運算進行訪問的。

下圖展示bucket存放8個key-value對:

c48fd32ff6a7a09b8855914dd38d2338.png

package runtime

// This file contains the implementation of Go's map type.

//

// A map is just a hash table. The data is arranged

// into an array of buckets. Each bucket contains up to

// 8 key/elem pairs. The low-order bits of the hash are

// used to select a bucket. Each bucket contains a few

// high-order bits of each hash to distinguish the entries

// within a single bucket.

//

// If more than 8 keys hash to a bucket, we chain on

// extra buckets.

//

// When the hashtable grows, we allocate a new array

// of buckets twice as big. Buckets are incrementally

// copied from the old bucket array to the new bucket array.

//

// Map iterators walk through the array of buckets and

// return the keys in walk order (bucket #, then overflow

// chain order, then bucket index). To maintain iteration

// semantics, we never move keys within their bucket (if

// we did, keys might be returned 0 or 2 times). When

// growing the table, iterators remain iterating through the

// old table and must check the new table if the bucket

// they are iterating through has been moved ("evacuated")

// to the new table.

// Picking loadFactor: too large and we have lots of overflow

// buckets, too small and we waste a lot of space. I wrote

// a simple program to check some stats for different loads:

// (64-bit, 8 byte keys and elems)

// loadFactor %overflow bytes/entry hitprobe missprobe

// 4.00 2.13 20.77 3.00 4.00

// 4.50 4.05 17.30 3.25 4.50

// 5.00 6.85 14.77 3.50 5.00

// 5.50 10.55 12.94 3.75 5.50

// 6.00 15.27 11.67 4.00 6.00

// 6.50 20.90 10.79 4.25 6.50

// 7.00 27.14 10.15 4.50 7.00

// 7.50 34.03 9.73 4.75 7.50

// 8.00 41.10 9.40 5.00 8.00

//

// %overflow = percentage of buckets which have an overflow bucket

// bytes/entry = overhead bytes used per key/elem pair

// hitprobe = # of entries to check when looking up a present key

// missprobe = # of entries to check when looking up an absent key

//

// Keep in mind this data is for maximally loaded tables, i.e. just

// before the table grows. Typical tables will be somewhat less loaded.

import (

"runtime/internal/atomic"

"runtime/internal/math"

"runtime/internal/sys"

"unsafe"

)

const (

// Maximum number of key/elem pairs a bucket can hold.

bucketCntBits = 3

bucketCnt = 1 << bucketCntBits

// Maximum average load of a bucket that triggers growth is 6.5.

// Represent as loadFactorNum/loadFactorDen, to allow integer math.

loadFactorNum = 13

loadFactorDen = 2

// Maximum key or elem size to keep inline (instead of mallocing per element).

// Must fit in a uint8.

// Fast versions cannot handle big elems - the cutoff size for

// fast versions in cmd/compile/internal/gc/walk.go must be at most this elem.

maxKeySize = 128

maxElemSize = 128

// data offset should be the size of the bmap struct, but needs to be

// aligned correctly. For amd64p32 this means 64-bit alignment

// even though pointers are 32 bit.

dataOffset = unsafe.Offsetof(struct {

b bmap

v int64

}{}.v)

// Possible tophash values. We reserve a few possibilities for special marks.

// Each bucket (including its overflow buckets, if any) will have either all or none of its

// entries in the evacuated* states (except during the evacuate() method, which only happens

// during map writes and thus no one else can observe the map during that time).

emptyRest = 0 // this cell is empty, and there are no more non-empty cells at higher indexes or overflows.

emptyOne = 1 // this cell is empty

evacuatedX = 2 // key/elem is valid. Entry has been evacuated to first half of larger table.

evacuatedY = 3 // same as above, but evacuated to second half of larger table.

evacuatedEmpty = 4 // cell is empty, bucket is evacuated.

minTopHash = 5 // minimum tophash for a normal filled cell.

// flags

iterator = 1 // there may be an iterator using buckets

oldIterator = 2 // there may be an iterator using oldbuckets

hashWriting = 4 // a goroutine is writing to the map

sameSizeGrow = 8 // the current map growth is to a new map of the same size

// sentinel bucket ID for iterator checks

noCheck = 1<

)

3. 哈希沖突

當有兩個或以上數量的鍵被哈希到了同一個bucket時,我們稱這些鍵發生了沖突。Go使用鏈地址法來解決鍵沖突。由于每個bucket可以存放8個鍵值對,所以同一個bucket存放超過8個鍵值對時就會再創建一個鍵值對,用類似鏈表的方式將bucket連接起來。

下圖展示產生沖突后的map:

bc71573956124f7a2014d373572c7e86.png

bucket數據結構指示下一個bucket的指針稱為overflow bucket,意為當前bucket盛不下而溢出的部分。事實上哈希沖突并不是好事情,它降低了存取效率,好的哈希算法可以保證哈希值的隨機性,但沖突過多也是要控制的,后面會再詳細介紹。

4. 負載因子

負載因子用于衡量一個哈希表沖突情況,公式為:

負載因子 = 鍵數量/bucket數量

例如,對于一個bucket數量為4,包含4個鍵值對的哈希表來說,這個哈希表的負載因子為1.

哈希表需要將負載因子控制在合適的大小,超過其閥值需要進行rehash,也即鍵值對重新組織:

哈希因子過小,說明空間利用率低

哈希因子過大,說明沖突嚴重,存取效率低

每個哈希表的實現對負載因子容忍程度不同,比如Redis實現中負載因子大于1時就會觸發rehash,而Go則在在負載因子達到6.5時才會觸發rehash,因為Redis的每個bucket只能存1個鍵值對,而Go的bucket可能存8個鍵值對,所以Go可以容忍更高的負載因子。

5. 漸進式擴容

5.1 擴容的前提條件

為了保證訪問效率,當新元素將要添加進map時,都會檢查是否需要擴容,擴容實際上是以空間換時間的手段。

觸發擴容的條件有二個:

1.????? 負載因子 > 6.5時,也即平均每個bucket存儲的鍵值對達到6.5個。

2.????? overflow數量 > 2^15時,也即overflow數量超過32768時。

5.2 增量擴容

當負載因子過大時,就新建一個bucket,新的bucket長度是原來的2倍,然后舊bucket數據搬遷到新的bucket。

考慮到如果map存儲了數以億計的key-value,一次性搬遷將會造成比較大的延時,Go采用逐步搬遷策略,即每次訪問map時都會觸發一次搬遷,每次搬遷2個鍵值對。

下圖展示了包含一個bucket滿載的map(為了描述方便,圖中bucket省略了value區域):

93f044aa041f7169fc96d03ef8f82b0e.png

當前map存儲了7個鍵值對,只有1個bucket。此地負載因子為7。再次插入數據時將會觸發擴容操作,擴容之后再將新插入鍵寫入新的bucket。

當第8個鍵值對插入時,將會觸發擴容,擴容后示意圖如下:

00035d0d6867888ad9ab28415e12396e.png

hmap數據結構中oldbuckets成員指身原bucket,而buckets指向了新申請的bucket。新的鍵值對被插入新的bucket中。后續對map的訪問操作會觸發遷移,將oldbuckets中的鍵值對逐步的搬遷過來。當oldbuckets中的鍵值對全部搬遷完畢后,刪除oldbuckets。

搬遷完成后的示意圖如下:

51552b26a20f3550dca1007e75e2ebc9.png

數據搬遷過程中原bucket中的鍵值對將存在于新bucket的前面,新插入的鍵值對將存在于新bucket的后面。實際搬遷過程中比較復雜,將在后續源碼分析中詳細介紹。

5.3 等量擴容

所謂等量擴容,實際上并不是擴大容量,buckets數量不變,重新做一遍類似增量擴容的搬遷動作,把松散的鍵值對重新排列一次,以使bucket的使用率更高,進而保證更快的存取。在極端場景下,比如不斷地增刪,而鍵值對正好集中在一小部分的bucket,這樣會造成overflow的bucket數量增多,但負載因子又不高,從而無法執行增量搬遷的情況,如下圖所示:

6145e3887d3b148c0c4abf434c22c3b5.png

上圖可見,overflow的bucket中大部分是空的,訪問效率會很差。此時進行一次等量擴容,即buckets數量不變,經過重新組織后overflow的bucket數量會減少,即節省了空間又會提高訪問效率。

6. 查找過程

查找過程如下:

1.????? 根據key值算出哈希值

2.????? 取哈希值低位與hmap.B取模確定bucket位置

3.????? 取哈希值高位在tophash數組中查詢

4.????? 如果tophash[i]中存儲值也哈希值相等,則去找到該bucket中的key值進行比較

5.????? 當前bucket沒有找到,則繼續從下個overflow的bucket中查找。

6.????? 如果當前處于搬遷過程,則優先從oldbuckets查找

注:如果查找不到,也不會返回空值,而是返回相應類型的0值。

7. 插入過程

新元素插入過程如下:

1.????? 根據key值算出哈希值

2.????? 取哈希值低位與hmap.B取模確定bucket位置

3.????? 查找該key是否已經存在,如果存在則直接更新值

4.????? 如果沒找到將key,將key插入

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/534487.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/534487.shtml
英文地址,請注明出處:http://en.pswp.cn/news/534487.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

四因素三水平正交表_做論文要用正交表?我打包送給你

正交試驗目前在國內的應用量仍然是比較高的&#xff0c;許多高校畢業生喜歡利用正交試驗來獲取研究數據&#xff0c;最終完成畢業論文的撰寫或者期刊投稿。正交試驗方案的設計&#xff0c;必然要用到(標準)正交表。那么大家都是從哪里獲取正交表的呢&#xff1f;小兵給這方面的…

plsql視圖添加表字段_Oracle-單表多字段查詢(不使用*)

環境&#xff1a;Oracle 11g&#xff0c;plsql 14目的&#xff1a;不使用*,查詢擁有上百個字段的表的所有字段。懶人大法&#xff1a;在文章末尾。sql實現邏輯&#xff1a;1、首先建一張100個字段以上的表&#xff0c;通過excel的方式將表建好后直接復制粘貼到plsql的建表界面。…

mysql 編譯安裝與rpm安裝的區別_編譯安裝與RPM安裝的區別

建議在安裝線上的生產服務器軟件包時都用源碼安裝&#xff0c;這是因為源碼安裝可以自行調整編譯參數&#xff0c;最大化地定制安裝結果。這里以MySQL 5線上環境的編譯安裝來說明之&#xff0c;其編譯參數如下所示&#xff1a;./configure-prefix/usr/local/mysql -without-deb…

python字符串變量s的值是python網絡爬蟲_【Python爬蟲作業】-字符串

一、定義字符串變量1.請定義三個字符串a,b,c值分別為 I,like, python2.請將上面三個變量合并輸出I like pythonaIblikecpythonprint(a)print(b)print(c)print(a,b,c)二、定義一個變量 s sdghHhf 1.請先將變量s的空白符去掉 賦值給新變量s1 打印輸出2.請分別將s1變為全部大寫(命…

lableimg閃退_CV學習筆記(二十五):數據集標注與制作

最近在做一些數據標注的工作&#xff0c;雖然標注數據比較枯燥&#xff0c;但這也是每個做算法的工程師升級打怪的必由之路。使用一些合適的工具往往可以事半功倍&#xff0c;效率UP。一&#xff1a;數據標注流程二&#xff1a;數據處理的一些小代碼1&#xff1a;重命名當得到這…

mysql show profile詳解_SQL 性能分析利器 show profile

本文首發個人公眾號《andyqian》, 期待你的關注&#xff5e;前言在之前的文章中&#xff0c;我們提到過一些慢SQL優化的步驟。其中就包括&#xff1a;使用 explain 關鍵字來查看執行計劃&#xff0c;是否命中索引。通過計算某列的區分度&#xff0c;來判斷該列是否適合新建索引…

php判斷給定的整數是否是2的冪_C++_C語言判斷一個數是否是2的冪次方或4的冪次方,快速判斷一個數是否是2的冪次 - phpStudy...

C語言判斷一個數是否是2的冪次方或4的冪次方快速判斷一個數是否是2的冪次方&#xff0c;若是&#xff0c;并判斷出來是多少次方&#xff01;將2的冪次方寫成二進制形式后&#xff0c;很容易就會發現有一個特點&#xff1a;二進制中只有一個1&#xff0c;并且1后面跟了n個0&…

python 包編譯安裝mysql_CentOS7編譯安裝MySQL8.0.23和Python3.1.9

卸載mariadbrpm -qa | grep mariadbmariadb-libs-5.5.64-1.el7.x86_64yum remove mariadb-libs.x86_64 -y安裝高版本GCC&#xff0c;解決編譯中會遇到的GCC 5.3 or newer is required (-dumpversion says 4.8.5)cd /optyum install centos-release-scl -yyum install devtoolse…

python3.0下載用什么瀏覽器_無法讓Python下載網頁源代碼:“不支持瀏覽器版本”...

查看您列出的url&#xff0c;我執行了以下操作&#xff1a;使用wget下載了頁面將urllib與ipython一起使用并下載了頁面使用chrome&#xff0c;只保存了url所有3個都給了我相同的結果文件(相同的大小&#xff0c;相同的內容)。在這可能是因為我沒有登錄&#xff0c;但我確實看到…

java線程堆棧_深入JVM剖析Java的線程堆棧

在這篇文章里我將教會你如何分析JVM的線程堆棧以及如何從堆棧信息中找出問題的根因。在我看來線程堆棧分析技術是Java EE產品支持工程師所必須掌握的一門技術。在線程堆棧中存儲的信息&#xff0c;通常遠超出你的想象&#xff0c;我們可以在工作中善加利用這些信息。我的目標是…

java 文件下載方法_【工具類】Java后臺上傳下載文件的幾種方式

/*** 將本地照片上傳至騰訊云服務上*/public void uploadImage(String localImagePath) throws Exception {// 1.將訂單照片上傳至騰訊地圖眾包側提供的云服務上try {File imageFile new File(localImagePath);if (imageFile.exists()) {String url "http://" map…

java io流讀取txt文件_Java使用IO流讀取TXT文件

通過BufferedReader讀取TXT文件window系統默認的編碼是GBK&#xff0c;而IDE的編碼多數為UTF-8&#xff0c;如果沒有規定new InputStreamReader(new FileInputStream(file),“GBK”)為GBK會出現讀取內容亂碼。//文件路徑String filePath"C:/Users/Admin/Desktop/products.…

c 調用java程序_C ++可以調用Java代碼嗎?

小編典典是的&#xff0c;您當然可以。這是一個例子&#xff1a;這是java文件&#xff1a;public class InvocationHelloWorld {public static void main(String[] args) {System.out.println("Hello, World!");System.out.println("Arguments sent to this pro…

java 大數類_Java大數類介紹

java能處理大數的類有兩個高精度大整數BigInteger和高精度浮點數BigDecimal&#xff0c;這兩個類位于java.math包內&#xff0c;要使用它們必須在類前面引用該包&#xff1a;importjava.math.BigInteger;和importjava.math.BigDecimal;或者importjava.math.*;以下從幾個方面對B…

java 畫樹_java – 如何繪制代表連接節點圖的樹?

我想在Java GUI中顯示樹,但我不知道如何.樹代表連接節點的圖形,如下所示&#xff1a;我應該說我有自己的樹類&#xff1a;public class BinaryTree{private BinaryNode root;public BinaryTree( ){root null;}public BinaryTree( Object rootItem ){root new BinaryNode( roo…

mysql 優化代碼_MySQL Order by 語句優化代碼詳解

Order by語句是用來排序的&#xff0c;經常我們會使用到Order by來進行排序&#xff0c;下面我給大家來講講Order by用法與優化排序&#xff0c;有需要的同學可參考MySQL Order By keyword是用來給記錄中的數據進行分類的。MySQL Order By Keyword根據關鍵詞分類ORDER BY keywo…

java.lang.class_關于Java.lang.Class的一些疑問

User.class可以在編譯時就確定下來Class的泛型&#xff0c;而new User().getClass()實際上是運行時才能確定下來實際是什么泛型。舉個例子&#xff1a;public class User{}public class Student extends User{public static void main(String[] args) {User user1 new User();…

java文件 linux_Linux執行Java文件

最近學習shell腳本&#xff0c;寫個簡單java類讓linux去執行java類沒別的東西&#xff0c;就引了一個fastjson的jar&#xff0c;寫了個main方法 序列化一個User對象 打印package com.lws.demo;import java.util.Date;import com.alibaba.fastjson.JSONObject;import com.lws.mo…

java 劊子手游戲_java基礎(九):容器

集合的引入List (ArrayList LinkedList)Set (HashSet LinkedHashSet TreeSet )Map (HashMap LinkedHashMap TreeMap)CollectionsIterator使用泛型1.為什么使用集合而不是數組&#xff1f;集合和數組相似點都可以存儲多個對象&#xff0c;對外作為一個整體存在數組的缺點長度必須…

java面試手寫單鏈表_(轉)面試大總結之一:Java搞定面試中的鏈表題目

packageLinkedListSummary;importjava.util.HashMap;importjava.util.Stack;/*** http://blog.csdn.net/luckyxiaoqiang/article/details/7393134 輕松搞定面試中的鏈表題目* http://www.cnblogs.com/jax/archive/2009/12/11/1621504.html 算法大全(1)單鏈表** 目錄&#xff1a…