Java Collections Framework Analysis (five) LinkedHashMap analysis

About LinkedHashMap

public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
复制代码

Inherited from the HashMap, an ordered Map interface, herein it refers to an ordered elements may be arranged in order of insertion order or access; compared to the HashMap, because LinkedHashMap inherited from a HashMap, the LinkedHashMap, also based on a hash table to achieve . While achieving the Serializable and Cloneable interface, support serialization and cloning. And as such is not thread safe. The difference is that a bi-directional internal loop maintains the linked list, the list is ordered, the sequence may be inserted by the element or elements nearest the access order (LRU) order.

LinkedHashMap data structure

LinkedHashMap as its only as HashMap + Entry storage array based on the hash table next linked list and single linked list, but also combines the advantages of the LinkedList increased Entry precursor and successor node for each and adds a header to the first node, a doubly linked, circular list is constructed. That is, each incoming put KV, except to save it to a position corresponding to the outer hash table, which is also inserted at the tail of the two-way circular list.

Here Insert Picture Description

The figure is all the LinkedHashMap data structure that contains a hash table and circular doubly linked list, due to the circular doubly linked list of line too much, bad painting, a simple drawing of a node (yellow circle out) to indicate what, pay attention to the red arrow to the left Entry node for the object referenced next reference (single-chain hash table), for the green line before Entry node objects, reference After (longitudinal circular doubly linked list of references);

Here Insert Picture Description

The figure dedicated to the circular doubly linked list extracted, intuitive thing, note that the circular doubly linked list head node is stored or accessed oldest first inserted node, or nearest the tail is inserted node visited recently, iterates over direction beginning from the head of the list to the tail end of the list, there is an empty node list header tail node does not store the key-value content, is a member of the class attribute LinkedHashMap, circular doubly linked list entry;

Source LinkedHashMap

The above analysis is conventional knowledge LinkedHashMap source, look, in order to better analyze its source code, here we started to analyze the work.

Attributes:

//属性设置,序列化ID
private static final long serialVersionUID = 3801124242820219131L;
//双向链表的头部
private transient LinkedHashMapEntry<K,V> header;
//迭代的时候所用到的顺序,如果为FALSE,则按照插入的时候顺序
private final boolean accessOrder;
复制代码

These attributes are simple, but more importantly, detailed instructions direct the outset, is not well understood, and so we analyze the code again look over the meaning they represent. We analyze analyze its constructor.

Constructor analysis

Provided constructor initial capacity and load factor

/**
  * 设置初始容量和加载因子的构造器
  */
 public LinkedHashMap(int initialCapacity, float loadFactor) {
     super(initialCapacity, loadFactor);
     accessOrder = false;
 }
复制代码

Setting initial capacity constructor

 /**
  * 设置初始容量的构造器
  * @param  initialCapacity the initial capacity
  * @throws IllegalArgumentException if the initial capacity is negative
  */
 public LinkedHashMap(int initialCapacity) {
     super(initialCapacity);
     accessOrder = false;
 }
复制代码

Default constructor parameter empty, the default capacity and load factor of 16 to 0.75

 /**
  * 默认的空参数的构造器,默认容量为16以及加载因子为0.75
  * with the default initial capacity (16) and load factor (0.75).
  */
 public LinkedHashMap() {
     super();
     accessOrder = false;
 }
复制代码

Use an existing Map to construct LinkedHashMap

 /**
  * 使用一个现有的Map来构造LinkedHashMap
  * @param  m the map whose mappings are to be placed in this map
  * @throws NullPointerException if the specified map is null
  */
 public LinkedHashMap(Map<? extends K, ? extends V> m) {
     super(m);
     accessOrder = false;
 }
复制代码

Iterative procedure set constructor

 /**
  * 设定迭代顺序的构造器
  * @param  initialCapacity the initial capacity
  * @param  loadFactor      the load factor
  * @param  accessOrder     the ordering mode - <tt>true</tt> for
  *         access-order, <tt>false</tt> for insertion-order
  * @throws IllegalArgumentException if the initial capacity is negative
  *         or the load factor is nonpositive
  */
 public LinkedHashMap(int initialCapacity,
                      float loadFactor,
                      boolean accessOrder) {
     super(initialCapacity, loadFactor);
     this.accessOrder = accessOrder;
 }
复制代码

