陣列:其實所謂的陣列指的就是一組相關型別的變數集合,並且這些變數彼此之間沒有任何的關聯。儲存區間連續,占用記憶體嚴重,陣列有下標,查詢資料快,但是增刪比較慢;
鍊錶:一種常見的基礎資料結構,是一種線性表,但是不會按照線性的順序儲存資料,而是每乙個節點裡存到下乙個節點的指標。儲存區間離散,占用記憶體比較寬鬆,使用鍊錶查詢比較慢,但是增刪比較快;
雜湊表:hash table 既滿足了資料的快速查詢(根據關鍵碼值key value 而直接進行訪問的資料結構),也不會占用太多的記憶體空間,十分方便。雜湊表是陣列加鍊表組成。
hashmap結構及原理
hashmap是基於雜湊表的map介面的非同步實現。實現hashmap對資料的操作,允許有乙個null鍵,多個null值。
hashmap底層就是乙個陣列結構,陣列中的每一項又是乙個鍊錶。陣列+鍊錶結構,新建乙個hashmap的時候,就會初始化乙個陣列。entry就是陣列中的元素,每個entry其實就是乙個key-value的鍵值對,它持有乙個指向下乙個元素的引用,這就構成了鍊錶,hashmap底層將key-value當成乙個整體來處理,這個整體就是乙個entry物件。hashmap底層採用乙個entry【】陣列來儲存所有的key-value鍵值對,當需要儲存乙個entry物件時,會根據hash演算法來決定在其陣列中的位置,在根據equals方法決定其在該陣列位置上的鍊錶中的儲存位置;當需要取出乙個entry物件時,也會根據hash演算法找到其在陣列中的儲存位置, 在根據equals方法從該位置上的鍊錶中取出entry;
hashmap的儲存
put:(key-value)方法是hashmap中最重要的方法,使用hashmap最主要使用的就是put,get兩個方法。
判斷鍵值對陣列table[i]是否為空或者為null,否則執行resize()進行擴容;
根據鍵值key計算hash值得到插入的陣列索引 i ,如果table[i] == null ,直接新建節點新增即可,轉入6,如果table[i] 不為空,則轉向3;
判斷table[i] 的首個元素是否和key一樣,如果相同(hashcode和equals)直接覆蓋value,否則轉向4;
判斷table[i] 是否為treenode,即table[i]是否為紅黑樹,如果是紅黑樹,則直接插入鍵值對,否則轉向5;
遍歷table[i] ,判斷鍊錶長度是否大於8,大於8的話把鍊錶轉換成紅黑樹,進行插入操作,否則進行鍊錶插入操作;便利時遇到相同key直接覆蓋value;
插入成功後,判斷實際存在的鍵值對數量size是否超過了threshold,如果超過,則擴容;
看一下put原始碼
get方法取值過程:
int hash = key.hashcode();
int index = hash%entry.length;
指定key通過hash函式得到key的hash值;
呼叫內部方法getnode(),得到桶號(一般為hash值對桶數求摸);
比較桶的內部元素是否和key相等,如不相等,則沒有找到,相等,則取出相等記錄的value;
如果得到key所在桶的頭結點恰好是紅黑樹節點,就呼叫紅黑樹節點的gettreenode()方法,否則就遍歷鍊錶節點。gettreenode()方法通過呼叫樹形節點的find()方法進行查詢。由於之前新增時已經保證這個樹是有序的,因此查詢時基本就是折半查詢,效率高;
如果對比節點的雜湊值和要查詢的雜湊值相等,就會判斷key是否相等,相等就直接返回;不相等就從子樹中遞迴查詢;
hashmap中直接位址用hash函式生成,衝突用比較函式解決。如果每個桶內部只有乙個元素,那麼查詢的時候只有一次比較。當許多桶內沒有值得時候,許多查詢就會更快
addentry方法
新增新元素前,判斷是否需要對map的陣列進行擴容,如果需要擴容,則擴容多大?
對於新增key-value鍵值對,如果可以的hash值相同,則構造單向列表;
原始碼分析:
該方法主要完成兩個功能,乙個是新增新的key到entry陣列中,第二個就是對於不同的key的hash值相同的情況下,在同乙個陣列下標出,構建單向鍊錶進行儲存;
原始碼如下:
hashmap碰撞以及解決方法(開放定址法,在雜湊法,鏈位址法,建立乙個公共溢位區)
當兩個物件的hashcode相同時,他們的bucket位置相同,hash碰撞就會發生。因為hashmap使用linkedlist儲存物件,這個entry(儲存鍵值對的map.entry物件)會儲存在linkedlist中。這兩個物件算hashcode相同,但是他們可能並不相等。那麼如何獲取這兩個物件的值呢?當我們呼叫get()方法,hashmap會使用鍵值物件的hashcode找到bucket位置,遍歷linkedlist一直找到值物件。找到bucket位置以後,會呼叫keys.equals()方法去找到linkedlist中正確的節點,最終找到要找的值物件,使用final修飾,並採用合適的equals()和hashcode()方法,減少碰撞。
hashmap擴容機制
擴容必須滿足兩個條件
hashmap在新增值的時候,它預設能儲存16個鍵值對,直到你使用這個hashmap時,它才會給hashmap分配16個鍵值對的儲存空間,(負載因子為0.75,閾值為12),當16個鍵值對已經儲存滿了,我們在新增第17個鍵值對的時候才會發生擴容現象,因為前16個值,每個值在底層陣列中分別佔據乙個位置,並沒有發生hash碰撞。
hashmap也有可能儲存更多的鍵值對,最多可以儲存26個鍵值對,我們來算一下:儲存的前11個值全部發生hash碰撞,存到陣列的同乙個位置中,(這時元素個數小於閾值12,不會擴容),之後存入15個值全部分散到陣列剩下的15個位置中,(這時元素個數大於等於閾值,但是每次存入元素並沒有發生hash碰撞,不會擴容),11+15=26,當我們存入第27個值得時候滿足以上兩個條件,hashmap才會發生擴容;
參考HashMap底層原始碼手擼hashMap
package io.renren public class exthashmapimplements extmap 2.判斷陣列是否需要擴容 3.計算hash指定位置 int index getindex key,default iniital capacity nodekvnode table ...
HashMap原始碼簡單分析
1 還是老習慣,一邊看,一邊新增注釋,希望堅持下去,hashmap的基本原始碼進行了分析,內部一些介面和設計還沒來得及看23 一 成員 45 1 transient entry table 67 hashmap內部維護了乙個內部類 entry,用來存放鍵值對,這個entry實現了map.entry這...
白話HashMap原始碼(下)
這一篇主要看一下遍歷相關的原始碼。override public boolean containskey object key int hash collections.secondaryhash key hashmapentry tab table for hashmapentrye tab ha...