導航:首頁 > 源碼編譯 > glmap源碼

glmap源碼

發布時間:2023-05-07 07:10:38

⑴ HashMap源碼分析

HashMap(jdk 1.8)
  使用哈希表存儲鍵值對,底層是一個存儲Node的table數組。其基本的運行邏輯是,table數組的每一個元素是一個槽,用於存儲Node對象,向Hash表中插入鍵值對時,首先計算鍵的hash值,並對數組容量(即數組長度)取余,定位該鍵值對應放在哪個槽中,若槽中已經存在元素(即哈希沖突),將Node節點插入到沖突鏈表尾端,當該槽中結點超過一定數量(默認為8)並且哈希表容量超過一定值(默認為64)時,對該鏈表進行樹化。低於一定數量(默認為6)後進行resize時,樹形結構又會鏈表化。當哈希表的總節點數超過閾值時,對哈希表進行擴容,容量翻倍。

由於哈希表需要用到key的哈希函數,因此對於自定義類作為key使用哈希表時,需要重寫哈希函數,保證相等的key哈希值相同。

由於擴容或樹化過程Node節點的位置會發生改變,因此哈希表是無序的,不同時期遍歷同一張哈希表,得到的節點順序會不同。

成員變數

    Hash表的成員變數主要有存儲鍵值對的table數組,size,裝載因子loadFactory,閾值theshold,容量capacity。

table數組:

哈希表的實質就是table數組,它用來存儲鍵值對。哈希表中內部定義了Node類來封裝鍵值對。

 Node類實現了Map的Entry介面。包括key,value,下一個Node指針和key的hash值。

 容量Capacity:

HashMap中沒有專門的屬性存儲容量,容量等於table.lenth,即數組的長度,默認初始化容量為16。初始化時,可以指定哈希表容量,但容量必須是2的整數次冪,在初始化過程中,會調用tableSizeFor函數,得到一個不小於指定容量的2的整數次冪,暫時存入到threshold中。

注意,若容量設置過大,那麼遍歷整個哈希表需要耗費更多的時間。

    閾值threshold:

指定當前hash表最多存儲多少個鍵值對,當size超過閾值時,hash表進行擴容,擴容後容量翻倍。

   loadFactory:

threshold=capacity*loadFactory。

裝填因子的大小決定了哈希表存儲鍵值對數量的能力,它直接影響哈希表的查找性能。初始化時可以指定loadFactory的值,默認為0.75。

 初始化過程

哈希表有3個構造函數,用戶可以指定哈希表的初始容量和裝填因子,但初始化過程只是簡單填入這兩個參數,table數組並沒有初始化。注意,這里的initialCapacity會向上取2的整數次冪並暫時保存到threshold中。

table數組的初始化是在第一次插入鍵值對時觸發,實際在resize函數中進行初始化。

hash過程與下標計算

哈希表是通過key的哈希值決定Node放入哪個槽中, 在java8中 ,hash表沒有直接用key的哈希值,而是自定義了hash函數。

哈希表中的key可以為null,若為null,哈希值為0,否則哈希值(一共32位)的高16位不變,低16位與高16位進行異或操作,得到新的哈希值。(h >>> 16,表示無符號右移16位,高位補0,任何數跟0異或都是其本身,因此key的hash值高16位不變。)

之所以這樣處理,是與table的下標計算有關

因為,table的長度都是2的冪,因此index僅與hash值的低n位有關(n-1為0000011111的形式),hash值的高位都被與操作置為0了,相當於hash值對n取余。

由於index僅與hash值的低n位有關,把哈希值的低16位與高16位進行異或處理,可以讓Node節點能更隨機均勻得放入哈希表中。

get函數:

V  get(Object key) 通過給定的key查找value

先計算key的哈希值,找到對應的槽,遍歷該槽中所有結點,如果結點中的key與查找的key相同或相等,則返回value值,否則返回null。

注意,如果返回值是null,並不一定是沒有這種KV映射,也有可能是該key映射的值value是null,即key-null的映射。也就是說,使用get方法並不能判斷這個key是否存在,只能通過containsKey方法來實現。

put函數

V put(K key, V value) 存放鍵值對

計算key的哈希值,找到哈希表中對應的槽,遍歷該鏈表,如果發現已經存在包含該key的Node,則把新的value放入該結點中,返回該結點的舊value值(舊value可能是null),如果沒有找到,則創建新結點插入到 鏈表尾端 (java7使用的是頭插法),返回null。插入結點後需要判斷該鏈表是否需要樹化,並且判斷整個哈希表是否需要擴容。

由該演算法可以看出,哈希表中不會有兩個key相同的鍵值對。

resize函數

    resize函數用來初始化或擴容table數組。主要做了兩件事。

1.根據table數組是否為空判斷初始化還是擴容,計算出相應的newCap和newThr,新建table數組並設置新的threshold。

2.若是進行擴容,則需要將原數組中的Node移植到新的數組中。