These are relatively simple constructor, we mention a little bit, if not specified initialCapacity initial capacity, the default is to use HashMap initial capacity, i.e., 16. If no load factor loadFactor, the default is 0.75. accessOrder default faslse. It should describe this Boolean value that is doubly linked list element flag collation.

When accessOrder If false, the doubly linked list traversal, the insertion order is sorted. accessOrder If true, indicates that the doubly linked list elements arranged according to the order of access, the first traversal (head of the list) is the least recently used element.

As can be seen from the method of construction, the default order of insertion order are used to maintain the key-value pair extraction. All construction methods are used to create the object by calling the constructor of the parent class.

In the constructor of the parent class, we can see that it calls the init method, which calls the init method in the Map class constructor, we look into the content

@Override
    void init() {
        header = new LinkedHashMapEntry<>(-1, null, null, null);
        header.before = header.after = header;
    }
复制代码

The init method is mainly to initialize the header node, constitute a doubly linked list. Analysis of finished constructor, then we analyze the most common attribute Entry.

LinkedHashMapEntry analysis

//这个Entry继承自HashMapEntry
 private static class LinkedHashMapEntry<K,V> extends HashMapEntry<K,V> {
        //双向节点的前后引用
        // These fields comprise the doubly linked list used for iteration.
        LinkedHashMapEntry<K,V> before, after;
        
        //构造器
        LinkedHashMapEntry(int hash, K key, V value, HashMapEntry<K,V> next) {
            super(hash, key, value, next);
        }
        //移除一个节点
        private void remove() {
            before.after = after;
            after.before = before;
        }
        /**
         * 在指定的位置前面插入一个节点
         */
        private void addBefore(LinkedHashMapEntry<K,V> existingEntry) {
            after  = existingEntry;
            before = existingEntry.before;
            before.after = this;
            after.before = this;
        }
        /*
         *在HashMap的put和get方法中,会调用该方法,在HashMap中该方法为空
         * 在LinkedHashMap中,当按访问顺序排序时,该方法会将当前节点插入到链表尾部(头结点的前一个节点),否则不做任何事
         */
        void recordAccess(HashMap<K,V> m) {
            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
            //当LinkedHashMap按访问排序时
            if (lm.accessOrder) {
                lm.modCount++;
                //移除当前节点
                remove();
                //将当前节点插入到头结点前面
                addBefore(lm.header);
            }
        }
        void recordRemoval(HashMap<K,V> m) {
            remove();
        }
    }
复制代码

The most common method then analyzed put.

put analysis

When we use the put method LinkedHashMap found it calls the put method is HashMap, put themselves no replication method, so in this case, we score two cases to discuss the LinkedHashMap put the operation.

Key existing situation

In the put method of HashMap, when found inserted key already exists, in addition to doing the replacement work, also calls recordAccess () method, in which the HashMap is empty. LinkedHashMap overrides this method (when called LinkedHashmap get overridden method is also called to the method), LinkedHashMap did not put in the HashMap method override, recordAccess () implemented in LinkedHashMap as follows:

//如果当前标明的accessOrder为TRUE的话,则将当前访问的Entry放置到双向循环链表的尾部,以标明最近访问 ,这也是为什么在HashMap.Entry中有一个空的 recordAccess(HashMap<K,V> m)方法的原因
void recordAccess(HashMap<K,V> m) {
            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
            //LRU算法,将访问的节点插入到链表尾部
            if (lm.accessOrder) {
                lm.modCount++;
                //删除当前节点
                remove();
                //将当前节点插入到头结点前面
                addBefore(lm.header);
            }
        }
        
//将当前节点插入到头结点前面
private void addBefore(LinkedHashMapEntry<K,V> existingEntry) {
            after  = existingEntry;
            before = existingEntry.before;
            before.after = this;
            after.before = this;
        }
     
复制代码

Case key does not exist

New Entry in the process put in, if you find the key does not exist, in addition to the corresponding location of the new Entry into the hash table, but also calls addEntry method, it calls creatEntry method, which will insert new elements into the two-way the tail of the list, doing so is consistent with the insertion of the order, and consistent with the order of access.

