JDK1.8 Map之HashMap

Q1 HashMap是通過什麼方式實現的? - >數組Entry的鏈表

Q2 HashMap爲什麼不安全?怎樣實現安全?

多線程情況下如果兩個線程同時放的哈希(鍵)相同,則會發生碰撞,出現覆蓋的情況。

避免需要這樣實例:建議使用ConcurrentHashMap

Q3兩個鍵的hashcode相同,你如何獲取值對象?如何把對象?

3.1當我們調用get()方法方法,HashMap中會使用鍵對象的哈希碼找到桶位置,找到位置之後,會調用keys.equals()方法去找到鏈表鏈表中正確的節點,最終找到要找的值對象。

3.2把時候先查找鍵的哈希值,如果哈希相同,那麼則找到該對象的鏈表,放在鏈頭的位置。

Q4調整HashMap的大小存在什麼問題嗎?
當多線程的情況下,可能產生條件競爭。

當重新調整HashMap的大小的時候,存在條件競爭,因爲如果兩個線程都發現HashMap中需要重新調整大小了,它們會同時試着調整大小。在調整大小的過程中,存儲在鏈表中的元素的次序會反過來,因爲移動到新的桶位置的時候,HashMap並不會將元素放在鏈表的尾部,而是放在頭部,這是爲了避免尾部遍歷(tail traversing)。如果條件競爭發生了,那麼就死循環了。這個時候,你可以質問面試官,爲什麼這麼奇怪,要在多線程的環境下使用HashMap的呢?:)

Q5爲什麼字符串,Interger這樣的包裝類適合作爲鍵?自定義對象可以作爲鍵嗎?

字符串是不可變的,也是最後的,而且已經重寫了equals()方法和hashCode()方法方法。

自定義對象可以作爲鍵,只要是插入之後是不可變的,並重寫了等於和hashCode()方法方法。

Q6 HashMap複雜度 - >理想O (N / Buckets),N就是以數組中沒有發生碰撞的元素

要保證O(N),需要儘量避免出現重複的的hashCode(鍵),因爲出現重複的回在獲得/放的時候需要進行二次等於比較計算出在鏈表的位置;以及鏈表的長度都會影響時間複雜度

如果某個桶中的鏈表記錄過大的話(當前是TREEIFY_THRESHOLD = 8),就會把這個鏈動態變成紅黑二叉樹(TreeMap),使查詢最差複雜度由O(N)變成了O( logn)時間。

Q7重新調整HashMap大小存在什麼問題嗎?
 

可能產生條件競爭,多線程的情況下會有多個線程同時修改地圖的大小,導致出現死循環。

Q8 HashMap&TreeMap&LinkedHashMap使用場景? 

一般情況下,使用最多的是HashMap.HashMap 
:在Map中插入,刪除和定位元素時; 
TreeMap:在需要按自然順序或自定義順序遍歷鍵的情況下; 

LinkedHashMap的:在需要輸出的順序和輸入的順序相同的情況下。

Q9 HashMap和HashTable有什麼區別? 
①,HashMap是線程不安全的,HashTable是線程安全的; 
②,由於線程安全,所以HashTable的效率比不上HashMap; 
③,HashMap最多隻允許一條記錄的鍵爲null ,允許多條記錄的值爲null,而HashTable不允許; 
④,HashTable爲11,前者擴容時,擴大兩倍,後者擴大兩倍+1; 
⑤,HashMap需要重新計算哈希值,而HashTable直接使用對象的hashCode

1地圖是一個接口。

//map是一個接口
public interface Map<K,V> {}
//定義抽象的類實現Map接口
public abstract class AbstractMap<K,V> implements Map<K,V>{}
//AbstractHashedMap繼承抽象map
public class AbstractHashedMap extends AbstractMap implements IterableMap{}

2可以通過entrySet獲取當前數組的所有條目。

public abstract Set<Entry<K,V>> entrySet();