由於數組擴容,原來鍵值對可能存儲在新數組不同的槽中。但由於鍵值對位置是對數組容量取余(index=hash(key)&(Capacity-1)),由於Capacity固定為2的整數次冪,並新的Capacity(newCap)是舊的兩倍(oldCap)。因此,只需要判斷每個Node中的hash值在oldCap二進制那一位上是否為0(hash&oldCap==0),若為0,對newCap取余值與oldCap相同,Node在新數組中槽的位置不變,若為1,新的位置是就得位置加一個oldCap值。

因此hashMap的移植演算法是,遍歷舊的槽:

若槽為空,跳過。

若槽只有一個Node,計算該Node在新數組中的位置,放入新槽中。

若槽是一個鏈表,則遍歷這個鏈表,分別用loHead,loTail,hiHead,hiTail兩組指針建立兩個鏈表,把hash值oldCap位為0的Node節點放入lo鏈表中,hash值oldCap位為1的節點放入hi鏈表中,之後將兩個鏈表分別插入到新數組中,實現原鍵值對的移植。

lo鏈表放入新數組原來位置,hi鏈表放入新數組原來位置+oldCap中

樹化過程 :

當一個槽中結點過多時,哈希表會將鏈表轉換為紅黑樹,此處挖個坑。

[總結] java8中hashMap的實現原理與7之間的區別:

1.hash值的計算方法不同 2.鏈表採用尾插法,之前是頭插法 3.加入了紅黑樹結構

⑵ Mapbox源碼分析(2)url解析

通過源碼,我們來一步步分析Mapbox地圖引擎如何進行指定字元串變數解析成url地址載入的,這里是基於5.3.0的版本.

在官方demo中,我們不僅可以載入本地樣式文件,已定義樣式文件和網路在線文件,它們的格式分別是

1. "asset://test.json"

2 . "https://www.mapbox.com/android-docs/files/mapbox-raster-v8.json"

3 . "mapbox://styles/mapbox/streets-v10"

這些格式,那麼Mapbox如果解析這些字元串去獲取到需要的樣式數據呢?我們從 Mapbox源碼分析(1)樣式載入 這篇的loadURL()方法開始看起

我們在這里看到,樣式的數據是通過fileSource.request進行請求載入的,通過調試我們發現這個fileSource是FileSource的子類DefaultFileSource,那麼我們先看看這個DefaultFileSource是什麼時候傳進來的

我們在這里看到,是在構造方法時對fileSource變數進行初始化的,那麼我們只需要看到Style::Impl對象什麼時候構造的,便知道了fileSource的來源,繼續往回找

在這里我們發現Impl對象的fileSource是Style對象構造時傳進來的,那麼我們繼續往回找

這里我們看到Style對象是通過map.cpp里的getStyle對象獲取的,而style對象是在Map::Impl::Impl構造方法時初始化的,繼續往回找

這里我們其實也能大概猜出來Map::Impl對象是在Map構造方法時初始化的,那麼map對象又是什麼時候初始化的,是不是覺得很繞,馬上就快到了,我們找到native_map_view.cpp文件,發現在NativeMapView構造方法中構造了map對象

到這里我們已經基本清楚fileSource的來源了,是JAVA層NativeMapView對象初始化的時候傳下來的,我們繼續看到開頭,既然我們已經知道fileSource對象是DefaultFileSource,那麼它調用的request方法,也就是調用的DefaultFileSource的request方法,這里我們看到default_file_source.cpp文件

這里我們看到它轉到了它的實現類的request方法

這里我們可以看到根據url的不同,和載入方法的不同,將請求分別轉給了assetFileSource,localFileSource,onlineFileSource等的request方法,這里我們看onlineFileSource的request方法

看到這里我們看到根據請求的類型不同,去處理不同的url,在這些參數里我們看下apiBaseURL這個變數,這是一個base url,指定了伺服器地址,我們在constants.hpp文件中找到了它

constexpr const char* API_BASE_URL = "https://api.mapbox.com";

繼續往下看,我們選normalizeStyleURL()方法往下看

這里我們看到它先驗證了一下url,然後將url字元串包裝成URL對象,然後進行一個拼接成tpl變數,最後再通過transformURL函數進行一個轉換,這里我們先看它如何包裝這個URL對象的

這里我們看到它將字元串分解成query,scheme,domain,path四個變數進行存儲,我們再看看transformURL()函數

這里我們看到根據url的不同變數值進行了再次字元串拼接,甚至根據路徑的不同,繼續拆分成Path對象,最後將拼接結果返回,到這里有關url解析拼接的過程就講完了.

⑶ golang map源碼淺析

golang 中 map的實現結構為: 哈希表 + 鏈表。 其中鏈表,作用是當發生hash沖突時,拉鏈法生成的結點。

可以看到, []bmap 是一個hash table, 每一個 bmap是我們常說的「桶」。 經過hash 函數計算出來相同的hash值, 放到相同的桶中。 一個 bmap中可以存放 8個 元素, 如果多出8個,則生成新的結點,尾接到隊尾。

以上是只是靜態文件 src/runtime/map.go 中的定義。 實際上編譯期間會給它加料 ,動態地創建一個新的結構:

