日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

hashmap containsvalue时间复杂度_面试宝典:数据结构HashMap

發(fā)布時間:2025/3/21 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 hashmap containsvalue时间复杂度_面试宝典:数据结构HashMap 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

常用數(shù)據(jù)結(jié)構在新增、查找等基礎操作上的性能

1、數(shù)組

  • 采用一段連續(xù)的存儲單元來存儲數(shù)據(jù)

  • 對于指定下標的查找,時間復雜度為O(1)

  • 通過給定值進行查找,需要遍歷數(shù)組,逐一比對給定關鍵字和數(shù)組元素,時間復雜度為O(n)

  • 對于有序數(shù)組,則可采用二分查找,插值查找,斐波那契查找等方式,可將查找復雜度提高為O(logn)

  • 對于一般的插入刪除操作,涉及到數(shù)組元素的移動,其平均復雜度也為O(n)

2、線性鏈表

  • 對于鏈表的新增,刪除等操作(在找到指定操作位置后),僅需處理結(jié)點間的引用即可,時間復雜度為O(1)

  • 查找操作需要遍歷鏈表逐一進行比對,復雜度為O(n)

3、二叉樹

對一棵相對平衡的有序二叉樹,對其進行插入,查找,刪除等操作,平均復雜度均為O(logn)

4、哈希表

  • 在哈希表中進行添加,刪除,查找等操作,性能十分之高 不考慮哈希沖突的情況下 僅需一次定位即可完成,時間復雜度為O(1)

數(shù)據(jù)結(jié)構的物理存儲結(jié)構

1、順序存儲結(jié)構

2、鏈式存儲結(jié)構

順序存儲結(jié)構和鏈式存儲結(jié)構

JDK 1.8 hashmap put邏輯圖

注意:JDK8之后

  • 如果哈希表單向鏈表中元素超過8個,那么單向鏈表這種數(shù)據(jù)結(jié)構會變成紅黑樹數(shù)據(jù)結(jié)構當

  • 紅黑樹上的節(jié)點數(shù)量小于6個,會重新把紅黑樹變成單向鏈表數(shù)據(jù)結(jié)構

hash沖突

概念

對某個元素進行哈希運算,得到一個存儲地址,然后要進行插入的時候,發(fā)現(xiàn)已經(jīng)被其他元素占用了,其實這就是所謂的哈希沖突,也叫哈希碰撞

沖突必然性

數(shù)組是一塊連續(xù)的固定長度的內(nèi)存空間,再好的哈希函數(shù)也不能保證得到的存儲地址絕對不發(fā)生沖突

解決沖突方式

哈希沖突的解決方案有多種:

1、開放定址法(發(fā)生沖突,繼續(xù)尋找下一塊未被占用的存儲地址)

2、再散列函數(shù)法

3、鏈地址法 (HashMap即是采用了鏈地址法,也就是數(shù)組+鏈表的方式)

深入源碼分析實現(xiàn)原理

Entry是HashMap中的一個靜態(tài)內(nèi)部類

static?class?Entry?implements?Map.Entry?{
????????final?K?key;
????????V?value;
????????Entry?next;//存儲指向下一個Entry的引用,單鏈表結(jié)構
????????int?hash;//對key的hashcode值進行hash運算后得到的值,存儲在Entry,避免重復計算
????????/**
?????????*?Creates?new?entry.
?????????*/
????????Entry(int?h,?K?k,?V?v,?Entry?n)?{
????????????value?=?v;
????????????next?=?n;
????????????key?=?k;hash?=?h;
????????}?

hashmap總體結(jié)構

HashMap中的鏈表出現(xiàn)越少,性能才會越好

重要字段

/**實際存儲的key-value鍵值對的個數(shù)*/
transient?int?size;

/**閾值,當table ==?{}時,該值為初始容量(初始容量默認為16);當table被填充了,也就是為table分配內(nèi)存空間后,
threshold一般為 capacity*loadFactory。HashMap在進行擴容時需要參考threshold,后面會詳細談到*/
int?threshold;

/**負載因子,代表了table的填充度有多少,默認是0.75
加載因子存在的原因,還是因為減緩哈希沖突,如果初始桶為16,等到滿16個元素才擴容,某些桶里可能就有不止一個元素了。
所以加載因子默認為0.75,也就是說大小為16的HashMap,到了第13個元素,就會擴容成32。
*/
final?float?loadFactor;

/**HashMap被改變的次數(shù),由于HashMap非線程安全,在對HashMap進行迭代時,
如果期間其他線程的參與導致HashMap的結(jié)構發(fā)生變化了(比如put,remove等操作),
需要拋出異常ConcurrentModificationException*/
transient?int?modCount;

initialCapacity默認為16,loadFactory默認為0.75

構造器

沒有為數(shù)組table分配內(nèi)存空間(有一個入?yún)橹付∕ap的構造器例外)

