目錄
5.1擴容方法inflateTable(threshold)
5.3indexFor(int h, int length)
5.4擴容addEntry(hash, key, value, i);
1.散列表簡介
1)什麼是哈希表:
由數組和鏈表組合成的一種根據關鍵碼值(Key value)而直接進行訪問的數據結構
2)特點(優點):
結合了數組和鏈表的優點
數組的特點是:尋址容易,插入和刪除困難;
而鏈表的特點是:尋址困難,插入和刪除容易。
3)結構:
4)缺點:
數組創建後難於擴展
5)散列法
元素特徵轉變爲數組下標的方法就是散列法。三種比較常用的:
1,除法散列法
2,平方散列法
3,斐波那契(Fibonacci)散列法
上圖的案例除法散列法 :
index = value % 16 (求模)
例如:12,28,108,140除16的模都是12所以存儲在數組12的位置
6)散列衝突:
如上圖的12,28,108,140除16的模都是12所以存儲在數組12的位置這就是哈西衝突
2.HashMap的數據結構
維護Hashmap的內部的是一個Entry<K,V>,數據結構是一個單鏈表。源碼如下:
static class Entry<K,V> implements Map.Entry<K,V> { |
單項鍊表
操作方式:
頭插法(不用遍歷鏈表直接在頭插入)
尾差發(需要遍歷鏈表找到最後一個指針在插入)
3主要參數
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //默認容量16 static final int MAXIMUM_CAPACITY = 1 << 30; //最大容量2^30; static final float DEFAULT_LOAD_FACTOR = 0.75f; //負載因子,擴容的依據 static final Entry<?,?>[] EMPTY_TABLE = {}; transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE; transient int size; //map大小 final float loadFactor; int threshold; // 第一次爲初始容量之後爲總容量的75%,當size大於threshold進行擴容 transient int modCount; //修改次數 |
4.HashMap的核心構造方法
public HashMap(int initialCapacity, float loadFactor) {
|
4.1初始化容量
initialCapacity(初始化容量默認16,最大爲2^30)loadFactor(負載因子0.75f) 初始化時賦值threshold爲初始容量(之後爲總容量的75%) |
5.HashMap的Put方法
public V put(K key, V value) { //計算key hash值在table中的位置 返回h & (length-1); //因爲length的大小爲2^n所以(length-1)保證了每一位都有1,否則1&0;的結果爲0,這樣8和9返回的位置就相同了 int i = indexFor(hash, table.length); //計算桶位
|
5.1擴容方法inflateTable(threshold)
private void inflateTable(int toSize) { //確保HashMap的容量爲2的N次冪(取離初始容量最近比較大一段的2的N次冪) //創建Entry初始容量默認爲16 |
5.1.1初始容量算法
private static int roundUpToPowerOf2(int number) {
|
解釋:Integer.highestOneBit(int i)的作用
如果一個數是0, 則返回0;
如果是負數, 則返回 -2147483648:【1000,0000,0000,0000,0000,0000,0000,0000】(二進制表示的數);
如果是正數, 返回的則是跟它最靠近的比它小的2的N次方
roundUpToPowerOf2方法返回的爲Integer.highestOneBit((number - 1) << 1) |
5.2 putForNullKey(value);
private V putForNullKey(V value) { //把Null保存在table[0]位置
|
當key爲null,調用putForNullKey方法,保存null於table第一個位置中,這是HashMap允許爲null的原因
5.3indexFor(int h, int length)
static int indexFor(int h, int length) {
|
計算key hash值在table中的位置返回h & (length-1); //因爲length的大小爲2^n所以(length-1)保證了每一位都有1,否則1&0;的結果爲0,這樣8和9返回的位置就相同了
5.4擴容addEntry(hash, key, value, i);
每次擴大到原來空間的2倍
void addEntry(int hash, K key, V value, int bucketIndex) { //每次創建一個Entry<K,V>後size加1,空間不足threshold(總空間的75%),擴大長處的2倍
|
5.4.1 創建Entry
void createEntry(int hash, K key, V value, int bucketIndex) {
|
獲取bucketIndex處的Entry
將新創建的 Entry 放入 bucketIndex 索引處,並讓新的 Entry 指向原來的 Entry
size加1
解釋:
單鏈表頭插法
第一次存儲時例如 bucketIndex=1;則 Entry<K,V> e= table[bucketIndex]爲空即e爲空
table[bucketIndex] = newEntry<>(hash, key, value, e);可以理解成
table[1] = new Entry<>(hash, key,value, null);
第二次Entry<K,V> e= table[bucketIndex]爲空即e爲 table[1]的地址
4.5Put方法綜合分析圖
6.HashMap的get方法
public V get(Object key) {
|
6.2getEntry(key);
final Entry<K,V> getEntry(Object key) { //先定位到桶位,在進行鏈表的遍歷(indexFor()方法上面分析了)
|
//如果hash衝突了,key是不同的可以通過遍歷鏈表找到value
7總結
1. 維護Hashmap內部的是一個Entry,Hashmap的默認容量爲16,負載因子爲0.75,
2. Entry的默認初始容量爲16(Entry初始容量一定爲2的N次冪,Hashmap初始容量不滿足闊大到最近較大的一個2的N次冪)
3. 擴容方式當Entry已用空間>=總空間的75%,總空間擴大到原來的2倍。
4. HashMap可以存Null值,存在table[0]處
5. HashMap的容量指的是Entry<K,V>[] table的長度
6. 計算桶位的方式 h & (length-1);
計算key hash值在table中的位置返回h & (length-1); //因爲length的大小爲2^n所以(length-1)保證了每一位都有1,否則1&0;的結果爲0,這樣8和9返回的位置就相同了