Thinking logic of computer programs (49), thinking 49

Source: Internet
Author: User

Thinking logic of computer programs (49), thinking 49

Previously, we introduced two implementation classes HashMap and TreeMap of the Map interface. This section introduces another implementation class LinkedHashMap. It is a subclass of HashMap, but it can keep the elements sorted by insert or access, which is different from the TreeMap key sorting.

It is easy to understand the order by insert. What does the order by access mean? What is the use of these two orders? How is it implemented internally? This section discusses these issues. Start with usage.

Usage

Basic Concepts

LinkedHashMap is a subclass of HashMap, but there is also a two-way linked list to maintain the order of key-value pairs, each key-value pair is located in both the hash table and the two-way linked list.

LinkedHashMap supports two types of sequence: insert sequence and access sequence.

The insertion sequence is easy to understand. The first step is before and the last step is after. The modification operation does not affect the sequence.

What does access order mean? Access refers to the get/put operation. After a get/put operation is performed on a key, the corresponding key-value pair will be moved to the end of the linked list, in the first place, it was not accessed for the longest time. This order is the access order.

LinkedHashMap has five constructor methods, four of which are in the insert order, as shown below:

public LinkedHashMap()public LinkedHashMap(int initialCapacity)public LinkedHashMap(int initialCapacity, float loadFactor)public LinkedHashMap(Map<? extends K, ? extends V> m)

Only one constructor can specify the access order as follows:

public LinkedHashMap(int initialCapacity,                     float loadFactor,                     boolean accessOrder)

The accessOrder parameter is used to specify whether to follow the access order. If it is true, it is the access order.

The following is a simple example.

Sort by insert

By default, LinkedHashMap is sorted by insertion. Let's look at the Code:

Map<String,Integer> seqMap = new LinkedHashMap<>();seqMap.put("c", 100);seqMap.put("d", 200);seqMap.put("a", 500);seqMap.put("d", 300);for(Entry<String,Integer> entry : seqMap.entrySet()){    System.out.println(entry.getKey()+" "+entry.getValue());}

The key is inserted in the order of "c", "d", "a". Changing the value of "d" does not change the order, so the output is:

c 100d 300a 500

When do you want to keep the insert sequence?

Map is often used to process some data. The processing mode is to accept some key-value pairs as input, process, and output, and maintain the original order during output. For example, in a configuration file, there are some key-value pairs in the form of configuration items, but some of the keys are repeated, you want to retain the last value, but still output in the original key order, linkedHashMap is a suitable data structure.

For another example, the expected data model may be a Map, but you want to maintain the order of addition. For example, if you want to add a shopping cart, the key is the purchase item, the value is the purchase quantity, and the value is saved in the order you add.

Another common scenario is that we want Map to be ordered by pressing keys, but the keys are sorted by other means before being added to Map. In this case, there is no need to use TreeMap, after all, TreeMap has a higher overhead. For example, you can use the SQL order by statement to sort data from the database to the memory.

Sequential access

Let's look at the example of sequential access. The Code is as follows:

Map<String,Integer> accessMap = new LinkedHashMap<>(16, 0.75f, true);accessMap.put("c", 100);accessMap.put("d", 200);accessMap.put("a", 500);accessMap.get("c");accessMap.put("d", 300);for(Entry<String,Integer> entry : accessMap.entrySet()){    System.out.println(entry.getKey()+" "+entry.getValue());}

This key-value pair is moved to the end of each access, so the output is:

a 500c 100d 300

When do you want to order by access? What is a typical LRU cache?

LRU Cache

Cache is a very useful technology in computer technology. It is a general idea to improve data access performance. It is generally used to save frequently-used data with a small capacity but faster access, the cache is relatively large, but the access is slower. The basic assumption of the cache is that the data will be accessed multiple times. Generally, when accessing the data, the data is first retrieved from the cache, And the cache is not found from the primary storage. After the data is found, the data is placed in the cache, in this way, the next time you find the same data, the access will be faster.

Cache is used in various fields of computer technology, such as cache in the CPU, level-1 cache, level-2 cache, and level-3 cache. level-1 cache is very small, expensive, and fast, the level-3 cache is larger, cheaper, and slower. The CPU cache is faster than the memory. The memory also has a cache. The memory cache is generally relative to the hard disk data. The hard disk may also be a cache to cache data from other machines on the network. For example, some webpages are cached to the local hard disk when a browser accesses a webpage.