而是在執(zhí)行put操作的時候才真正構建table數(shù)組

put函數(shù)

public?V?put(K?key,?V?value)?{
????????//如果table數(shù)組為空數(shù)組{},進行數(shù)組填充(為table分配實際內(nèi)存空間),入?yún)閠hreshold,
????????//此時threshold為initialCapacity?默認是1<<4(24=16)
????????if?(table?==?EMPTY_TABLE)?{
????????????inflateTable(threshold);
????????}
???????//如果key為null,存儲位置為table[0]或table[0]的沖突鏈上
????????if?(key?==?null)
????????????return?putForNullKey(value);
????????int?hash?=?hash(key);//對key的hashcode進一步計算,確保散列均勻
????????int?i?=?indexFor(hash,?table.length);//獲取在table中的實際位置
????????for?(Entry?e?=?table[i];?e?!=?null;?e?=?e.next)?{
????????//如果該對應數(shù)據(jù)已存在,執(zhí)行覆蓋操作。用新value替換舊value,并返回舊value
????????????Object?k;if?(e.hash?==?hash?&&?((k?=?e.key)?==?key?||?key.equals(k)))?{
????????????????V?oldValue?=?e.value;
????????????????e.value?=?value;
????????????????e.recordAccess(this);return?oldValue;
????????????}
????????}
????????modCount++;//保證并發(fā)訪問時,若HashMap內(nèi)部結(jié)構發(fā)生變化,快速響應失敗
????????addEntry(hash,?key,?value,?i);//新增一個entryreturn?null;
????}

inflateTable函數(shù)

  • 用于為主干數(shù)組table在內(nèi)存中分配存儲空間

  • 通過roundUpToPowerOf2(toSize)可以確保capacity為大于或等于toSize的最接近toSize的二次冪,比如toSize=13,則capacity=16;to_size=16,capacity=16;to_size=17,capacity=32.

private?void?inflateTable(int?toSize)?{
????????int?capacity?=?roundUpToPowerOf2(toSize);//capacity一定是2的次冪
????????/**此處為threshold賦值,取capacity*loadFactor和MAXIMUM_CAPACITY+1的最小值,
????????capaticy一定不會超過MAXIMUM_CAPACITY,除非loadFactor大于1?*/
????????threshold?=?(int)?Math.min(capacity?*?loadFactor,?MAXIMUM_CAPACITY?+?1);
????????table?=?new?Entry[capacity];
????????initHashSeedAsNeeded(capacity);
????}

roundUpToPowerOf2函數(shù)

使得數(shù)組長度一定為2的次冪,Integer.highestOneBit是用來獲取最左邊的bit(其他bit位為0)所代表的數(shù)值.

