面試刷題9:HashTable HashMap TreeMap的區別?

image.png




map是廣義集合的一部分。




我是李福春,我在準備面試,今天咱們來回答:




HashTable,HashMap,TreeMap的區別?




共同點:都是Map的子類或者間接子類,以鍵值對的形式存儲和操做數據。


區別以下表:


java

項目 線程安全 是否支持null鍵值 使用場景
HashTable 不支持 java早期hash實現,同步開銷大不推薦被使用
HashMap 支持 大部分場景的首選put,get時間複雜度是常數級別
TreeMap 不支持 基於紅黑樹提供順序訪問的map,傳入比較器來決定順序,get,put,remove操做時間複雜度log(n)





下面分析一下面試官可能根據上面的問題進行一些擴展的點。
面試

Map的類層級


image.png






HashTable是java早期的hash實現,實現了Dictionary接口;
TreeMap是根據比較器來決定元素的順序;
LinkedHashMap 按照插入的順序來遍歷。下面的代碼是一個不常常使用的資源自動釋放的例子。




算法

package org.example.mianshi;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 不常使用的資源被釋放掉
 *
 */
public class App 
{
    public static void main( String[] args )
    {

        LinkedHashMap<String,String> linkedHashMap = new LinkedHashMap<String,String>(){

            @Override
            protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
                return size()>3;
            }
        };

        linkedHashMap.put("a","aaa");
        linkedHashMap.put("b","bbb");
        linkedHashMap.put("c","ccc");


        linkedHashMap.forEach((k,v)->System.out.println(k+" = " + v));


        System.out.println(linkedHashMap.get("a"));
        System.out.println(linkedHashMap.get("b"));
        System.out.println(linkedHashMap.get("c"));

        linkedHashMap.forEach((k,v)->System.out.println(k+" = " + v));

        linkedHashMap.put("d","ddd");

        System.out.println("=========");

        linkedHashMap.forEach((k,v)->System.out.println(k+" = " + v));


    }
}








HashMap的源碼分析




數據結構:   Node[] table , 首先是一個數組,數組的元素是一個鏈表;數組

以下圖: 數組叫作桶,數組的單個元素中的鏈表叫作bin; 






image.png


安全

put操做涉及的關鍵源碼以下:


final V putVal(int hash, K key, V value, boolean onlyIfAbent,boolean evit) {
    Node<K,V>[] tab; Node<K,V> p; int , i;
    if ((tab = table) == null || (n = tab.length) = 0)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == ull)
        tab[i] = newNode(hash, key, value, nll);
    else {
        // ...
        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for first 
           treeifyBin(tab, hash);
        //  ... 
     }
}




路由規則:


key計算hash值, hash值%數組長度= 數組的索引;  經過索引找到對應的數組元素,若是hash值相同,則在該鏈表上繼續擴展。


若是鏈表的大小超過閾值,則鏈表會被樹化。




服務器

hashMap的hash值的計算:

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




這麼設置算法是爲了下降hash碰撞的機率,數據計算出來的hash值差別通常是在高位,上面的代碼是忽略容量以上的高位(進行了位移)。


數據結構

擴容邏輯

final Node<K,V>[] resize() {
    // ...
    else if ((newCap = oldCap << 1) < MAXIMUM_CAPACIY &&
                oldCap >= DEFAULT_INITIAL_CAPAITY)
        newThr = oldThr << 1; // double there
       // ... 
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    else {  
        // zero initial threshold signifies using defaultsfults
        newCap = DEFAULT_INITIAL_CAPAITY;
        newThr = (int)(DEFAULT_LOAD_ATOR* DEFAULT_INITIAL_CAPACITY;
    }
    if (newThr ==0) {
        float ft = (float)newCap * loadFator;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);
    }
    threshold = neThr;
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newap];
    table = n;
    // 移動到新的數組結構e數組結構 
   }




若是沒指定容量和負載因子,按照默認的負載因子和容量初始化;
門閥值=容量 * 負載因子,門閥值按照倍數擴容
擴容後,會把老的數組中的元素複製到新的數組,這是擴容開銷的主要來源;


ide

樹化


final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        resize();
    else if ((e = tab[index = (n - 1) & hash]) != null) {
        //樹化改造邏輯
    }
}




哈希碰撞:元素在放入hashmap的過程當中,若是一個對象hash衝突,妒被放置到同一個桶裏面,會造成一個鏈表,鏈表的存取耗費性能,沒法達到常數級別的時間複雜度;若是大量的hash衝突,則會造成一個長鏈表,若是客戶端跟這些數據交互頻繁,則會佔用大量的cpu,致使服務器宕機拒絕服務。




樹化的目的是:爲了安全,減小hash衝突;




源碼分析

小結

先從線程安全,是否容許null鍵值,使用場景方面說出來HashTable,HashMap,TreeMap的區別。


而後擴展到了Map的類層級,分析了面試官喜歡問的hashmap的數據結構,hash值計算,擴容,樹化問題。




性能

image.png

原創不易,轉載請註明出處,讓咱們互通有無,共同進步,歡迎溝通交流。
相關文章
相關標籤/搜索