《java集合》--LinkedHashMap

《java集合》–LinkedHashMap

說明:此文章基於jdk1.8

參考:LRU[【Java集合源碼剖析】LinkedHashmap源碼剖析](http://blog.csdn.net/ns_code/article/details/37867985)

簡介

LinkedHashMap是HashMap的子類,與HashMap有着同樣的存儲結構,同時又維護了一個雙線鏈表結構,將所有put到LinkedHashmap的節點一一串成了一個雙向循環鏈表,因此它保留了節點插入的順序,可以使節點的輸出順序與輸入順序相同。

LinkedHashMap可以用來實現LRU算法

數據結構

image

LinkedHashMap是HashMap的子類,與HashMap有相同的數據結構,同時爲了實現按照插入順序或者訪問順序進行迭代,LinkedHashMap內部維護了一個雙向鏈表結構。

基本屬性

​ transient LinkedHashMap.Entry head; 指向LinkedHashMap中雙向鏈表的首部指針

​ transient LinkedHashMap.Entry tail;指向LinkedHashMap中雙向鏈表的尾部指針

​ final boolean accessOrder;

​ 1、true:按照訪問順序倒序排序,基於LRU算法(最少訪問算法),每次操作鏈表中的一個元素後,都將元素移除然後放到鏈表的最後,這樣保證最近訪問的放在最後,保證了最少訪問的元素最優先被遍歷訪問到。

​ 2、false:按照插入順序排序

構造器

構造器調用了父類 HashMap 的相關構造方法來構造一個底層存放的 table 數組,需要初始長度和負載因子,同時比HashMap多了一個參數accessOrder,默認是false,表示雙向鏈表按照插入順序排序。

public LinkedHashMap() {
  super();
  accessOrder = false;
}
public LinkedHashMap(Map<? extends K, ? extends V> m) {
  super();
  accessOrder = false;
  putMapEntries(m, false);
}
public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) {
  super(initialCapacity, loadFactor);
  this.accessOrder = accessOrder;
}

添加元素

LinkedHashMap沒有覆寫HashMap中的put方法,而是覆寫了newNode方法

​ 調用HashMap中的put方法(基於1.8)

public V put(K key, V value) {
  return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
  Node<K,V>[] tab; Node<K,V> p; int n, i;
  if ((tab = table) == null || (n = tab.length) == 0)
    n = (tab = resize()).length;
  if ((p = tab[i = (n - 1) & hash]) == null)
    //新增一個節點,LinkedHashMap將同時向雙向鏈表尾部添加節點
    tab[i] = newNode(hash, key, value, null);
  else {
    Node<K,V> e; K k;
    if (p.hash == hash &&
        ((k = p.key) == key || (key != null && key.equals(k))))
      e = p;
    else if (p instanceof TreeNode)
       //新增一個節點,LinkedHashMap將同時向雙向鏈表尾部添加節點
      e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
    else {
      for (int binCount = 0; ; ++binCount) {
        if ((e = p.next) == null) {
           //新增一個節點,LinkedHashMap將同時向雙向鏈表尾部添加節點
          p.next = newNode(hash, key, value, null);
          if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
            treeifyBin(tab, hash);
          break;
        }
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k))))
          break;
        p = e;
      }
    }
    if (e != null) { // existing mapping for key
      V oldValue = e.value;
      if (!onlyIfAbsent || oldValue == null)
        e.value = value;
      //如果對已存在的key進行覆蓋,則相當於訪問該key,LinkedHashMap操作雙向鏈表中的元素
      afterNodeAccess(e);
      return oldValue;
    }
  }
  ++modCount;
  if (++size > threshold)
    resize();
  afterNodeInsertion(evict);
  return null;
}

LinkedList中覆寫了newNode方法

Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
  LinkedHashMap.Entry<K,V> p = new LinkedHashMap.Entry<K,V>(hash, key, value, e);
  //將新添加的元素添加到雙向鏈表的尾部
  linkNodeLast(p);
  return p;
}
//覆寫紅黑二叉樹node
TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
  TreeNode<K,V> p = new TreeNode<K,V>(hash, key, value, next);
   //將新添加的元素添加到雙向鏈表的尾部
  linkNodeLast(p);
  return p;
}

將新添加的元素添加到雙向鏈表的尾部

private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
  //操作雙向鏈表中的指針
  LinkedHashMap.Entry<K,V> last = tail;
  tail = p;
  if (last == null)
    head = p;
  else {
    p.before = last;
    last.after = p;
  }
}

當執行了get或者put已存在的key時,如果accessOrder是true,表示按照LRU算法,將本次訪問的元素移除並添加到雙向鏈表的最後

void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMap.Entry<K,V> last;
    //判斷accessOrder
    if (accessOrder && (last = tail) != e) {
        LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, 
        b = p.before, a = p.after;
        //將操作的元素放到雙向鏈表最後
        p.after = null;
        if (b == null)
            head = a;
        else
            b.after = a;
        if (a != null)
            a.before = b;
        else
            last = b;
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
        tail = p;
        ++modCount;
    }
}

