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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

HashMap源码解释

發(fā)布時間:2024/7/23 编程问答 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 HashMap源码解释 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

HashMap


前言:
本文的hashMap是基于jdk1.7的hashMap.
關(guān)于jdk1.8的hashMap在另一篇中,那里將會介紹與1.7的差異與優(yōu)勢

首先基礎(chǔ)知識介紹:

1.HashMap的成員變量
  int DEFAULT_INITIAL_CAPACITY = 16:默認的初始容量為2 ^ 4
  int MAXIMUM_CAPACITY = 1 << 30:最大的容量為 2 ^ 30
  float DEFAULT_LOAD_FACTOR = 0.75f:默認的加載因子為 0.75f
  Entry< K,V>[] table:Entry類型的數(shù)組,HashMap用這個來維護內(nèi)部的數(shù)據(jù)結(jié)構(gòu),它的長度由容量決定
  int size:HashMap的大小
  int threshold:HashMap的極限容量,擴容臨界點(容量和加載因子的乘積),默認為12

2.HashMap的構(gòu)造函數(shù)
  public HashMap():構(gòu)造一個具有默認初始容量 (16) 和默認加載因子 (0.75) 的空 HashMap
  public HashMap(int initialCapacity):構(gòu)造一個帶指定初始容量和默認加載因子 (0.75) 的空 HashMap
  public HashMap(int initialCapacity, float loadFactor):構(gòu)造一個帶指定初始容量和加載因子的空 HashMap
  public HashMap(Map< ? extends K, ? extends V> m):構(gòu)造一個映射關(guān)系與指定 Map 相同的新 HashMap
  
3.HashMap的數(shù)據(jù)結(jié)構(gòu)
要知道hashmap是什么,首先要搞清楚它的數(shù)據(jù)結(jié)構(gòu),在java編程語言中,最基本的結(jié)構(gòu)就是兩種,一個是數(shù)組,另外一個是模擬指針(引用),所有的數(shù)據(jù)結(jié)構(gòu)都可以用這兩個基本結(jié)構(gòu)來構(gòu)造的,hashmap也不例外。Hashmap實際上是一個數(shù)組和鏈表的結(jié)合體(在數(shù)據(jù)結(jié)構(gòu)中,一般稱之為“鏈表散列“),請看下圖(縱排表示數(shù)組,橫排表示數(shù)組元素【實際上是一個鏈表】)。

從圖中我們可以看到一個hashmap就是一個數(shù)組結(jié)構(gòu),當(dāng)新建一個hashmap的時候,就會初始化一個數(shù)組。

4.hashMap的put邏輯
首先put有兩個入?yún)?我們擬定叫k和v,
提前申明上圖中的縱列,我們也叫hash表,也稱table,table[index]表示下標為index的bucket(這里的bucket可能是多個entry,比如上圖的第一行,有4個)
1).根據(jù)k算出hash值(這個hash方法,可以說是整個hashMap的精華所在,后面講)
2).根據(jù)這個hash值,算出index下標(這里算的地方很巧妙,Entry數(shù)組的大小規(guī)定為2的冪就是為了能夠使用這個算法來確定數(shù)組的下標,具體實現(xiàn)后面講)
3).如果找不到該下標對應(yīng)的entry,就新建一個entry(新建entry涉及到擴容,后面講)
4),如果找到了該下標,就在對應(yīng)的多個entry中找對應(yīng)k的entry,找到就替換這個entry的value,找不到就新建一個entry
注意:hash表的初始化過程,并不是在new HashMap的時候執(zhí)行的,而是在第一次push的時候執(zhí)行的

5.hash表的初始化
初始化方法內(nèi)部會重新計算Entry數(shù)組的容量,因為在構(gòu)造HashMap時傳入的初始化大小可能不是2的冪(前面有提到,hashMap的構(gòu)造函數(shù),忘了快去看…),因此要將這個數(shù)轉(zhuǎn)換成2的冪再去根據(jù)新的容量新建Entry數(shù)組。初始化哈希表時再次重新設(shè)置閥值,閥值一般是capacity*loadFactor。
此外,在初始化哈希表時還會去初始化哈希種子(hashSeed),這個hashSeed用于優(yōu)化哈希函數(shù),默認為0是不使用替代哈希算法,但是也可以自己去設(shè)置hashSeed的值,以達到優(yōu)化效果。