private?static?int?roundUpToPowerOf2(int?number)?{
????????//?assert?number?>=?0?:?"number?must?be?non-negative";
????????return?number?>=?MAXIMUM_CAPACITY
??????????????????MAXIMUM_CAPACITY
????????????????:?(number?>?1)???Integer.highestOneBit((number?-?1)?<????}

hash函數(shù)

  • 用了很多的異或,移位等運算

  • 對key的hashcode進一步進行計算以及二進制位的調(diào)整等來保證最終獲取的存儲位置盡量分布均勻

final?int?hash(Object?k)?{
????????int?h?=?hashSeed;
????????if?(0?!=?h?&&?k?instanceof?String)?{
????????????return?sun.misc.Hashing.stringHash32((String)?k);
????????}

????????h?^=?k.hashCode();

????????h?^=?(h?>>>?20)?^?(h?>>>?12);
????????return?h?^?(h?>>>?7)?^?(h?>>>?4);
????}

indexFor

以上hash函數(shù)計算出的值,通過indexFor進一步處理來獲取實際的存儲位置

/**
*?返回數(shù)組下標
*/
static?int?indexFor(int?h,?int?length)?{
????return?h?&?(length-1);
}??

1、h&(length-1)保證獲取的index一定在數(shù)組范圍內(nèi)

舉個例子,默認容量16,length-1=15,h=18,轉(zhuǎn)換成二進制計算為index=2

2、位運算對計算機來說,性能更高一些(HashMap中有大量位運算)

終存儲位置的確定流程

addEntry函數(shù)

void?addEntry(int?hash,?K?key,?V?value,?int?bucketIndex)?{
????????if?((size?>=?threshold)?&&?(null?!=?table[bucketIndex]))?{
????????????resize(2?*?table.length);//當size超過臨界閾值threshold,并且即將發(fā)生哈希沖突時進行擴容
????????????hash?=?(null?!=?key)???hash(key)?:?0;
????????????bucketIndex?=?indexFor(hash,?table.length);
????????}

????????createEntry(hash,?key,?value,?bucketIndex);
????}
  • 當發(fā)生哈希沖突并且size大于閾值的時候,需要進行數(shù)組擴容

  • 擴容時,需要新建一個長度為之前數(shù)組2倍的新的數(shù)組

  • 然后將當前的Entry數(shù)組中的元素全部傳輸過去,擴容后的新數(shù)組長度為之前的2倍

  • 擴容相對來說是個耗資源的操作

為何HashMap的數(shù)組長度一定是2的次冪

resize擴容函數(shù)

void?resize(int?newCapacity)?{
????????Entry[]?oldTable?=?table;
????????int?oldCapacity?=?oldTable.length;
????????if?(oldCapacity?==?MAXIMUM_CAPACITY)?{
????????????threshold?=?Integer.MAX_VALUE;
????????????return;
????????}

????????Entry[]?newTable?=?new?Entry[newCapacity];
????????transfer(newTable,?initHashSeedAsNeeded(newCapacity));
????????table?=?newTable;
????????threshold?=?(int)Math.min(newCapacity?*?loadFactor,?MAXIMUM_CAPACITY?+?1);
????}

如果數(shù)組進行擴容,數(shù)組長度發(fā)生變化,而存儲位置 index = h&(length-1),index也可能會發(fā)生變化,需要重新計算index

transfer函數(shù)

void?transfer(Entry[]?newTable,?boolean?rehash)?{
????????int?newCapacity?=?newTable.length;
?????//for循環(huán)中的代碼,逐個遍歷鏈表,重新計算索引位置,將老數(shù)組數(shù)據(jù)復制到新數(shù)組中去(數(shù)組不存儲實際數(shù)據(jù),所以僅僅是拷貝引用而已)
????????for?(Entry?e?:?table)?{while(null?!=?e)?{
????????????????Entry?next?=?e.next;if?(rehash)?{
????????????????????e.hash?=?null?==?e.key???0?:?hash(e.key);
????????????????}
????????????????int?i?=?indexFor(e.hash,?newCapacity);
????????????????//將當前entry的next鏈指向新的索引位置,newTable[i]有可能為空,有可能也是個entry鏈,如果是entry鏈,直接在鏈表頭部插入。
????????????????e.next?=?newTable[i];
????????????????newTable[i]?=?e;
????????????????e?=?next;
????????????}
????????}
????}
  • 將老數(shù)組中的數(shù)據(jù)逐個鏈表地遍歷 扔到新的擴容后的數(shù)組中

  • 數(shù)組索引位置的計算是通過 對key值的hashcode進行hash擾亂運算后

  • 通過和 length-1進行位運算得到最終數(shù)組索引位置

1、

HashMap的數(shù)組長度一定保持2的次冪

比如16的二進制表示為 10000,那么length-1就是15,二進制為01111

同理擴容后的數(shù)組長度為32,二進制表示為100000,length-1為31,二進制表示為011111

  • 會保證低位全為1,而擴容后只有一位差異,也就是多出了最右位的1

  • 在通過 h&(length-1)的時候,只要h對應的最左邊的那一個差異位為0,就能保證得到的新的數(shù)組索引和老數(shù)組索引一致(大大減少了之前已經(jīng)散列良好的老數(shù)組的數(shù)據(jù)位置重新調(diào)換)

2、

數(shù)組長度保持2的次冪,length-1的低位都為1,會使得獲得的數(shù)組索引index更加均勻

3、

  • &運算,高位是不會對結(jié)果產(chǎn)生影響的(hash函數(shù)采用各種位運算可能也是為了使得低位更加散列)

  • 只關注低位bit,如果低位全部為1,那么對于h低位部分來說,任何一位的變化都會對結(jié)果產(chǎn)生影響,也就是說,要得到index=21這個存儲位置,h的低位只有這一種組合

4、

  • 如果不是2的次冪,也就是低位不是全為1此時,要使得index=21,h的低位部分不再具有唯一性了,哈希沖突的幾率會變的更大

  • index對應的這個bit位無論如何不會等于1了,而對應的那些數(shù)組位置也就被白白浪費了

get函數(shù)

public?V?get(Object?key)?{
?????//如果key為null,則直接去table[0]處去檢索即可。
????????if?(key?==?null)
????????????return?getForNullKey();
????????Entry?entry?=?getEntry(key);return?null?==?entry???null?:?entry.getValue();
?}

get方法通過key值返回對應value,如果key為null,直接去table[0]處檢索

getEntry函數(shù)

final?Entry?getEntry(Object?key)?{if?(size?==?0)?{return?null;
????????}
????????//通過key的hashcode值計算hash值
????????int?hash?=?(key?==?null)???0?:?hash(key);
????????//indexFor?(hash&length-1)?獲取最終數(shù)組索引,然后遍歷鏈表,通過equals方法比對找出對應記錄for?(Entry?e?=?table[indexFor(hash,?table.length)];
?????????????e?!=?null;
?????????????e?=?e.next)?{
????????????Object?k;if?(e.hash?==?hash?&&?
????????????????((k?=?e.key)?==?key?||?(key?!=?null?&&?key.equals(k))))return?e;
????????}return?null;
????}????