//创建节点,插入到LinkedHashMap中,该方法覆盖HashMap的addEntry方法
void addEntry(int hash, K key, V value, int bucketIndex) {
        //注意头结点的下个节点即header.after,存放于链表头部,是最不经常访问或第一个插入的节点,
        LinkedHashMapEntry<K,V> eldest = header.after;
        //如果有必要,则删除掉该近期最少使用的节点
        if (eldest != header) {
            boolean removeEldest;
            size++;
            try {
                //removeEldestEntry方法的实现,这里默认为false
                removeEldest = removeEldestEntry(eldest);
            } finally {
                size--;
            }
            if (removeEldest) {
                removeEntryForKey(eldest.key);
            }
        }
        //调用HashMap的addEntry方法
        super.addEntry(hash, key, value, bucketIndex);
    }
    
//创建节点,并将该节点插入到链表尾部
 void createEntry(int hash, K key, V value, int bucketIndex) {
        HashMapEntry<K,V> old = table[bucketIndex];
        LinkedHashMapEntry<K,V> e = new LinkedHashMapEntry<>(hash, key, value, old);
        table[bucketIndex] = e;
        //并将其移到双向链表的尾部  
        e.addBefore(header);
        size++;
    }
复制代码

In the above method has a addEntry removeEldestEntry method that can be overwritten, such as the method can override setting if the memory is full, it returns true, so the node can be the least recently used (after the header node) removed.

Why is this method always returns false?

Combination of the above addEntry (int hash, K key, V value, int bucketIndex) method, which is designed to make LinkedHashMap become a normal Map, not to remove the "oldest" node. Why not direct the removal of this part of the code logic but is designed in such a way it? This provides convenience for developers who wish to use the Map as Cache, and limit size, simply inherit LinkedHashMap and rewrite removeEldestEntry (Entry <K, V> eldest) method, like this:

private static final int MAX_ENTRIES = 100;
protected boolean removeEldestEntry(Map.Entry eldest) {
      return size() > MAX_ENTRIES;
}
复制代码

To sum up as long as put a new element coming in, no matter what flag is accessOrder are new elements into the doubly linked list tail, and can always override the need to achieve Lru algorithm removeEldestEntry method, nodes are taken out of the least recently used.

get analysis

//覆写HashMap中的get方法,通过getEntry方法获取Entry对象。  
//注意这里的recordAccess方法,  
//如果链表中元素的排序规则是按照插入的先后顺序排序的话,该方法什么也不做,  
//如果链表中元素的排序规则是按照访问的先后顺序排序的话,则将e移到链表的末尾处。
public V get(Object key) {
        LinkedHashMapEntry<K,V> e = (LinkedHashMapEntry<K,V>)getEntry(key);
        if (e == null)
            return null;
        e.recordAccess(this);
        return e.value;
    }
    
void recordAccess(HashMap<K,V> m) {
            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
            if (lm.accessOrder) {
                lm.modCount++;
                remove();
                addBefore(lm.header);
            }
        }
复制代码

getEntry (Object key) get (Object key) method method of obtaining HashMap node, and returns the value the value of the node, if the node returns acquires null null. Get value by key, the difference between the HashMap is: when LinkedHashMap sorted in order of access time, will move the list to access the tail current node (a node before the head node).

Here we summarize the principles specifically accessOrder flag effect.

1, accessOrder does not work

When put to operation, no matter what accessOrder flag, we will node is inserted into the end of the list, but then, you can always override the need to achieve Lru algorithm removeEldestEntry method, nodes are taken out of the least recently used.

2, accessOrder work

When we put action is, if the key is not equal to null, then calls recordAccess method, this method accessOrder would have worked, if accessOrder is fasle, do nothing, that is when we put already exists Key key-value pairs, in which the position of the doubly linked list will not change. When accessOrder set to true, put into operation will be placed in the tail of the relevant elements of the doubly linked list.

Another case is get operation, get the operation we will also call recordAccess method, for this method, we need to determine the status of accessOrder if accessOrder is fasle, do nothing, that is when we put the existing Key key-value pairs, in which the position of the doubly linked list will not change. When accessOrder set to true, put into operation will be placed in the tail of the relevant elements of the doubly linked list. In the cache's point of view, this is the so-called "dirty data", ie data recently visited, so when you need to clean up memory (when added to the new elements), it can be doubly linked list head node (blank node) back the nodes are taken out.

No common method

So far, basically LinkedHashMap important methods to analyze too, left some of the more important ways, we watched it one time to the next, a little look.

