1. 程式人生 > >春秋招java後端方向技能全面突破-基礎篇05

春秋招java後端方向技能全面突破-基礎篇05

HashMap

首先要說的是HashMap 是一個散列表,它儲存的內容是鍵值對(key-value)對映。同時HashMap 的實現不是同步的,這也就是意味著它不是執行緒安全的。它的key、value都可以為null。此外,HashMap中的對映也不是有序的(由於沒有實現了NavigableMap介面,對於NavigableMap來說,它是一個可導航的鍵-值對集合)。

對於Hashmap來說,它是存在兩個引數會影響其效能,“初始容量”(這裡得容量是指雜湊表中桶的數量,初始容量是表在建立時的容量)和“載入因子”(雜湊表在達到自動增加之前所能達到最大的尺度),當雜湊表的數目超過當前容量和載入因子乘積的時候,就要對其進行重建表結構,即rehash,使雜湊表變成大約兩倍的桶數。

整體的一個實現結構如下:(!!這裡要特別注意它的連結串列為單向連結串列

HashMap是通過"拉鍊法"實現的雜湊表。它包括幾個重要的成員變數:table, size, threshold, loadFactor, modCount。
  table是一個Entry[]陣列型別,而Entry實際上就是一個單向連結串列。雜湊表的"key-value鍵值對"都是儲存在Entry陣列中的。 
  size是HashMap的大小,它是HashMap儲存的鍵值對的數量。 
  threshold是HashMap的閾值,用於判斷是否需要調整HashMap的容量。threshold的值="容量*載入因子",當HashMap中儲存資料的數量達到threshold時,就需要將HashMap的容量加倍。
  loadFactor就是載入因子。 
  modCount是用來實現fail-fast機制的。

這裡提一下資料節點Entry的資料結構:

static class Entry<K,V> implements Map.Entry<K,V> {
    final K key;
    V value;
    // 指向下一個節點
    Entry<K,V> next;
    final int hash;

    // 建構函式。
    // 輸入引數包括"雜湊值(h)", "鍵(k)", "值(v)", "下一節點(n)"
    Entry(int h, K k, V v, Entry<K,V> n) {
        value = v;
        next = n;
        key = k;
        hash = h;
    }

    public final K getKey() {
        return key;
    }

    public final V getValue() {
        return value;
    }

    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }

    // 判斷兩個Entry是否相等
    // 若兩個Entry的“key”和“value”都相等,則返回true。
    // 否則,返回false
    public final boolean equals(Object o) {
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry e = (Map.Entry)o;
        Object k1 = getKey();
        Object k2 = e.getKey();
        if (k1 == k2 || (k1 != null && k1.equals(k2))) {
            Object v1 = getValue();
            Object v2 = e.getValue();
            if (v1 == v2 || (v1 != null && v1.equals(v2)))
                return true;
        }
        return false;
    }

    // 實現hashCode()
    public final int hashCode() {
        return (key==null   ? 0 : key.hashCode()) ^
               (value==null ? 0 : value.hashCode());
    }

    public final String toString() {
        return getKey() + "=" + getValue();
    }

    
}

而對於拉鍊發解決Hash衝突,則為以下步驟:

  1. 得到一個 key
  2. 計算 key 的 hashValue
  3. 根據 hashValue 值定位到 data[hashValue] 。( data[hashValue] 是一條連結串列)
  4. 若 data[hashValue] 為空則直接插入
  5. 不然則新增到連結串列末尾

再說說HashMap的方法,clear()(清空HashMap。它是通過將所有的元素設為null來實現的),containsKey()(判斷HashMap是否包含key,containsKey() 首先通過getEntry(key)獲取key對應的Entry,然後判斷該Entry是否為null,這裡需要強調的是:HashMap將“key為null”的元素都放在table的位置0處,即table[0]中;“key不為null”的放在table的其餘位置

這裡提兩個方法:

get() 的作用是獲取key對應的value:

public V get(Object key) {
    if (key == null)
        return getForNullKey();
    // 獲取key的hash值
    int hash = hash(key.hashCode());
    // 在“該hash值對應的連結串列”上查詢“鍵值等於key”的元素
    for (Entry<K,V> e = table[indexFor(hash, table.length)];
         e != null;
         e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
            return e.value;
    }
    return null;
}

put() 的作用是對外提供介面,讓HashMap物件可以通過put()將“key-value”新增到HashMap中:

public V put(K key, V value) {
    // 若“key為null”,則將該鍵值對新增到table[0]中。
    if (key == null)
        return putForNullKey(value);
    // 若“key不為null”,則計算該key的雜湊值,然後將其新增到該雜湊值對應的連結串列中。
    int hash = hash(key.hashCode());
    int i = indexFor(hash, table.length);
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        // 若“該key”對應的鍵值對已經存在,則用新的value取代舊的value。然後退出!
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }

    // 若“該key”對應的鍵值對不存在,則將“key-value”新增到table中
    modCount++;
    addEntry(hash, key, value, i);
    return null;
}

最後提一下HashMap的遍歷方式:

1 遍歷HashMap的鍵值對

第一步:根據entrySet()獲取HashMap的“鍵值對”的Set集合。
第二步:通過Iterator迭代器遍歷“第一步”得到的集合。

2 遍歷HashMap的鍵

第一步:根據keySet()獲取HashMap的“鍵”的Set集合。
第二步:通過Iterator迭代器遍歷“第一步”得到的集合。

3 遍歷HashMap的值

第一步:根據value()獲取HashMap的“值”的集合。
第二步:通過Iterator迭代器遍歷“第一步”得到的集合。

TreeMap

首先我們應該知道TreeMap 是一個有序的key-value集合,它是通過紅黑樹(該對映根據其鍵的自然順序進行排序,或者根據建立對映時提供的 Comparator 進行排序,具體取決於使用的構造方法,具體紅黑樹內容我會在後續的資料結構中詳細講的)實現的,對於我們提及的R-B Tree,它包含幾個重要的成員變數: root, size, comparator。
  root 是紅黑數的根節點。它是Entry型別,Entry是紅黑數的節點,它包含了紅黑數的6個基本組成成分:key(鍵)、value(值)、left(左孩子)、right(右孩子)、parent(父節點)、color(顏色:Red-false   Black-ture)。Entry節點根據key進行排序,Entry節點包含的內容為value,紅黑數排序時,根據Entry中的key進行排序;Entry中的key比較大小是根據比較器comparator來進行判斷的,size是紅黑數中節點的個數。(由於後端面試中TreeMap提及的不是特別多,手撕紅黑AVL之類就有點過分了,所以這裡就不多提及了)

HashSet

首先明確一點,不管是HashSet還是TreeSet都是繼承自Map的,比如繼承自HashMap的HashSet具有了非同步(!!如果多個執行緒同時訪問一個雜湊 set,而其中至少一個執行緒修改了該 set,那麼它必須 保持外部同步。這通常是通過對自然封裝該 set 的物件執行同步操作來完成的。如果不存在這樣的物件,則應該使用 Collections.synchronizedSet 方法來“包裝” set。最好在建立時完成這一操作,以防止對該 set 進行意外的不同步訪問)的特點,且可以為null值,同時Set還具有無序的特點。

TreeSet(非執行緒安全)

TreeSet 是一個有序的集合,它的作用是提供有序的Set集合。它繼承於AbstractSet抽象類,實現了NavigableSet<E>, Cloneable, java.io.Serializable介面。(!!這裡注意一下,TreeSet中的元素支援2種排序方式:自然排序 或者 根據建立TreeSet 時提供的 Comparator 進行排序。這取決於使用的構造方法。)