This is the 20th day of my participation in the August Text Challenge.More challenges in August

Class first

This implementation differs from HashMap in that it maintains a doubly-linked list running through all of its entries.
Copy the code
  • On the basis of hashMap, for the bin of array, additional bidirectional linked list storage structure is added.

Class declaration

public class LinkedHashMap<K.V>
    extends HashMap<K.V>
    implements Map<K.V>
Copy the code
  • Override method:

    newNode()
    replacementNode() // In hashMap, the production method used for tree degradation is used in conjunction with transferLinks in LHM
    newTreeNode()
    replacementTreeNode()
    internalWriteEntries()//IO serialization is related
    get()// and related methods
    reinitialize()//super () + first and last =null
    Copy the code
  • Three special methods that implement leaving in a parent class:

    void afterNodeAccess(Node<K,V> p) {}void afterNodeInsertion(boolean evict) {}void afterNodeRemoval(Node<K,V> p) {}Copy the code

The comments for these three methods in the hashMap are:

Callbacks to allow LinkedHashMap post-actions

Maintain the variable

  • Data structure correlation
transient LinkedHashMap.Entry<K,V> head;

transient LinkedHashMap.Entry<K,V> tail;
Copy the code

The key managed structure of the bidirectional linked list is declared: the beginning and end nodes, which conforms to the above

it maintains a doubly-linked list running through all of its entries

  • Traverse the related
/**
 * The iteration ordering method for this linked hash map: <tt>true</tt>
 * for access-order, <tt>false</tt> for insertion-order.
 *
 * @serial* /
final boolean accessOrder;
Copy the code

This variable means (from blog.csdn.net/weixin_3691…

value meaning
true The accessed elements are placed after the linked list in the order in which they were accessed
false Traverse in insertion order

Main storage structure

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); }}Copy the code

Class method

Structure specialization method
LinkNodeLast (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; }}Copy the code
  • There is nothing special in itself, except to hang the node behind the last node.

    • The method that calls this method:

      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;
      }
      Copy the code
      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;
      }	
      Copy the code
    • You can see that LinkedHashMap does not, in essence, replace the array in HashMap with a list, but maintains an additional two-way list to store data.

transferLinks
// apply src's links to dst
private void transferLinks(LinkedHashMap.Entry
       
         src, LinkedHashMap.Entry
        
          dst)
        ,v>
       ,v> {
    LinkedHashMap.Entry<K,V> b = dst.before = src.before;
    LinkedHashMap.Entry<K,V> a = dst.after = src.after;
    if (b == null)
        head = dst;
    else
        b.after = dst;
    if (a == null)
        tail = dst;
    else
        a.before = dst;
}
Copy the code
  • Assign all SRC nodes to DST
    • The method that calls the method
Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) {
    LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p;
    LinkedHashMap.Entry<K,V> t =
        new LinkedHashMap.Entry<K,V>(q.hash, q.key, q.value, next);
    transferLinks(q, t);
    return t;
}
Copy the code
TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
    LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p;
    TreeNode<K,V> t = new TreeNode<K,V>(q.hash, q.key, q.value, next);
    transferLinks(q, t);
    return t;
}
Copy the code
  • One is called when a tree node is called, and one is called when a non-tree node is called, and it’s just a swap of Pointers.
HashMap leave method
 void afterNodeAccess(Node<K,V> p) { }
 void afterNodeInsertion(boolean evict) { }
 void afterNodeRemoval(Node<K,V> p) { }
Copy the code
afterNodeAccess
  • Operations performed after a node is accessed

    • If accessOrder is true, the accessed node is placed last
void afterNodeAccess(Node<K,V> e) { // move node to last LinkedHashMap.Entry<K,V> last; 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; }}Copy the code
afterNodeInsertion
  • Operations performed after a node is inserted

    void afterNodeInsertion(boolean evict) { // possibly remove eldest LinkedHashMap.Entry<K,V> first; if (evict && (first = head) ! = null && removeEldestEntry(first)) { K key = first.key; removeNode(hash(key), key, null, false, true); } } protected boolean removeEldestEntry(Map.Entry<K,V> eldest) { return false; }Copy the code
afterNodeRemoval
 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;
 }
Copy the code
Get the relevant
  public V get(Object key) {
      Node<K,V> e;
      if ((e = getNode(hash(key), key)) == null)
          return null;
      if (accessOrder)
          afterNodeAccess(e);
      return e.value;
  }
 ​
  /**
   * {@inheritDoc}
   */
  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;
 }
Copy the code

This is where you can see one of the uses of accessOrder: control of the query node.

  • However, the get method has not been completely rewritten. It is probably because the hash + node data structure is intuitively faster than the list + node data structure.
public boolean containsValue(Object value) { for (LinkedHashMap.Entry<K,V> e = head; e ! = null; e = e.after) { V v = e.value; if (v == value || (value ! = null && value.equals(v))) return true; } return false; }Copy the code
  • The containsValue method is completely rewritten.

There are also subclass methods and overrides of some of the Map methods in 1.8, which are similar to the implementation of HashMap, except for a few key points:

  • The foreach method uses the head node to traverse.
  • The traverser method also uses the head node for next’s query traversal.

conclusion

As you can see, LinkedHashMap is not a complete collection interface:

  • The head and tail nodes are maintained, but not the main data structure; the main functional structure is still the Table array in the HashMap.

    • Use of head and tail nodes: values and foreach methods
  • Here’s an interesting question:

Is the linkedHashMap ordered?

In a way, it is. But there are a few disagreements about this order:

  • Is accessOrder true? If so, there is no guarantee of overall orderliness (because queries cause nodes to be postpositioned)
  • Has it been rewrittenremoveEldestEntry? If the method may return a value, the insertion may be accompanied by a deletion of the old node.

LinkedHashMap is more about inheriting and rewriting in other middleware, taking advantage of the maintained bidirectional linked list nature for secondary development.

  • Example: By rewritingremoveEldestEntry, you can easily construct a LRUcache.