6.entry新建與擴容
新建一個Entry之前會先判斷當(dāng)前集合元素的大小是否超過了閥值,如果超過了閥值并且當(dāng)前entry所在位置不為空,就調(diào)用resize進行擴容。傳入的新的容量是原來哈希表的兩倍,在resize方法內(nèi)部會新建一個容量為原先的2倍的Entry數(shù)組。然后將舊的哈希表里面的元素全部遷移到新的哈希表,其中可能會進行再哈希,根據(jù)initHashSeedAsNeeded方法計算的值來確定是否進行再哈希。完成哈希表的遷移之后,將當(dāng)前哈希表替換為新的,最后再根據(jù)新的哈希表容量來重新計算HashMap的閥值。
很明顯,擴容步驟很多,操作很多,所以我們要合理的設(shè)置初始容量,盡量要避免這種擴容

7.如何根據(jù)hash值,算出index下標
indexFor方法是根據(jù)hash碼來計算出在數(shù)組中對應(yīng)的下標。我們可以看到在這個方法內(nèi)部使用了與(&)操作符。與操作是對兩個操作數(shù)進行位運算,如果對應(yīng)的兩個位都為1,結(jié)果才為1,否則為0。與操作經(jīng)常會用于去除操作數(shù)的高位值,例如:01011010 & 00001111 = 00001010。

//返回哈希碼對應(yīng)的數(shù)組下標 static int indexFor(int h, int length) {return h & (length-1); }


已知傳入的length是Entry數(shù)組的長度,我們知道數(shù)組下標是從0開始計算的,所以數(shù)組的最大下標為length-1.如果length為2的冪,那么length-1的二進制位后面都為1.這時h&(length-1)的作用就是去掉了h的高位值,只留下h的低位值來作為數(shù)組的下標.由此可以看到Entry數(shù)組的大小規(guī)定為2的冪就是為了能夠使用這個算法來確定數(shù)組的下標.

8.計算hash
這個先上源碼

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);}

hash方法的最后兩行是真正計算hash值的算法,計算hash碼的算法被稱為擾動函數(shù),所謂的擾動函數(shù)就是把所有東西雜糅到一起,可以看到這里使用了四個向右移位運算.目的就是將h的高位值與低位值混合一下,以此增加低位值的隨機性.在上面我們知道定位數(shù)組的下標是根據(jù)hash碼的低位值來確定的.key的hash碼是通過hashCode方法來生成的,而一個糟糕的hashCode方法生成的hash碼的低位值可能會有很大的重復(fù).為了使得hash碼在數(shù)組上映射的比較均勻,擾動函數(shù)就派上用場了,把高位值的特性糅合進低位值,增加低位值的隨機性,從而使散列分布的更加松散,以此提高性能.下圖舉了個例子幫助理解.

9.hashMap的get邏輯
1).如果key為null,求null鍵
2).調(diào)用hash(key)求得key的hash值,然后調(diào)用indexFor(hash)求得hash值對應(yīng)的table的索引位置,然后遍歷索引位置的鏈表,如果存在key,則把key對應(yīng)的Entry返回,否則返回null
**注意:**這里經(jīng)常會有人問:如果兩個key的hashcode一樣,那么會怎么取.其實只要記住,相同的hashcode的entry會放在同一個位置,這個位置可能會有多個entry形成鏈表,每個entry存儲key,value值,所以再用key找到對應(yīng)的entry,就能拿到真正要的value了.
其實,在整個get方法中,key是被用了兩次,一次是用來計算hash值,為了找到bucket位置(哪一行(每行有多個value)),另外一次是找到某行后,用在尋找具體的某一個entry,從而拿到真正的value.

