HashMap是非常重要的數據結構,並且大部分面試都會問到,優秀的java程序員應當要對HashMap進行深入的瞭解,今天我們就來剖析一下它。
目錄
- HashMap簡介
- 成員變量
- get和put的流程
- hashMap相關的面試題
- 總結
一.簡介
首先,HashMap是一個無序key,value集合,它的底層存儲是由數組加鏈表和紅黑樹結構組成的的。在進行添加,刪除和查找時,效率非常高,如果不考慮哈希碰撞,一次定位就能完成操作,時間複雜度爲O(1)。
下面是一個默認長度的hashMap。
二.hashMap的成員變量
1.初始大小
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
這個是值是數組長度,也就是數組的初始最大行數,當然如果碰撞比較多,也有可能hashMap存了200個值,但是容量還是16。當然這不是理想狀態。
2.最大值
static final int MAXIMUM_CAPACITY = 1 << 30;
這個沒有什麼好說的,hashMap最大容量。
3.增長因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
增長因子,就是如果數組被佔用的大小超過當前大小的75%時,就進行擴容,按照當前容量二倍創建一個新的Entry,把舊Entry裏的值重新hash計算,放到新的Entry裏。
4.變樹閾值
static final int TREEIFY_THRESHOLD = 8;
jdk8對hashMap做的優化,當鏈表長度大於8時,鏈表轉換成紅黑樹,因爲紅黑樹的查詢效率是O(logn),
而鏈表的查詢效率是O(n)
5.變鏈閾值
static final int UNTREEIFY_THRESHOLD = 6;
當紅黑樹總元素數小於6時,再轉換成鏈表。因爲紅黑樹元素太少時,效率比較低,所以轉換成鏈表
三.HashMap的構造器
HashMap 提供了四個構造器,下面來一一看下。
1.HashMap()
使用默認的構造函數,構造一個初始容量爲16,增長因子爲0.75的空HashMap;
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
2.HashMap(int initialCapacity, float loadFactor)
第一個參數是初始大小,第二個參數是增長因子,通過這兩個參數來構造一個空的HashMap。
public HashMap(int initialCapacity, float loadFactor) {
//判斷容量是否小於0
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
//判斷容量是否超過最大值
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//判斷加載因子
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
//計算下一次resize的閾值
this.threshold = tableSizeFor(initialCapacity);
}
3.HashMap(int initialCapacity)
一個參數的構造,通過初始增長因子構造一個空的HashMap,調用
HashMap(int initialCapacity, float loadFactor)構造方法。
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
4.HashMap(Map<? extends K, ? extends V> m)
根據已有的Map接口創建一個元素相同的hashMap,使用默認的初始容量和增長因子。
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
四.get和put方法
1.put方法
在HashMap插入值時,會判斷是否已經初始化,如果沒有初始化,就會創建一個長度爲16的Entry,設置增長因子,默認爲0.75。
然後判斷key是否是Null,如果是Null則放到0處,如果不是,則對其key進行hash,實際操作是高位與低位混淆後做求餘操作,然後,如果hash求出來的值與數組上的key值進行匹配,如果沒有匹配上,則放到對應的數組bucket位置;如果與之前可以匹配上了(即爲hash碰撞),則一一對比鏈表上的Key值是否相同,如果匹配上了則對當前位置的值進行修改,如果沒有匹配的key,則放到該鏈表的尾部。
2.get方法
在hashMap進行get操作時,先判斷key是否是null,如果是null則,直接取0處的bucket值,
如果不是則對key的hash值和數組上的每一個bucket的key的hash值對比沒如果相同,則循環鏈表並對比key值是否相同,如果都相同,則返回該位置的value,如果沒有相同的,則返回空。
五.HashMap相關的面試題
1.hashMap和hashtable區別
hashMap和Hashtable的內部結構基本一致。
1)hashMap支持null 作爲key,但hashtable不支持;
2)HashMap是非線程安全的數據結構,所以相對來說,存取速度比較快;而Hashtable是線程安全的,因爲hashtable的get、put等方法上添加了synchrounized的參數,對大部分方法上了排他鎖,也就是說,在一個線程訪問時其他線程都要等待。
但是如果數據量增大以後,HashTable的效率下降的比較快,所以如果你需要保證線程安全又要兼顧效率的話,從jdk1.6版本開始,java給提供了ConcurrentHashMap,它相對於hashtable的性能,做了優化,
它提出了分段鎖機制, 它默認分8個段,操作開始時,先判斷在那段內,然後操作時只鎖定該段的數據,這樣的話,只有訪問同一個段的線程纔回排隊等待,訪問不同段的線程之間互不衝突,這樣來說,相對於hashtable對多線程的操作的效率提升了至少8倍,多線程併發操作效率得到了極大的提高。
2.Map<String,Object> map = new HashMap<>(20),問,該HashMap擴容了幾次?
答:擴容了0次,因爲它調用了HashMap的初始化方法,直接創建了一個長度爲32的HashMap,而不是一個一個添加值,添加了20次,所以它只擴容了1次。
3.Map<String,Object> map = new HashMap<>(28),問,這條語句java虛擬機做了什麼?
答:該語句創建了一個初始容量爲64的空格的HashMap,爲什麼是64而不是32呢,因爲它沒有傳加載因子,所以他的加載因子是0.75,28/32=0.875 ,已超過了0.75的閾值,所以自動擴容爲當前的二倍,創建了一個長度爲64的hashMap。
總結
今天對HashMap的實現原理進行了講解,希望本篇文章對大家的學習有所幫助,同時也歡迎評論指正,謝謝支持!