LinkedHashMap can be used for caching. For example, it caches basic user information, keys are user IDs, and values are user information. All user information may be stored in the database, and some active user information may be cached.

In general, the cache capacity is limited, so you cannot store all the data infinitely. If the cache is full, when you need to store new data, you need certain policies to clear some old data, this policy is generally called the replacement algorithm. LRU is a popular replacement algorithm. Its full name is Least Recently Used, which has been Used at Least Recently. Its idea is that it is most likely to be Used again soon, however, it is the least likely that users who have not been accessed for the longest time will be used again soon, so they will be cleared first.

Using LinkedHashMap can easily implement LRU cache. By default, LinkedHashMap does not limit capacity, but it can be easily implemented. It has a protected method, as shown below:

protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {    return false;}

After an element is added to LinkedHashMap, LinkedHashMap calls this method. The passed parameter is the key-value pair that has not been accessed for the longest time. If this method returns true, the longest key-value pair will be deleted. The implementation of LinkedHashMap always returns false, and there is no limit on all capacity, but the subclass can override this method. if certain conditions are met, true is returned.

The following is a simple LRU cache implementation. It has a capacity limit, which is passed in the constructor. The code is:

public class LRUCache<K, V> extends LinkedHashMap<K, V> {    private int maxEntries;        public LRUCache(int maxEntries){        super(16, 0.75f, true);        this.maxEntries = maxEntries;    }        @Override    protected boolean removeEldestEntry(Entry<K, V> eldest) {        return size() > maxEntries;    }}    

This cache can be used as follows:

LRUCache<String,Object> cache = new LRUCache<>(3);cache.put("a", "abstract");cache.put("b", "basic");cache.put("c", "call");cache.get("a");cache.put("d", "call");System.out.println(cache);

The limited cache capacity is 3. Four key-value pairs are added successively. The key that has not been accessed for the longest time is "B" and will be deleted. Therefore, the output is:

{c=call, a=abstract, d=call}

Implementation Principle

After understanding the usage of LinkedHashMap, let's look at its implementation code. For the code, we will describe this series of articles. If there is no additional description, it is based on JDK 7.

Internal components

LinkedHashMap is a subclass of HashMap. The following instance variables are added internally:

private transient Entry<K,V> header;private final boolean accessOrder;

AccessOrder indicates the order of access or insertion. Header indicates the head of the two-way linked list. Its Type Entry is an internal class, which is HashMap. the Child class of the Entry. Two variables before and after are added to point to the front and back of the linked list. The complete definition of the Entry is as follows:

private static class Entry<K,V> extends HashMap.Entry<K,V> {    Entry<K,V> before, after;    Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {        super(hash, key, value, next);    }    private void remove() {        before.after = after;        after.before = before;    }    private void addBefore(Entry<K,V> existingEntry) {        after  = existingEntry;        before = existingEntry.before;        before.after = this;        after.before = this;    }    void recordAccess(HashMap<K,V> m) {        LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;        if (lm.accessOrder) {            lm.modCount++;            remove();            addBefore(lm.header);        }    }    void recordRemoval(HashMap<K,V> m) {        remove();    }}

RecordAccess and recordRemoval are HashMap. methods defined in the Entry. In HashMap, the implementation of these two methods is empty. They are designed to be overwritten by the quilt class. When put is called and the key exists, hashMap calls the recordAccess method of the Entry. when the key is deleted, HashMap calls the recordRemoval method of the Entry.

LinkedHashMap. the Entry overrides these two methods. In recordAccess, if they are in the access order, the node is moved to the end of the linked list and removed from the linked list in recordRemoval.

After learning about the internal components, let's look at the operation method. Let's first look at the constructor method.

Constructor

In the construction method of HashMap, The init method is called. The init method is empty in the implementation of HashMap and is also designed for rewriting. LinkedHashMap overwrites this method to initialize the head node of the linked list. The Code is as follows:

void init() {    header = new Entry<>(-1, null, null, null);    header.before = header.after = header;}

The header is initialized as an Entry object, and both the front-end and the next-end point to itself, as shown in:


Header. after points to the first node, header. before points to the last node, and header points to indicate that the linked list is empty.

Put Method

In LinkedHashMap, The put method also adds nodes to the linked list. If the nodes are sorted by access, it also adjusts the nodes to the end and deletes the nodes that have not been accessed for a long time.

