HahMap的靈魂拷問

帶着問題讀源碼:
1.HashMap是線程安全的嗎?如果不是,怎麼變成線程安全?
       不是線程安全的, 可以使用concurrentHashmap  或者 使用Collections類的synchronizedMap方法包裝一下

2.怎麼解決hash衝突的?
       用鏈表

3. jdk1.7 與1.8版本的實現有什麼差異?做了哪些優化?
        jdk1.8版本引入了紅黑樹,當鏈表的長度大於8的時候會轉化爲紅黑樹,解決長鏈表效率低的問題

4. put 與get的過程是怎樣的?
   jdk1.7的過程
    put的過程:
    if(key == null){
       //調用方法,將該鍵值對保存在table的第一位置
      return putForNullKey(value);
    }
  如果不爲空,則計算該key的hash值,計算應該放在數組的哪個位置,如果該位置原本沒有值,直接存放;如果有值,查找鏈表,equals看看有沒有相同的值,如果有的話覆蓋,沒有的話插入。jdk1.7中是插入在表頭(因爲寫jdk的大佬可能認爲剛插入的被查找的概率比較高一點)
   get的過程:計算key的hash值,定位到數組的位置, equals判斷鏈表中的key是否與目標key相同,相同就返回對應的value

5.什麼時候需要擴容?怎麼實現擴容的?
        當hashmap中的元素越來越多,發生碰撞的概率就會越來越大,造成鏈越來越長,勢必會影響到hashmap的存取效率,所以當個數到達臨界值的時候就會擴容,也就是 容量*負載因子
        擴容是怎麼實現的呢?擴容實際上是一個非常耗時的過程,因爲它需要重新計算這些元素在新table數組中的位置並進行復制處理,每個元素的位置都有可能變化,因爲數組大小改變了,對每個元素重新哈希計算存儲的位置。按當前桶數組長度的2倍進行擴容
 在開發中如果我們能夠估計元素的個數,儘量初始化指定,這樣可以避免擴容,提升效率

6.列舉幾種遍歷的方法?

7.HashMap的底層數組長度爲什麼總是2的冪次方?
       不同的hash值發生碰撞的概率比較小,這樣就會使得數據在table數組中分佈較均勻,空間利用率較高,查詢速度也較快;
h&(length - 1) 就相當於對length取模,而且在速度、效率上比直接取模要快得多,即二者是等價不等效的,這是HashMap在速度和效率上的一個優化。爲什麼相當於取模呢,其實相當於按位與運算。

如默認的hashmap的length = 16, 那麼 Length-1 化爲二進制就是  0000 1111,其他數字與之按位與,那麼範圍就會在二進制 0000 0000 到

0000 1111中。也就是0 到15,這就相當與h%16的效果了,但是對於二進制的計算,明顯效率會更好

注:hashmap中沒有直接使用獲取到的hashcode,而是做了一些處理,比如右移,按位與等等,這些操作都是爲了讓擾動結果,讓得到的結果的散列性更好。

8.負載因子爲什麼是0.75,基於怎樣的考慮?
     負載因子表示的是在擴容之前,元素佔滿容量的程度是多少。例如默認的初始容量是16,那麼達到16*0.75 = 12 時就進行擴容。
     負載因子越大,表示對空間的使用越充分,但是衝突的概率也大,由於是拉鍊法來解決衝突,會導致鏈表長度增長,搜索效率降低
     負載因子越小,那麼空間的使用不充分,分佈很稀疏,造成空間的浪費。
     所以0.75是一種折中的方案

9.什麼時候會線程不安全?
    多線程同時put時,如果同時觸發了rehash操作,會導致HashMap中的鏈表中出現循環節點,進而使得後面get的時候,會死循環

10.爲什麼重寫equals 時候要重寫 hashcode 方法?
    首先在java中的有這樣的規則:兩個對像如果hashcode相同 那麼一定equals,反之不然。所以如果寫了重寫了equals,不重寫hashcode,那麼就會存在兩個對象在重寫的equals方法下相等,但是hashcode不同(因爲hashcode 是object類下計算的,每個new出來的對象都是不同的,作用是利用哈希快速尋找 判斷)
  那以上會有什麼影響呢?會造成使用到這兩個方法的集合,出現錯亂,達不到效果,例如hashset無法去重之類的,我的另外博文中講。

11.有人問既然HashMap是無序的,那爲什麼我打印出來的和我插入的順序是一樣的呢?

    首先從底層的原理來講,hashmap在put的時候是用散列的方法(也就是計算hash值來確定位置的),那肯定是無序的。

public static void main(String[] args) {
        HashMap<Integer, String> map = new HashMap();
        for (int i = 150; i > 0; i--) {
            map.put(i , "name" + i);
        }
        for (Map.Entry<Integer, String> entry : map.entrySet()) {
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }

    }
//如果key的類型是Integer ,Long等這樣的數值類型,由於 這樣類型的值計算出來的hashcode就是它本身。
// 如 Integer a =123; 那麼 a.hashcode就是爲123,那麼此時就是有序的。這完全是一種特殊情況。

//如果Key是String類型,那就不會有序了

    12.關於hashMap中的hash算法

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

 

發佈了34 篇原創文章 · 獲贊 35 · 訪問量 9154
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章