How to assure an orderly LinkedHashMap

I. Introduction

Look at an example, we want to show changes in consumption in the week page, on display with echarts area chart. as follows:

We will complete the data structure in the background

The HashMap <String, Integer> = new new Map the HashMap <> (); 
map.put ( "Monday", 40); 
map.put ( "Tuesday", 43 is); 
map.put ( "Wednesday", 35); 
Map .put ( "Thursday", 55); 
map.put ( "Friday", 45); 
map.put ( "Saturday", 35); 
map.put ( "Sunday", 30);

However, to show a page and found that is not the case, we print it out and saw that the order is not what we want, to put into the first get out

for (of Map.Entry <String, Integer> entry: EnumMap.entrySet ()) { 
    System.out.println ( "Key:" entry.getKey + () + ", value:" + entry.getValue ()); 
} 
/ ** 
 * results are as follows: 
 * Key: Tuesday, value: 40 
 * Key: Saturday, value: 35 
 * Key: Wednesday, value: 50 
 * Key: Thursday, value: 55 
 * Key: Friday, value: 45 
 * Key: Sunday, value: 65 
 * Key: Monday, value: 30 
 * /

So how to ensure that results are expected to show what we want to do, this time we need to use LinkedHashMap entity.

II. Acquaintance LinkedHashMap

First, we put the above code is reconstituted with LinkedHashMap

A LinkedHashMap <String, Integer> a LinkedHashMap new new Map = <> (); 
map.put ( "Monday", 40); 
map.put ( "Tuesday", 43 is); 
map.put ( "Wednesday", 35); 
Map .put ( "Thursday", 55); 
map.put ( "Friday", 45); 
map.put ( "Saturday", 35); 
map.put ( "Sunday", 30); 
for (Map.Entry <String , Integer> entry: EnumMap.entrySet ()) { 
    System.out.println ( "Key:" entry.getKey + () + ", value:" + entry.getValue ()); 
}

This time, as we expected result

key: Monday, value: 40 
key: Tuesday, value: 43 
key: Wednesday, value: 35 
key: Thursday, value: 55 
key: Friday, value: 45 
key: Saturday, value: 35 
key: Sunday, value: 30

LinkedHashMap inherited HashMap class is a subclass of HashMap, implements most direct method of LinkedHashMap using a method of the parent class HashMap, HashMap about in previous chapters have already spoken, "HashMap principle (a) concepts and underlying architecture." , "HashMap principle (ii) expansion of access mechanisms and principles" .

LinkedHashMap can be said to be a collection of LinkedList and HashMap, HashMap using both the data structure, but also borrowed a doubly linked list LinkedList structure (refer to about LinkedList LinkedList principles and use Java collections ), then how such a structure to achieve it, we look at the class structure of LinkedHashMap

We see Entry LinkedHashMap defines a static inner class defines five constructors, some members of variables, such as the head, tail, accessOrder, and inherited the method of HashMap, while achieving some of the iterator method. We look at the Entry class

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);
    }
}

We see this very simple static inner class inherits from Node inner class HashMap, we know Node class is the underlying data structure HashMap to achieve a linked list structure array + / red-black tree, while retaining the Entry class data structure is HashMap , through before, after enables bi-directional linked list structure (in the HashMap next Node class attributes only and does not have the two-way linked list structure). So before, after, and next in the end what to do with it.

See the above chart, the first node defines head, when we call the iterator is traversed by a traversing head, can continue to find the next pass before property, until the tail end of the junction, thereby achieving sequential. In the same hash (in the figure above show the same line) after an internal list and next effect is the same. The difference is that the list can be connected before and after between different hash.

Earlier we found that the data structure has been fully supported by the order of, and then we look at what the constructor, the constructor HashMap than look at whether there are different.

// constructor 1, construct a specified initial capacity and load factor, in accordance with the order of insertion LinkedList 
public a LinkedHashMap (int initialCapacity, a float loadFactor) { 
    Super (initialCapacity, loadFactor); 
    accessOrder = to false; 
} 
// constructor 2, structure LinkedHashMap a specified initial capacity, of the order of obtaining the key is inserted into the sequence of 
public LinkedHashMap (int initialCapacity) { 
    Super (initialCapacity); 
    accessOrder = to false; 
} 
// constructor 3, to create a default initialization with LinkedHashMap capacity and load factor , on the order of obtaining the key is inserted into the order 
public a LinkedHashMap () { 
    Super (); 
    accessOrder = to false; 
} 
// constructor 4, a map is created by passing a LinkedHashMap, the default capacity of the capacity (16) and (map. zise () / DEFAULT_LOAD_FACTORY) +1 is greater loading factor to a default value 
public a LinkedHashMap (the Map m) {<K the extends, the extends V??> 
    Super (m);
    = to false accessOrder; 
} 
// constructor 5. The specified capacity, and load factor to create a key pair LinkedHashMap order to maintain 
public LinkedHashMap (int initialCapacity, loadFactor a float, Boolean accessOrder) { 
    Super (initialCapacity, loadFactor); 
    this.accessOrder = accessOrder; 
}