3 HashMap定義

3.1初始容量:默認map容量,默認爲16

3.2加載因子:爲設定臨界值的維度,默認爲0.75。

3.3容量臨界值:在put後會如果當前的大小大於舊的數組大小,需要重新計劃臨界值。

3.4最大容量:1.8jdk規定最大爲2的30次方。

    protected static final int DEFAULT_CAPACITY = 16;//初始容量
    protected static final int DEFAULT_THRESHOLD = 12;//容量臨界值=初始容量*默認加載因子
    protected static final float DEFAULT_LOAD_FACTOR = 0.75F;//默認加載因子0.75
    protected static final int MAXIMUM_CAPACITY = 1073741824;//最大容量爲2的30次方
    protected static final Object NULL = new Object();
    protected transient float loadFactor;
    protected transient int size;//已經存在的個數
    protected transient AbstractHashedMap.HashEntry[] data;//數組
    protected transient int threshold;
    protected transient int modCount;//已從結構上修改 此列表的次數

4 HashMap的構造函數

JDK源碼中有3個構造函數,最後都調用AbstractHashedMap(int initialCapacity,float loadFactor)方法。

/**
* initialCapacity : 初始化容量,默認16
* loadFactor : 初始化加載因子,默認0.75
**/
protected AbstractHashedMap(int initialCapacity, float loadFactor) {
        if(initialCapacity < 1) {
            throw new IllegalArgumentException("Initial capacity must be greater than 0");
        } else if(loadFactor > 0.0F && !Float.isNaN(loadFactor)) {
            this.loadFactor = loadFactor;
            // 判斷初始化容量是否大於hashMap定義大小(2的30次方),如果大於設置初始大小爲2的30次方,如果小於,設置大小爲2(1<<=1)           
            initialCapacity = this.calculateNewCapacity(initialCapacity);
            //初始化臨界值:(int)((float)initialCapacity* loadFactor)
            this.threshold = this.calculateThreshold(initialCapacity, loadFactor);
            //初始化數組大小,通過initialCapacity
            this.data = new AbstractHashedMap.HashEntry[initialCapacity];
            this.init();
        } else {
            throw new IllegalArgumentException("Load factor must be greater than 0");
        }
    }

面試問題:HashMap的默認大小是多大?

HashMap的默認大小爲16

/**
* 如果傳遞參數的map的大小大於16,那麼會創建size*2的容量。
**/
protected AbstractHashedMap(Map map) {
      this(Math.max(2 * map.size(), 16), 0.75F);
      this.putAll(map);
}

面試問題:爲什麼每次創建都是爲2的冪次?

判斷初始化容量是否大於hashMap定義大小(2的30次方),如果大於設置初始大小爲2的30次方,如果小於,設置大小爲2(1 << = 1);通過左移位賦值運算符進行計算<< 1

protected int calculateNewCapacity(int proposedCapacity) {
        int newCapacity = 1;
        if(proposedCapacity > 1073741824) {
            newCapacity = 1073741824;
        } else {
            while(true) {
                if(newCapacity >= proposedCapacity) {
                    if(newCapacity > 1073741824) {
                        newCapacity = 1073741824;
                    }
                    break;
                }
                newCapacity <<= 1;//左移位賦值運算符
            }
        }
        return newCapacity;
}

5放(Object key,Object value)方法