10.為什么hashMap是線程不安全的
一句話解釋:當(dāng)多線程的情況下,擴容過程可能產(chǎn)生條件競爭(race condition),可能會帶來循環(huán)鏈表,導(dǎo)致死循環(huán)致使線程掛掉.
慢慢解釋:如果兩個線程都發(fā)現(xiàn)HashMap需要重新調(diào)整大小了,它們會同時試著調(diào)整大小.在調(diào)整大小的過程中,存儲在鏈表中的元素的次序會反過來,因為移動到新的bucket位置的時候,HashMap并不會將元素放在鏈表的尾部,而是放在頭部,這是為了避免尾部遍歷(tail traversing).如果條件競爭發(fā)生了,就可能存在鏈表末尾的元素的next指針指向了鏈表頭,循環(huán)鏈表就出現(xiàn)了.按道理,HashMap是不存在循環(huán)鏈表的,當(dāng)我們調(diào)用get()這個鏈表中不存在的元素的時候,那么就死循環(huán)了.
注:hashTable是線程安全的,但它并未使用分段鎖,而是鎖住整個數(shù)組,高并發(fā)環(huán)境下效率非常的低,會導(dǎo)致大量線程等待.因此并發(fā)環(huán)境下,建議使用Java.util.concurrent包中的ConcurrentHashMap以保證線程安全.

最后附帶成員變量源碼的簡單解釋(可自己參照源碼對比,效果更好)

/*** The default initial capacity - MUST be a power of two.* 默認初始容量(16)*/static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16/*** The maximum capacity, used if a higher value is implicitly specified* by either of the constructors with arguments.* MUST be a power of two <= 1<<30.* 默認最大容量*/static final int MAXIMUM_CAPACITY = 1 << 30;/*** The load factor used when none specified in constructor.* 默認加載因子, 指哈希表可以達到多滿的尺度*/static final float DEFAULT_LOAD_FACTOR = 0.75f;/*** An empty table instance to share when the table is not inflated.* 空的哈希表*/static final Entry<?,?>[] EMPTY_TABLE = {};/*** The table, resized as necessary. Length MUST Always be a power of two.* 實際使用的哈希表* 其實是一個Entry數(shù)組,Entry是HashMap的靜態(tài)內(nèi)部類*/transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;/*** The number of key-value mappings contained in this map.* HashMap大小, 即HashMap存儲的鍵值對數(shù)量*/transient int size;/*** The next size value at which to resize (capacity * load factor).* 鍵值對的閾值, 用于判斷是否需要擴增哈希表容量* 默認是初始容量*加載因子,也就是16*0.75=12* 當(dāng)鍵值對超過閾值,會觸發(fā)自動擴容機制*/// If table == EMPTY_TABLE then this is the initial capacity at which the// table will be created when inflated.int threshold;/*** The load factor for the hash table.* 加載因子*/final float loadFactor;/*** The number of times this HashMap has been structurally modified* Structural modifications are those that change the number of mappings in* the HashMap or otherwise modify its internal structure (e.g.,* rehash). This field is used to make iterators on Collection-views of* the HashMap fail-fast. (See ConcurrentModificationException).* 修改次數(shù), 用于fail-fast機制*/transient int modCount;/*** The default threshold of map capacity above which alternative hashing is* used for String keys. Alternative hashing reduces the incidence of* collisions due to weak hash code calculation for String keys.* <p/>* This value may be overridden by defining the system property* {@code jdk.map.althashing.threshold}. A property value of {@code 1}* forces alternative hashing to be used at all times whereas* {@code -1} value ensures that alternative hashing is never used.* 使用替代哈希的默認閥值*/static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;/*** A randomizing value associated with this instance that is applied to* hash code of keys to make hash collisions harder to find. If 0 then* alternative hashing is disabled.* 隨機的哈希種子, 有助于減少哈希碰撞的次數(shù)*/transient int hashSeed = 0; 創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎

總結(jié)

以上是生活随笔為你收集整理的HashMap源码解释的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

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