We found that in addition to more than one variable accessOrder, no different, this variable is in the end what role?

/**
 * 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;

Note found by the variables when the true access-order, i.e., in order of access traversal, if false, it indicates insertion order traversal. The default is false, to the use of variables in what areas, and how to understand? We can see the method described below

Two. Put method

We mentioned earlier LinkedHashMap put the put method follows the parent class HashMap, but we also mentioned as LinkedHashMap of Entry HashMap class that inherits the Node class, the same, other methods put method of HashMap call in the LinkedHashMap It has been rewritten. We look at HashMap method of put this in the "principle HashMap (b) expansion of access mechanisms and principles" are already described, we focus on differences which

/ ** 
 * the Implements map.put and Related Methods 
 * 
 * @param hash hash for Key 
 * @param Key at The Key 
 * @param value at The value to PUT 
 * @param onlyIfAbsent IF to true, do not Change existing value 
 * @param evict to false IF, in The Table Creation MODE IS. 
 * @return Previous value, or null none IF 
 * / 
Final V PutVal (int the hash, Key K, V value, onlyIfAbsent Boolean, Boolean The evict) { 
    the Node <K, V> [] Tab; the Node <K, V> P; n-int, I; 
    / ** 
     * If the current table is an array HashMap undefined or not yet initialized its length, the first for expansion by a resize (), 
     * returns an array of the expansion the length of the n- 
     * / 
    IF ((Tab = Table) == null || (= n-tab.length) == 0) 
        n-= (Tab = a resize ()) length.;
    // do the hash value of the length of the array by bitwise AND operation to obtain the corresponding & array subscript, if this position is not the element, the new element directly to the new Node inserted 
    if ((p = tab [i = (n - 1) & hash ]) == null) 
        Tab [I] = the newNode (the hash, Key, value, null); 
    // otherwise, the position of elements has we need some additional operations 
    the else { 
        the Node <K, V> E; K K; 
        // if the key is inserted and the same as the original key, click on the bin is replaced 
        if (p.hash == hash && (( k = p.key) == key || (key = null && key!. the equals (K)))) 
            E = P; 
        / ** 
         * key or different, it is determined whether the current Node is the TreeNode, if the execution putTreeVal new element is inserted 
         * into a red-black tree. 
         * / 
        The else IF (the instanceof the TreeNode P) 
            E = ((the TreeNode <K, V>) P) .putTreeVal (the this, Tab, the hash, Key, value); 
        // if not TreeNode, a linked list traversal is performed
        {the else 
            for (int BinCount = 0;; BinCount ++) { 
                / ** 
                 * not found after the last node in the list the same elements, the following operation is performed directly inserted new Node, 
                 * but could be transformed conditional red-black tree 
                 * / 
                IF ((E = p.next) == null) { 
                    // a new direct the Node 
                    p.next the newNode = (the hash, Key, value, null); 
                    / ** 
                     * = TREEIFY_THRESHOLD. 8, because binCount starts from 0, that is, the list exceeds 8 (inclusive), the 
                     * turned red-black tree. 
                     * / 
                    IF (BinCount> = TREEIFY_THRESHOLD -. 1) for -1 // 1st 
                        treeifyBin (Tab, the hash); 
                    BREAK;
                } 
        } 
                / **
                 * If the same key value is found in the list prior to the last node (and do not conflict with the above judgment, is directly above the array 
                 * index is determined whether the same key value), then the replacement 
                 * / 
                IF (the hash && == e.hash 
                    (! (K = e.key) == || Key (Key = null && key.equals (K)))) 
                    BREAK; 
                P = E; 
            } 
        } 
        ! IF (E = null) {// for existing Key Mapping 
            V oldValue = e.value; 
            when // onlyIfAbsent is true: when a position is not already exist covering elements 
            iF (onlyIfAbsent oldValue == null ||!) 
                e.Value = value; 
            afterNodeAccess (E); 
            return oldValue; 
    } 
    ++ modCount; 
    // Finally, threshold determination, if the expansion. 
    IF (size ++> threshold) 
        a resize (); 
    afterNodeInsertion (The evict); 
    return null; 
}

1. newNode method

First: LinkedHashMap rewriting the newNode () method, this method ensures by sequential insertion.

/ ** 
 * LinkedHashMap inner class using the Entry 
 * / 
the Node <K, V> the newNode (int the hash, Key K, V value, the Node <K, V> E) { 
    LinkedHashMap.Entry <K, V> P = new new LinkedHashMap .Entry <K, V> (the hash, Key, value, E); 
    linkNodeLast (p); 
    return p; 
} 
/ ** 
 * p newly created node as the end node tail, 
 * of course, if the stored first node, i.e. it is the head node, tail node is, at this time before and after the node p are null 
 * otherwise, establishing a relationship with the upper end of the linked list node, the previous node of the current node p tail (before) is set to the last tail node last, 
 * the last tail node last one node (after) set as a current tail node P 
 * this method enables two-way linked list function, complete before, after, head, tail of value 
 * / 
Private void linkNodeLast (LinkedHashMap.Entry <K, V> P) { 
    LinkedHashMap.Entry <K, V> Last = tail; 
    tail = P; 
    IF (Last == null) 
        head = P; 
    the else {
        p.before = last;
        last.after = p;
    }
}

2. afterNodeAccess method

Second: About afterNodeAccess () method, the HashMap did not give a specific implementation, but in LinkedHashMap rewritten, the purpose is to ensure that the operation had Node node in last forever, thus ensuring sequential read, call the put and get methods in when it will be used.

/ ** 
 * When accessOrder not the last time and incoming node is true, the incoming node moves to the last 
 * / 
void afterNodeAccess (the Node <K, V> E) { 
    // before the execution of the method a tail node 
    LinkedHashMap.Entry <K, V> last; 
    when accessOrder is true and if // a node not on the incoming end of the node, the following method 
    if (accessOrder && (last = tail )! E =) { 
        LinkedHashMap.Entry <K, V> P = 
            (LinkedHashMap.Entry <K, V>) E, B = p.before, A = p.after; 
        // P: the current node 
        // b: the current node previous node 
        // a: after a current node; 
        
        // will p.after set to null, a disconnected relationship with the node, but has not determined its location 
        p.after = null; 
        / ** 
         * p removed as the current node, then the disconnection between the node b and the node a, node b let stand with an angle establishing a node 
         associated with a *, b if the node is null, which means the current node is the head node p point, the removed node p, p of the next node is a node of the head; 
         * otherwise, after a node b is set to the node a 
         * /
        IF (B == null) 
            head = a; 
        the else 
            b.after = a; 
        / ** 
         * p removed as the current node, then disconnected between the node A and a node B, a node angle we stand establishment and a node b 
         is associated *, if a node is null, which means the current node is the end node p, p removed node, the previous node p b is tail node, 
         * but this time we do not directly node p assigned to the tail, but a local variable to the last (i.e. the last node in the current), since the 
         * is not directly assigned tail consistent with the ultimate goal of the method; null if the node is not a node of a previous node is provided node b 
         * 
         * (as has already been judged (last = tail)! = e , explanation incoming node is not the end node, since it is not the end node, then 
         * e.after inevitable is not null, then why here and determine the case a == null? 
         * As I understand it, the Java can damage the package by reflex mechanism, so if you are a reflection of Entry to create an entity may not meet in front of 
         * judgment condition) 
         * / 
        the else 
        IF (a! = null)
            B = a.before; 
            last = B; 
        / ** 
         * last under normal circumstances should not be empty, determining why reasons as before 
         * p.after front set to null, then set here before its value a tail node to the last, while the end of the last node 
         * last setting of the present time P 
         * / 
        IF (last == null) 
            head = P; 
        the else { 
            p.before = last; 
            last.after = P ; 
        } 
        // last node to the end node p, bin 
        tail = p; 
        ++ ModCount; 
    } 
}

We said before linkNodeLast (Entry e) methods and now afterNodeAccess (Node e) all incoming Node node into the final, then how they use scenario?

When explaining HashMap front, put the HashMap mentioned process, if the corresponding hash element position has not, then the direct new Node () into an array in the table, this time corresponds to the LinkedHashMap, called the newNode () method, will be used linkNodeLast (), the new node into the final, but if there is an element corresponding hash position covering element values, will call afterNodeAccess (), the original may not be the last node node got the last . Such as

A LinkedHashMap <String, Integer> a LinkedHashMap new new Map = <> (16, 0.75f, to true); 
map.put ( "January", 20); 
// call to this point will linkNodeLast () method, also called afterNodeAccess () method, but will be blocked 
// if (accessOrder && (last = tail) = e!) than 
map.put ( "February", 30); 
map.put ( "March", 65); 
map.put ( "April", 43); 
// At this point does not call linkNodeLast (), calls afterNodeAccess () method will key into the final as "January" element 
map.put ( "January" 35); 
// At this point does not call linkNodeLast (), calls afterNodeAccess () method will be key to "February" element into the final 
map.get ( "February"); 
// calls the print method 
for (Map .Entry <String, Integer> entry: EnumMap.entrySet ()) { 
    System.out.println ( "Key:" entry.getKey + () + ", value:" + entry.getValue ()); 
}

The results are as follows:

key: 3月, value: 65
key: 4月, value: 43
key: 1月, value: 35
key: 2月, value: 30

And if it is to perform the following piece of code, accessOrder to false

A LinkedHashMap <String, Integer> a LinkedHashMap new new Map = <> (16, 0.75f, to false); 
map.put ( "January", 20); 
// call to this point will linkNodeLast () method, also called afterNodeAccess () method, but will be blocked 
// if (accessOrder && (last = tail) = e!) than 
map.put ( "February", 30); 
map.put ( "March", 65); 
map.put ( "April", 43); 
// At this point does not call linkNodeLast (), calls afterNodeAccess () method will key into the final as "January" element 
map.put ( "January" 35); 
as map.get ( "Feb"); 
// calls the print method 
for (of Map.Entry <String, Integer> entry: EnumMap.entrySet ()) { 
    System.out.println ( "Key:" + entry. getKey () + ", value:" entry.getValue + ()); 
}

The results are as follows:

key: 1月, value: 35
key: 2月, value: 30
key: 3月, value: 65
key: 4月, value: 43

We see the difference yet, accessOrder is false, according to the order that you order your first visit inserted; and accessOrder is true, any time you operate, including put, get operation, will change the map already the storage order.

3. afterNodeInsertion方法

We see LinkedHashMap also rewrite the afterNodeInsertion (boolean evict) method, its purpose is to remove the list of the oldest node object, which is the current head of the node object, but does not actually perform in JDK8 because removeEldestEntry method always returns false. Look Source:

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;
}

Three. Get method

LinkedHashMap get method different from the HashMap get method is also characterized by much afterNodeAccess () method

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;
}

Here is not more talk about it, getNode () method has been talked about in the chapter HashMap, while the front had just afterNodeAccess talk about it.

Four .remove method

remove method used directly in the HashMap remove, and do not explain the HashMap section, because the principle is very simple remove the calculated hash key parameter passing, whereby Node can find the corresponding node, if the next node is a Node node directly in the array, then the position of the table array element is disposed node.next; if the list, then traverse the list until it finds a node corresponding to the node, and arranged to establish a next node is the node the next node.

LinkedHashMap rewritten therein afterNodeRemoval (Node e), this method is not embodied in the HashMap, by this method of adjusting the structure of the doubly-linked list when the delete node.

afterNodeRemoval void (the Node <K, V> E) { 
    LinkedHashMap.Entry <K, V> P = (LinkedHashMap.Entry <K, V>) E, B = p.before, A = p.after; 
    // to be before and after the deletion node are set to null 
    p.before = p.after = null; 
    / ** 
     * If the node b is null, which means to delete the node p is the head node that removed next, the node a node is a head node to a head 
     * or after setting properties on a node b to be the deletion node is the node a 
     * / 
    IF (b == null) 
        head = a; 
    the else 
        b.after = a; 
    / ** 
     * If a node is null, the node p is to be deleted indicates a tail node, the node is removed, the one node of a node on the tail node tail 
     * or properties to be provided before the deletion node a next node is the node b 
     * / 
    IF (A == null) 
        tail = B; 
    the else 
        a.before = B; 
}

V. summary

LinkedHashMap also more frequently used, which is based on HashMap, HashMap for the characteristics, but also increases the structural doubly-linked list, so as to ensure sequential, article from the point of view of how the source code which ensures sequential explanation, accessOrder, and common interpretation method, if imperfect, please criticism and hope to make progress together, thank you!

Guess you like

Origin www.linuxidc.com/Linux/2019-08/160019.htm