key(hashcode)–>hash–>indexFor–>最終索引位置,找到對應位置table[i],再查看是否有鏈表,遍歷鏈表,通過key的equals方法比對查找對應的記錄

e.hash == hash 這里是否有必要判斷?從而引出下個話題

重寫equals方法需同時重寫hashCode方法

如果沒有重寫hashCode方法

put:key(hashcode1)–>hash–>indexFor–>最終索引位置

get:通過key取出value的時候 key(hashcode1)–>hash–>indexFor–>最終索引位置

由于hashcode1不等于hashcode2,導致沒有定位到一個數(shù)組位置而返回邏輯上錯誤的值null

小結(jié):

在重寫equals的方法的時候,必須注意重寫hashCode方法

同時還要保證通過equals判斷相等的兩個對象,調(diào)用hashCode方法要返回同樣的整數(shù)值

而如果equals判斷不相等的兩個對象,其hashCode可以相同(只不過會發(fā)生哈希沖突,應盡量避免)

HashMap的數(shù)據(jù)結(jié)構

特點簡介

  • 無序

因為不一定掛到哪一個單向鏈表上的,因此加入順序和取出也不一樣

  • 不可重復

1、使用equals方法來保證HashMap集合key不可重復,如key重復來,value就會覆蓋

2、存放在HashMap集合key部分的元素,其實就是存放在HashSet集合中,則HashSet集合也需要重寫equals和hashCode方法

參考文章

https://blog.csdn.net/woshimaxiao1/article/details/83661464

總結(jié)

以上是生活随笔為你收集整理的hashmap containsvalue时间复杂度_面试宝典:数据结构HashMap的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。