//
@Override
void transfer(HashMapEntry[] newTable) {
    int newCapacity = newTable.length;
    for (LinkedHashMapEntry<K,V> e = header.after; e != header; e = e.after) {
        int index = indexFor(e.hash, newCapacity);
        e.next = newTable[index];
        newTable[index] = e;
    }
}
复制代码

As also transfer (HashMap.Entry [] newTable) method and init () method is called the HashTable. transfer (HashMap.Entry [] newTable) method is called when the call HashMap resize (int newCapacity) method. e index table in the array in the new capacity is calculated according to the hash value list node e, and e is inserted into the list of the calculated index referenced.

public boolean containsValue(Object value) {
        // Overridden to take advantage of faster iterator
        if (value==null) {
            for (LinkedHashMapEntry e = header.after; e != header; e = e.after)
                if (e.value==null)
                    return true;
        } else {
            for (LinkedHashMapEntry e = header.after; e != header; e = e.after)
                if (value.equals(e.value))
                    return true;
        }
        return false;
    }
复制代码

ContainsValue override the parent class (Object value) method, directly by traversing the list header of equal value and determines whether the value, the characteristics of the bidirectional circular list query, less for the outer loop of the array, without a query table array.

public void clear() {
       super.clear();
       header.before = header.after = header;
   }
复制代码

clear () method first calls the parent class method clear () method, after the before header node of the list and after a reference point to the header itself, namely header node is a two-way circular list. This will not gain access to other nodes in the rest of the original list, they will be recovered GC. Empty HashMap, while an empty doubly linked list is reduced to only the first node.

The above is the main method of analysis LinkedHashMap source, to end here, we summarize the relevant things about the HashMap and LinkedHashMap.

to sum up

For LinkedHashMap, we summarize the following points:

1, since LinkedHashMap inherited from HashMap, as it not only be based HashMap as storage array + next Entry list hash table and a single linked list, but also it combines the advantages of the LinkedList increased Entry node for each precursor and successor, and adds a header to the first node, a doubly linked, circular list is constructed. (In a multi-way circular linked list header of the first node, that is, put each come KV, except to save it to a corresponding position outside the hash table, but also to insert it into the tail of the bidirectional circular list .)

2, LinkedHashMap property more than a accessOrder property HashMap. When it is false, indicating that the doubly linked list of elements according to the sort order LinkedHashMap Entry into the insertion, i.e., put into each Entry LinkedHashMap are placed at the end of the doubly linked list, so when traversing the doubly linked list, then output order of Entry and consistent with the order of insertion, which is the default storage sequence doubly linked list; when it is true, indicating that the doubly linked list elements arranged according to the order of access, it can be seen, although the Entry in the linked list according to their order remains put LinkedHashMap in the order, but both put and get methods recordAccess calls (method invocation recordAccess method put under the same key, the original cover in case of Entry), the method determines whether accessOrder is true, and if so, the current Entry (put or get out of the incoming Entry of Entry) moved visit doubly linked list tail (key is not the same, put a new Entry, the calls addEntry, it calls creatEntry, which is also inserted a new element into to the end of the doubly linked list, consistent with the insertion order, and consistent with I asked the order, because when the Entry also visited), otherwise, do nothing.

3, there is provided accessOrder constructor method, if we need to implement an LRU algorithm, it is necessary to set the value of accessOrder TRUE.

It calls recordAccess method 4, the put method HashMap of, if the key is not null and the hash table already in the presence of loops through table [i] in the list, and in the HashMap This method is null method, in LinkedHashMap is achieved in the method, which determines whether accessOrder is true, if the Entry is true, it sets the current access (referred to herein as put incoming Entry) to move the tail way circular linked list, a doubly linked list in order to achieve access elements in accordance with the sort order Entry (recently accessed into the final list, so that down times, that is, the front element has not been recently accessed, when implementing an LRU algorithm, when the doubly linked list of nodes reaches a maximum the previous element can be deleted, because the front element is the least recently used), or do nothing.

About the Author

Focus on Android development for many years, likes to write blog records summarize learning experience, synchronized updates on my blog public number, welcome everyone's attention, we can talk about ~

Here Insert Picture Description

Guess you like

Origin juejin.im/post/5dc14746f265da4d3e1745cd