上圖就是 bmap的內存模型, HOB Hash 指的就是 top hash。 注意到 key 和 value 是各自放在一起的,並不是 key/value/key/value/... 這樣的形式。源碼里說明這樣的好處是在某些情況下可以省略掉 padding 欄位,節省內存空間。

每個 bmap設計成 最多隻能放 8 個 key-value 對 ,如果有第 9 個 key-value 落入當前的 bmap,那就需要再構建一個 bmap,通過 overflow 指針連接起來。

map創建方法:

我們實際上是通過調用的 makemap ,來創建map的。實際工作只是初始化了hmap中的各種欄位,如:設置B的大小, 設置hash 種子 hash 0.

注意 :

makemap 返回是*hmap 指針, 即 map 是引用對象, 對map的操作會影響到結構體內部

使用方式

對應的是下面兩種方法

map的key的類型,實現了自己的hash 方式。每種類型實現hash函數方式不一樣。

key 經過哈希計算後得到hash值,共 64 個 bit 位。 其中後B 個bit位置, 用來定位當前元素落在哪一個桶里, 高8個bit 為當前 hash 值的top hash。 實際上定位key的過程是一個雙重循環的過程, 外層循環遍歷 所有的overflow, 內層循環遍歷 當前bmap 中的 8個元素

舉例說明: 如果當前 B 的值為 5, 那麼buckets 的長度 為 2^5 = 32。假設有個key 經過hash函數計算後,得到的hash結果為:

外層遍歷bucket 中的鏈表

內層循環遍歷 bmap中的8個 cell

建議先不看此部分內容,看完後續 修改 map中元素 -> 擴容 操作後 再回頭看此部分內容。

擴容前的數據:

等量擴容後的數據:

等量擴容後,查找方式和原本相同, 不多做贅述。

兩倍擴容後的數據

兩倍擴容後,oldbuckets 的元素,可能被分配成了兩部分。查找順序如下:

此處只分析 mapaccess1 ,。 mapaccess2 相比 mapaccess1 多添加了是否找到的bool值, 有興趣可自行看一下。

使用方式:

步驟如下:

擴容條件 :

擴容的標識 : h.oldbuckets != nil

假設當前定位到了新的buckets的3號桶中,首先會判斷oldbuckets中的對應的桶有沒有被搬遷過。 如果搬遷過了,不需要看原來的桶了,直接遍歷新的buckets的3號桶。

擴容前:

等量擴容結果

雙倍擴容會將old buckets上的元素分配到x, y兩個部key & 1 << B == 0 分配到x部分,key & 1 << B == 1 分配到y部分

注意: 當前只對雙倍擴容描述, 等量擴容只是重新填充了一下元素, 相對位置沒有改變。

假設當前map 的B == 5,原本元素經過hash函數計算的 hash 值為:

因為雙倍擴容之後 B = B + 1,此時B == 6。key & 1 << B == 1, 即 當前元素rehash到高位,新buckets中 y 部分. 否則 key & 1 << B == 0 則rehash到低位,即x 部分。

使用方式:

可以看到,每一遍歷生成迭代器的時候,會隨機選取一個bucket 以及 一個cell開始。 從前往後遍歷,再次遍歷到起始位置時,遍歷完成。

https://www.qcrao.com/2019/05/22/dive-into-go-map/

https://draveness.me/golang/docs/part2-foundation/ch03-datastructure/golang-hashmap/

https://www.bilibili.com/video/BV1Q4411W7MR?spm_id_from=333.337.search-card.all.click

⑷ 在OpenGL中,函數glMap2f具體怎麼用及如何確定參數如何確定控制點

int x = 5;
int y = 4;
int z = 3;
float u1=0;
float u2=1;
float v1=0;
float v2=1;
int n=100;
float data[x][y][z]={...};
Gl.glMap2f(Gl.GL_MAP2_VERTEX_3, u1, u2, z, y, v1, v2, z * y, x, &data[0][0][0]);
.
.
.
Gl.glMapGrid2f(n, u1, u2, n, v1, v2);
Gl.glEvalMesh2(Gl.GL_FILL, 0, n, 0, n);
大致帆粗巧就這樣態鍵 意凳源思一下

閱讀全文

與glmap源碼相關的資料

熱點內容
如何在vps上搭建自己的代理伺服器 瀏覽:742
nginxphp埠 瀏覽:401
內臟pdf 瀏覽:150
怎麼看雲伺服器架構 瀏覽:83
我的世界國際服為什麼登不進伺服器 瀏覽:994
微盟程序員老婆 瀏覽:928
intellij創建java 瀏覽:110
java連接odbc 瀏覽:38
啟動修復無法修復電腦命令提示符 瀏覽:359
手機編程是什麼 瀏覽:98
山東移動程序員 瀏覽:163
蘇州java程序員培訓學校 瀏覽:477
單片機液晶驅動 瀏覽:854
魔拆app里能拆到什麼 瀏覽:131
新預演算法的立法理念 瀏覽:144
wdcpphp的路徑 瀏覽:134
單片機p0口電阻 瀏覽:926
瀏覽器中調簡訊文件夾 瀏覽:594
五菱宏光空調壓縮機 瀏覽:69
為什麼app佔用幾百兆 瀏覽:680