集合框架-HashMap&HashSet&LinkedHshMap

xiang發表於2020-07-31

一、HashMap的底層實現

HashMap底層是基於陣列和連結串列實現的。其中最重要的引數:容量和負載因子。

容量的預設大小事16,負載因子是0.75,當HashMap的size>16*0.75的時候就會發生庫容(容量和負載因子都可以自由調整)

Hashmap實現了Map介面,允許放入null元素,出了該類未實現同步外,其餘和HashTable大致相同,跟TreeMap不同,該容器不保證冤死順序,根據需要該容器可能對元素重新雜湊,元素的順序也會被重新打散,因此不同時間迭代同一個HashMap的順序可能會不同。

二、HashMap的put(key,value)方法

首先會將傳入的可以、做hash運算計算出hashCode,然後根據陣列長度取模計算出在陣列中的index下表

由於在計算機中位運算比取模運算效率高,所以HashMsap規定陣列的長度為2n。這樣用2n-1做位運算與取模效果一致,並且效率要高出許多

由於陣列的長度有限,所以難免出現不同放入key通過運算得到的index相同,這種情況可以利用連結串列來解決,HashMap會在table[index]出形成連結串列,採用頭插法將資料插入連結串列中

三、HashMap的get(key)fangfa

get和put類似,也是講傳入的可以計算出index,如果該位置上是一個連結串列就需要比那裡整個連結串列,通過key.equals(k)來找到對應的元素。

遍歷方式:

第一種

Iterator<Map.Entry<String, Integer>> entryIterator=map.entrySet().iterator();
        while(entryIterator.hasNext()){
            Map.Entry<String,Integer> next=entryIterator.next();
            System.err.println("key="+next.getKey()+"value="+next.getValue());
        }

第二種

Iterator iterator=map.keySet().iterator();
        while(iterator.hasNext()){
            String key=iterator.next();
            System.err.println("key="+key+"value="+map.get(key));
        }

第三種

map.forEach((key,value)->{
            System.err.println("key="+key+"value="+value);
        });

第一種可以把key value同時取出,第二種還得需要通過key去一次value,效率較低,第三種需要JDK1.8以上,通過外層遍歷table,內層遍歷連結串列或紅黑樹。

四、為什麼多執行緒場景下不推薦使用HashMap

在併發環境下使用HashMap容易出現死迴圈。併發場景下發生擴容,呼叫resize()方法裡的rehash()時,容易出現環形連結串列。這樣當獲取一個不存在的key時,計算出的index正好是環形連結串列的下標時就會出現死迴圈

所以,HashMap只能在單執行緒中使用,並且儘量的預設容量,儘可能的減少擴容發

在JDK1.8中對HashMap進行了優化:當hash碰撞之後寫入連結串列的長度超過閾值(預設為8),連結串列將會轉換成紅黑樹。假設hash衝突非常嚴重,一個陣列後面接了很長的連結串列,此時查詢的時間複雜度就是O(n)。如果是紅黑樹,時間複雜度就是O(logn)。大大提高了查詢的效率。多執行緒場景下推薦使用ConcurrentHashMap。

五、HashSet的底層實現

HashSet是對HashMap的簡單包裝,對HashSet的函式呼叫都會轉換成合適的HashMap方法,因此HashSet的實現非常簡單。

成員變數

首先了解下HashSet的成員變數

  private transient HashMap<E,Object> map;

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();

發現主要有兩個變數:

map:用於存放最終資料

PRESENT:是所有寫入map的value值

建構函式

public HashSet() {
        map = new HashMap<>();
    }

 public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }

 public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }

 HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }

建構函式很簡單,利用了HashMap初始化了map

add

 public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

比較關鍵的就是這個add()方法。可以看出他是將存放的物件當做了HashMap的鍵,value都是相同的PRESENT.由於HashMap的key是不能重複的,所以每當有重複的值寫入到HashSet中只能存放不重複的元素

相關文章