/**
* 邏輯步驟
* 1 判斷key是否爲空,支持NULL關鍵字
* 2 取key的hashCode值
* 3 通過key的hashCode值與當前map的長度進行&與運算,得到數組下標
(&運算:將兩端轉化爲二進制,進行對應位比較,如果相等返回1,不想等返回0)
* 4 循環當前map是否存在key,如果存在比較==/equals是否相等,
* 4.1 如果相等更新key的值,返回舊的值
* 5 不存在則創建HashEntry對象,
* 6 將HashEntry存放到數組中指定下標位置
**/
public Object put(Object key, Object value) { 
    // 判斷key是否爲空,如果爲空,返回NULL 
    key = this.convertKey(key);      
    //得到key的hash()值 
    int hashCode = this.hash(key); 
    //得到數組下標:hashCode & this.data.length- 1; 
    int index = this.hashIndex(hashCode, this.data.length); 
    // 循環當前數組,判斷是否選在當前key 
    for(AbstractHashedMap.HashEntry entry = this.data[index]; entry != null; entry = entry.next) { 
        // 如果存在key進行比較,如果完全對象相等,值相等則替換key值,返回舊的值             
        if(entry.hashCode == hashCode && this.isEqualKey(key, entry.key)) {                 
            Object oldValue = entry.getValue();                 
            this.updateEntry(entry, value);                 
            return oldValue;             
        }         
    }         
    this.addMapping(index, hashCode, key, value);        
    return null; 
}
protected void addMapping(int hashIndex, int hashCode, Object key, Object value) {
   ++this.modCount;
   //創建HashEntry對象,
   AbstractHashedMap.HashEntry entry = this.createEntry(this.data[hashIndex], hashCode, key, value);
   this.addEntry(entry, hashIndex);
   //將enrty存放到指定數組下標位置
   -->this.data[hashIndex] = entry;
   //設置房前的map大小+1
   ++this.size;
   //檢查map容量
   this.checkCapacity();
}

   
/**
* 檢查容量
* 邏輯步驟:
* 1 判斷當前map大小大於等於容量界定值(默認12) && map大小*2後小於最大容量(2的32次方)
* 2 重新進行容量的初始化
* 3 實例化新的數組,並將old數組集合循環賦值給新的數組集合
* 4 根據新的容量*加載因子計算臨界值
**/
protected void checkCapacity() {
    if(this.size >= this.threshold) {
        int newCapacity = this.data.length * 2;
        if(newCapacity <= 1073741824) {
            this.ensureCapacity(newCapacity);
        }
    }
}

protected void ensureCapacity(int newCapacity) {
    int oldCapacity = this.data.length;
    if(newCapacity > oldCapacity) {
        if(this.size == 0) {
            this.threshold = this.calculateThreshold(newCapacity, this.loadFactor);
            this.data = new AbstractHashedMap.HashEntry[newCapacity];
        } else {
            AbstractHashedMap.HashEntry[] oldEntries = this.data;
            AbstractHashedMap.HashEntry[] newEntries = new AbstractHashedMap.HashEntry[newCapacity];
            ++this.modCount;

            for(int i = oldCapacity - 1; i >= 0; --i) {
                AbstractHashedMap.HashEntry entry = oldEntries[i];
                if(entry != null) {
                    oldEntries[i] = null;
                    AbstractHashedMap.HashEntry next;
                    do {
                        next = entry.next;
                        int index = this.hashIndex(entry.hashCode, newCapacity);
                        entry.next = newEntries[index];
                        newEntries[index] = entry;
                        entry = next;
                    } while(next != null);
                }
            }
            //計算新的加載因子並複製
            this.threshold = this.calculateThreshold(newCapacity, this.loadFactor);
            //-->(int)((float)newCapacity * loadFactor);
            this.data = newEntries;
        }

    }
}

 

6 get(String key)

 

/*** 1 檢查key是否爲空
* 2 獲取key的HashCode值
* 3 循環遍歷得到HashEntry,返回entry的值
**/
public Object get(Object key) {
    key = this.convertKey(key);    
    int hashCode = this.hash(key);     
    for(AbstractHashedMap.HashEntry entry = this.data[this.hashIndex(hashCode, this.data.length)]; entry != null; entry = entry.next) { 
        if(entry.hashCode == hashCode && this.isEqualKey(key, entry.key)) {              
            return entry.getValue();         
        }     
    }     
    return null; 
}

 


 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章