map是廣義集合的一部分。
我是李福春,我在準備面試,今天咱們來回答:
HashTable,HashMap,TreeMap的區別?
共同點:都是Map的子類或者間接子類,以鍵值對的形式存儲和操做數據。
區別以下表:
java
項目 | 線程安全 | 是否支持null鍵值 | 使用場景 |
---|---|---|---|
HashTable | 是 | 不支持 | java早期hash實現,同步開銷大不推薦被使用 |
HashMap | 否 | 支持 | 大部分場景的首選put,get時間複雜度是常數級別 |
TreeMap | 否 | 不支持 | 基於紅黑樹提供順序訪問的map,傳入比較器來決定順序,get,put,remove操做時間複雜度log(n) |
下面分析一下面試官可能根據上面的問題進行一些擴展的點。
面試
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)); } }
數據結構: Node[] table , 首先是一個數組,數組的元素是一個鏈表;數組
以下圖: 數組叫作桶,數組的單個元素中的鏈表叫作bin;
安全
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值相同,則在該鏈表上繼續擴展。
若是鏈表的大小超過閾值,則鏈表會被樹化。
服務器
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值計算,擴容,樹化問題。
性能
原創不易,轉載請註明出處,讓咱們互通有無,共同進步,歡迎溝通交流。