LinkedHashMap中的節點比HashMap中的Entry多了雙向鏈表中 before和after指針

static class Entry<K,V> extends HashMap.Node<K,V> {
  //雙向鏈表指針
  Entry<K,V> before, after;
  Entry(int hash, K key, V value, Node<K,V> next) {
    super(hash, key, value, next);
  }
}

刪除元素

LinkedHashMap 沒有覆寫HashMap中的remove方法,只是覆寫了remove方法的回調,來刪除雙向鏈表中的節點

void afterNodeRemoval(Node<K,V> e) { // unlink
    LinkedHashMap.Entry<K,V> p =
        (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
    p.before = p.after = null;
    if (b == null)
        head = a;
    else
        b.after = a;
    if (a == null)
        tail = b;
    else
        a.before = b;
}

獲取元素

LinkedHashMap 根據key值獲取value時,調用的是父類HashMap中的get方法,在獲取完後,判斷accessOrder是否爲true,如果爲true,將本次訪問的節點從雙向鏈表中移除,然後添加到雙向鏈表的最後,保證LRU算法迭代訪問雙向鏈表

    public V get(Object key) {
        Node<K,V> e;
        //調用父類HashMap中getNode方法
        if ((e = getNode(hash(key), key)) == null)
            return null;
            //accessOrder若是true,按照LRU算法訪問雙向鏈表
        if (accessOrder)
            afterNodeAccess(e);
        return e.value;
    }

    public V getOrDefault(Object key, V defaultValue) {
       Node<K,V> e;
       if ((e = getNode(hash(key), key)) == null)
           return defaultValue;
       if (accessOrder)
           afterNodeAccess(e);
       return e.value;
   }

遍歷LinkedHashMap

遍歷LinkedHashMap是訪問的雙向鏈表,保證了順序性

public void forEach(BiConsumer<? super K, ? super V> action) {
  if (action == null)
    throw new NullPointerException();
  int mc = modCount;
  for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
    action.accept(e.key, e.value);
  if (modCount != mc)
    throw new ConcurrentModificationException();
}

迭代器操作是LinkedHashMap中的雙向鏈表

 final class LinkedKeyIterator extends LinkedHashIterator
   implements Iterator<K> {
   public final K next() { return nextNode().getKey(); }
 }

final class LinkedValueIterator extends LinkedHashIterator
  implements Iterator<V> {
  public final V next() { return nextNode().value; }
}

final class LinkedEntryIterator extends LinkedHashIterator
  implements Iterator<Map.Entry<K,V>> {
  public final Map.Entry<K,V> next() { return nextNode(); }
}
abstract class LinkedHashIterator {
  LinkedHashMap.Entry<K,V> next;
  LinkedHashMap.Entry<K,V> current;
  int expectedModCount;

  LinkedHashIterator() {
    next = head;
    expectedModCount = modCount;
    current = null;
  }

  public final boolean hasNext() {
    return next != null;
  }

  final LinkedHashMap.Entry<K,V> nextNode() {
    LinkedHashMap.Entry<K,V> e = next;
    if (modCount != expectedModCount)
      throw new ConcurrentModificationException();
    if (e == null)
      throw new NoSuchElementException();
    current = e;
    next = e.after;
    return e;
  }

  public final void remove() {
    Node<K,V> p = current;
    if (p == null)
      throw new IllegalStateException();
    if (modCount != expectedModCount)
      throw new ConcurrentModificationException();
    current = null;
    K key = p.key;
    removeNode(hash(key), key, null, false, false);
    expectedModCount = modCount;
  }
}

總結

  • LinkedHashMap是HashMap和LinkedList兩個集合類的存儲結構的結合。在LinkedHashMapMap中,所有put進來的Entry都跟HashMap一樣保存,但它又額外定義了一個雙向鏈表,每次put進來Entry,除了將其保存到對哈希表中對應的位置上外,還要將其插入到雙向循環鏈表的尾部。

  • LinkedHashMap由於繼承自HashMap,因此它具有HashMap的所有特性,同樣允許key和value爲null

  • LinkedHashMap不是線程安全的

  • accessOrder爲false時按照節點的插入順序排序;爲true按照訪問順序倒序排序LRU算法。

LinkedHashSet

LinkedHashSet 是對LinkedHashMap的簡單封裝,對LinkedHashSet的函數調用都會轉換成合適的LinkedHashMap方法。

public class LinkedHashSet<E>
    extends HashSet<E>
    implements Set<E>, Cloneable, java.io.Serializable {
    ......
    // LinkedHashSet裏面有一個LinkedHashMap
    public LinkedHashSet(int initialCapacity, float loadFactor) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }
    ......
    public boolean add(E e) {//簡單的方法轉換
        return map.put(e, PRESENT)==null;
    }
    ......
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章