Schematic set 6: LinkedHashMap, schematic linkedhashmap
First knowledge of LinkedHashMap
The last two articles talked about the problems caused by HashMap and HashMap in multiple threads, and explained that HashMap is a very common and useful set, in addition, improper use of multiple threads may cause thread security problems.
In most cases, as long as thread security issues are not involved, Map can basically use HashMap. However, there is a problem with HashMap, that is, the order of iterative HashMap is not the order where HashMap is placed, that is, disorder. This disadvantage of HashMap is often troublesome, because in some scenarios, we look forward to an orderly Map.
At this time, LinkedHashMap was launched. Although it increased the overhead of time and space, by maintaining a two-way linked list running on all entries, LinkedHashMap ensured the order of element iteration.
Answers with four focus on LinkedHashMap
Close note |
Conclusion |
Whether LinkedHashMap allows null key-value pairs |
Both Key and Value can be empty. |
Whether repeated data is allowed in LinkedHashMap |
Duplicate Key will overwrite, and repeated Value will be allowed |
Sequence hashmap |
Ordered |
Whether LinkedHashMap is thread-safe |
Non-thread security |
LinkedHashMap Basic Data Structure
For LinkedHashMap, we should first mention two points:
1. LinkedHashMap can be considered as a HashMap + partition list, that is, it uses both HashMap to operate the data structure and the partition list to maintain the order of inserted Elements
2. The basic implementation idea of LinkedHashMap is polymorphism. It can be said that understanding polymorphism and understanding the principle of LinkedHashMap will get twice the result with half the effort. On the contrary, learning the principle of LinkedHashMap can also promote and deepen the understanding of polymorphism.
For more information, see the definition of LinkedHashMap:
public class LinkedHashMap
extends HashMap
implements Map
{ ...}
As you can see, LinkedHashMap is a subclass of HashMap. Naturally, LinkedHashMap inherits all non-private methods in HashMap. Let's take a look at the methods in LinkedHashMap:
There are no methods for operating the data structure in LinkedHashMap. That is to say, the method for operating the data structure (such as put a data) in LinkedHashMap is exactly the same as that for operating the data in HashMap, there are some differences in details.
The difference between LinkedHashMap and HashMap lies in their basic data structure. Let's take a look at the basic data structure of LinkedHashMap, that is, Entry:
private static class Entry
extends HashMap.Entry
{ // These fields comprise the doubly linked list used for iteration. Entry
before, after;Entry(int hash, K key, V value, HashMap.Entry
next) { super(hash, key, value, next); } ...}
List some attributes in the Entry:
K key V value Entry Next Int hash Entry Before Entry After
The first four, that is, the red part, are inherited from HashMap. Entry; the other two, that is, the blue part, are unique to LinkedHashMap. Do not confuse next, before, and After. next is used to maintain the order of entries connected to the specified table location of HashMap, and before and After are used to maintain the Entry insertion sequence.
You can also use a diagram to indicate the attributes:
Initialize LinkedHashMap
Suppose there is such a piece of code:
1 public static void main(String[] args)2 {3 LinkedHashMap
linkedHashMap =4 new LinkedHashMap
();5 linkedHashMap.put("111", "111");6 linkedHashMap.put("222", "222");7 }
The first is 3rd rows ~ Line 3: A new LinkedHashMap is shown. Let's take a look at what we have done:
1 public LinkedHashMap() { 2 super(); 3 accessOrder = false; 4 }
1 public HashMap() {2 this.loadFactor = DEFAULT_LOAD_FACTOR;3 threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);4 table = new Entry[DEFAULT_INITIAL_CAPACITY];5 init();6 }
1 void init() { 2 header = new Entry
(-1, null, null, null); 3 header.before = header.after = header; 4 }
/** * The head of the doubly linked list. */private transient Entry
header;
Here we see the first polymorphism: init () method. Although the init () method is defined in HashMap:
1. LinkedHashMap overwrites the init method.
2. the instantiated is LinkedHashMap.
Therefore, the init method actually called is the init method rewritten by LinkedHashMap. Assuming that the header address is 0x00000000, the initialization is completed as follows:
LinkedHashMap add elements
Let's continue to look at what the javashashmap adds elements, that is, put ("111", "111") has done. The first of all, of course, is to call the put Method of HashMap:
1 public V put(K key, V value) { 2 if (key == null) 3 return putForNullKey(value); 4 int hash = hash(key.hashCode()); 5 int i = indexFor(hash, table.length); 6 for (Entry
e = table[i]; e != null; e = e.next) { 7 Object k; 8 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { 9 V oldValue = e.value;10 e.value = value;11 e.recordAccess(this);12 return oldValue;13 }14 }15 16 modCount++;17 addEntry(hash, key, value, i);18 return null;19 }
Row 17th is also a polymorphism. Because LinkedHashMap overwrites the addEntry method, addEntry calls the method rewritten by LinkedHashMap:
1 void addEntry(int hash, K key, V value, int bucketIndex) { 2 createEntry(hash, key, value, bucketIndex); 3 4 // Remove eldest entry if instructed, else grow capacity if appropriate 5 Entry
eldest = header.after; 6 if (removeEldestEntry(eldest)) { 7 removeEntryForKey(eldest.key); 8 } else { 9 if (size >= threshold)10 resize(2 * table.length);11 }12 }
Because LinkedHashMap maintains the insertion sequence, LinkedHashMap can be used for caching, 5th rows ~ The first row is used to support the FIFO algorithm. You do not need to care about it for the time being. Let's take a look at the createEntry method:
1 void createEntry(int hash, K key, V value, int bucketIndex) {2 HashMap.Entry
old = table[bucketIndex];3 Entry
e = new Entry
(hash, key, value, old);4 table[bucketIndex] = e;5 e.addBefore(header);6 size++;7 }
private void addBefore(Entry
existingEntry) { after = existingEntry; before = existingEntry.before; before.after = this; after.before = this;}
2nd rows ~ There is no difference between the Code in line 1 and HashMap. The newly added elements are placed on table [I]. The difference is that LinkedHashMap also performs the addBefore operation, the four lines of code mean to generate a two-way linked list for the new Entry and the original linked list. Assume that string 111 is placed on the position table [1] and the generated Entry address is 0x00000001. The figure shows this:
If you are familiar with the source code of the listing list, it is not difficult to understand, or explain it. Note that the existingEntry indicates the header:
1. after = existingEntry, that is, the after = header address of the newly added Entry, that is, after = 0x00000000
2. before = existingEntry. before: The before of the newly added Entry is the before address of the header. The before of the header is 0x00000000. Therefore, the before of the newly added Entry is 0x00000000.
3. before. after = this: before of the newly added Entry is 0x00000000, that is, header, after = this of header, that is, after = 0x00000001 of header
4. after. before = this: after the newly added Entry, 0x00000000 indicates the header, and before = this indicates the header's before = 0x00000001.
In this way, a two-way linked list is formed between the header and the newly added Entry. Then, what is the new string after 222? Assume that the address of the newly added Entry is 0x00000002 and is generated on table [2], which is shown in the figure below:
As long as before and after are cleared to know which Entry represents, there is no problem.
Let's take a look at it. Again, the implementation of LinkedHashMap is the implementation of HashMap + LinkedList. The data structure is maintained by HashMap and the data insertion sequence is maintained by LinkList.
Implement LRU algorithm cache using LinkedHashMap
As mentioned above, LinkedHashMap adds elements. It is relatively simple to delete and modify elements. It is similar to deleting and modifying elements in HashMap + LinkedList. Next we will introduce a new content.
LinkedHashMap can be used as a cache, for example, LRUCache. Let's take a look at the code of this class. It's just a dozen lines:
public class LRUCache extends LinkedHashMap{ public LRUCache(int maxSize) { super(maxSize, 0.75F, true); maxElements = maxSize; } protected boolean removeEldestEntry(java.util.Map.Entry eldest) { return size() > maxElements; } private static final long serialVersionUID = 1L; protected int maxElements;}
As the name suggests, LRUCache is a Cache based on the LRU algorithm. This class inherits from LinkedHashMap, and there is no special method in the class, this indicates that LRUCache implements the cache LRU function from LinkedHashMap. LinkedHashMap can implement the LRU algorithm cache based on two points:
1. Sort list first it is a Map, Map is based on K-V, consistent with the cache
2. The sorted list provides a boolean value that allows you to specify whether to implement LRU.
First, let's take a look at what LRU is: LRU, that is, Least Recently Used, which is Least Used Recently. That is to say, when the cache is full, data that is Least frequently accessed Recently will be preferentially eliminated. For example, data a is accessed one day ago. Data B is accessed two days ago. If the cache is full, data B is eliminated first.
Let's take a look at the constructor with boolean parameters in the shortlist:
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder;}
This is the accessOrder, which indicates:
(1) false. All entries are arranged in the insert order.
(2) true. All entries are arranged in the access order.
The second point means that if there are three entries 1 2 3, 1 is accessed, and 1 is moved to the end, that is, 2 3 1. When the accessed data is moved to the end of the two-way queue during each access, isn't the data at the top of the two-way queue the data that is least frequently accessed? In other words, the data at the top of the two-way linked list is the data to be eliminated.
The word "access" has two meanings:
1. get the Value based on the Key, that is, the get method.
2. Modify the Value corresponding to the Key, that is, the put method.
First, let's take a look at the get method, which is overwritten in LinkedHashMap:
public V get(Object key) { Entry
e = (Entry
)getEntry(key); if (e == null) return null; e.recordAccess(this); return e.value;}
Then the put method follows the HashMap of the parent class:
1 public V put(K key, V value) { 2 if (key == null) 3 return putForNullKey(value); 4 int hash = hash(key.hashCode()); 5 int i = indexFor(hash, table.length); 6 for (Entry
e = table[i]; e != null; e = e.next) { 7 Object k; 8 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { 9 V oldValue = e.value;10 e.value = value;11 e.recordAccess(this);12 return oldValue;13 }14 }15 16 modCount++;17 addEntry(hash, key, value, i);18 return null;19 }
Modify data that is 6th rows ~ 14th lines of code. The code at both ends has one thing in common: the recordAccess method is called, and this method is the method in the Entry, that is, each recordAccess operation is a fixed Entry.
RecordAccess, as its name implies, records access. That is to say, if you access a two-way linked list this time, I will record you. How can I record it? Move the Entry you accessed to the end. This method is an empty method in HashMap, which is used to access sub-class records. Let's take a look at the implementation in LinkedHashMap:
void recordAccess(HashMap
m) { LinkedHashMap
lm = (LinkedHashMap
)m; if (lm.accessOrder) { lm.modCount++; remove(); addBefore(lm.header); }}
private void remove() { before.after = after; after.before = before;}
private void addBefore(Entry
existingEntry) { after = existingEntry; before = existingEntry.before; before.after = this; after.before = this;}
We can see two things are done each time recordAccess:
1. Connect the front and back Entry of the Entry to be moved
2. Move the Entry to the end
Of course, this is all based on accessOrder = true. The last figure shows the entire recordAccess process:
Code demonstrate the effect of sorted by access order of LinkedHashMap
The final Code demonstrates the effect of sorted sort list by access order, and verifies the LRU function of the previous segment of LinkedHashMap:
public static void main(String[] args){ LinkedHashMap
linkedHashMap = new LinkedHashMap
(16, 0.75f, true); linkedHashMap.put("111", "111"); linkedHashMap.put("222", "222"); linkedHashMap.put("333", "333"); linkedHashMap.put("444", "444"); loopLinkedHashMap(linkedHashMap); linkedHashMap.get("111"); loopLinkedHashMap(linkedHashMap); linkedHashMap.put("222", "2222"); loopLinkedHashMap(linkedHashMap);}public static void loopLinkedHashMap(LinkedHashMap
linkedHashMap){ Set
> set = inkedHashMap.entrySet(); Iterator
> iterator = set.iterator(); while (iterator.hasNext()) { System.out.print(iterator.next() + "\t"); } System.out.println();}
Note that the constructor here uses the three parameters and the final value must be set to true, which indicates sorting by access order. Let's take a look at the code running result:
111=111 222=222 333=333 444=444 222=222 333=333 444=444 111=111 333=333 444=444 111=111 222=2222
The code running result proves two points:
1. The sorted list is ordered.
2. Each time an element (get or put) is accessed, the accessed element is mentioned at the end.