In the put implementation of HashMap, if it is a new key, it will call the addEntry method to add nodes. LinkedHashMap overwrites this method. The code is:

void addEntry(int hash, K key, V value, int bucketIndex) {    super.addEntry(hash, key, value, bucketIndex);    // Remove eldest entry if instructed    Entry<K,V> eldest = header.after;    if (removeEldestEntry(eldest)) {        removeEntryForKey(eldest.key);    }}

It first calls the addEntry method of the parent class. The addEntry method of the parent class calls createEntry to create a node, and the javashashmap overwrites createEntry. The Code is as follows:

void createEntry(int hash, K key, V value, int bucketIndex) {    HashMap.Entry<K,V> old = table[bucketIndex];    Entry<K,V> e = new Entry<>(hash, key, value, old);    table[bucketIndex] = e;    e.addBefore(header);    size++;}

Create a node, add it to the hash table, and add it to the linked list. The code added to the end of the linked list is:

e.addBefore(header)

For example, run the following code:

Map<String,Integer> countMap = new LinkedHashMap<>();countMap.put("hello", 1);

After execution, the graph structure is as follows:


After adding the node, call removeEldestEntry to check whether the old node should be deleted. If the return value is true, call removeEntryForKey to delete the node. removeEntryForKey is the method defined in HashMap. When deleting the node, HashMap is called. the recordRemoval method of the Entry, which is called LinkedHashMap. if the Entry is rewritten, the node is deleted from the linked list.

In the put implementation of HashMap, if the key already exists, the recordAccess method of the node, LinkedHashMap, will be called. the Entry overwrites this method. If the method is sequential by access, the node is adjusted to the end of the linked list.

Get Method

LinkedHashMap overwrites the get method. The code is:

public V get(Object key) {    Entry<K,V> e = (Entry<K,V>)getEntry(key);    if (e == null)        return null;    e.recordAccess(this);    return e.value;}

The difference between the get method and the HashMap method is that the recordAccess method of the node is called. If the access is ordered, recordAccess adjusts the node to the end of the linked list.

Check whether a value exists.

Check whether a HashMap contains a value that needs to be traversed. Because LinkedHashMap maintains a separate linked list, it can be used for more efficient traversal. The containsValue code is:

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

The code is relatively simple and will not be explained.

Principles

The above is the basic implementation principle of LinkedHashMap. It is a subclass of HashMap and its node class LinkedHashMap. the Entry is HashMap. entry sub-class, LinkedHashMap maintains a separate two-way linked list. Each node is located in the hash table and also in the two-way linked list. The order in the linked list is the insertion order by default, you can also configure the access sequence as LinkedHashMap and its node class LinkedHashMap. the Entry overrides several methods to maintain this relationship.

LinkedHashSet

The implementation classes of the Map interface described earlier have a corresponding Set interface implementation class. For example, HashMap has HashSet, TreeMap has TreeSet, and LinkedHashMap is no exception, it also has a corresponding Set interface implementation class LinkedHashSet. LinkedHashSet is a subclass of HashSet, but its internal Map implementation class is LinkedHashMap, so it can also maintain the insertion sequence, such:

Set<String> set = new LinkedHashSet<>();set.add("b");set.add("c");set.add("a");set.add("c");System.out.println(set);

Output:

[b, c, a]

The implementation of LinkedHashSet is relatively simple and we will not introduce it any more.

Summary

This section describes the usage and implementation principle of LinkedHashMap. In terms of usage, it can maintain the insertion sequence or access sequence. The insertion sequence is often used to process key-value pairs, it also maintains the input order and is often used in scenarios where the keys are sorted, which is more efficient than TreeMap, And the access order is often used to implement LRU cache. In principle, it is a subclass of HashMap, but there is a bidirectional linked list inside to maintain the node sequence.

Finally, we briefly introduce LinkedHashSet, which is a subclass of HashSet, but uses LinkedHashMap internally.

If you need a Map implementation class and the key type is Enumeration type, you can use HashMap, but you should use a dedicated implementation class EnumMap. Why? Let's discuss it in the next section.

----------------

For more information, see the latest article. Please pay attention to the Public Account "lauma says programming" (scan the QR code below), from entry to advanced, ma and you explore the essence of Java programming and computer technology. Retain All copyrights with original intent.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.