Java and Android LRU caching and implementation principles _android

Source: Internet
Author: User
Tags bitwise int size prev static class

I. Overview

Android provides a LRUCache class that can be easily used to implement LRU algorithm caching. Java provides LINKEDHASHMAP, you can use this class is very convenient to implement LRU algorithm, Java Lrulinkedhashmap is directly inherited Linkedhashmap, made a few changes can be implemented LRU algorithm.

Two, Java's LRU algorithm

Java's LRU algorithm is based on Linkedhashmap,linkedhashmap inherited HashMap, and hashmap based on a certain change to achieve the LRU algorithm.

1, HashMap

The first thing to note is that HashMap stores each node information in the entry<k,v> structure. The key, value, and hash information of the node is stored in the entry<k,v>, and the reference for the next node of the current node is stored. So entry<k,v> is a one-way linked list. The HASHMAP storage structure is a form of an array plus a one-way list. Each key corresponds to a hashcode that can be found in an array of hashmap, and if multiple keys correspond to the same hashcode, they correspond in the same position in the array, at which point HashMap will put the corresponding information in the ENTRY<K ,v> and use a linked list to connect these entry<k,v>.

 Static Class Entry<k,v> implements Map.entry<k,v> {final K key;
    V value;
    Entry<k,v> Next;

    int hash;
     /** * Creates new entry.
      */Entry (int h, K K, v V, entry<k,v> N) {value = V;
      Next = n;
      key = k;
    hash = h;
    Public final K Getkey () {return key;
    Public Final v. GetValue () {return value;
      Public final V SetValue (v newvalue) {v oldValue = value;
      value = newvalue;
    return oldValue; Public final Boolean equals (Object o) {if (!) (
      o instanceof Map.entry) return false;
      Map.entry e = (map.entry) o;
      Object K1 = Getkey ();
      Object K2 = E.getkey (); if (k1 = = K2 | | (K1!= null && k1.equals (K2)))
        {Object V1 = GetValue ();
        Object v2 = E.getvalue (); if (v1 = = V2 | |
          (v1!= null && v1.equals (v2)))
      return true;
    return false; Public final inT Hashcode () {return Objects.hashcode (Getkey ()) ^ Objects.hashcode (GetValue ());
    Public final String toString () {return getkey () + "=" + GetValue ();  /** * This is invoked whenever the ' value in a entry is * overwritten by a invocation of put (K,V)
     For a key K "s already * in the HASHMAP.  */void Recordaccess (hashmap<k,v> m) {}/** * this ' is invoked whenever the entry is *
     Removed from the table.

 */void Recordremoval (hashmap<k,v> m) {}}

Here's the code for the HashMap put method, and analyze it.

Public V-Put (K key, V value) {
    if (table = = empty_table) {
      inflatetable (threshold);
    }
    if (key = = null) return
      Putfornullkey (value);

The above information is not concerned, the following is the normal insertion logic.     

//First compute hashcode
    int hash = hash (key);
By calculating the hashcode, the position of the hashcode in the array
    int i = indexfor (hash, table.length)

is calculated. For loop, find out if there is a node in the HashMap, and the corresponding key is exactly the same as the incoming key. If it exists, the user wants to replace the value of the key, so it can be returned directly by replacing value. For
    (entry<k,v> e = table[i]; e!= null; e = e.next) {
      Object K;
      if (E.hash = = Hash && ((k = e.key) = = Key | | key.equals (k))) {
        V oldValue = e.value;
        E.value = value;
        E.recordaccess (this);
        Return OldValue
      }
    }

The logical execution here shows that there is no exact Kye in the HashMap. Call AddEntry, create a new node to save key, value information, and add to HashMap
    modcount++;
    AddEntry (hash, key, value, I);
    return null;
  }

Add some comments to the code above to have an understanding of the whole. Here are some points that are worth analyzing.

<1> int i = indexfor (hash, table.length);

Can look at the source code:

static int indexfor (int h, int length) {
    //Assert integer.bitcount (length) = = 1: "Length must is a non-zero power of 2 ";
    Return H & (length-1);
  }

Why is the hashcode (h) to be obtained and (LENGTH-1) bitwise-and-op? This is to ensure that H's high level information is removed. If the array size is 8 (1000) and the computed H value is 10 (1010), if you get the data of index 10 of the array directly, you will definitely throw the array out of bounds. So by using bitwise AND (0111&1010), the high level information is successfully eliminated, and 2 (0010) is obtained, representing the data in the corresponding array with index 2. The effect is the same as that of the remainder, but the efficiency of the bit operation is obviously higher.

But there is a problem, if the length is 9, get length-1 information of 8 (1000), so that the bitwise operation, not only can not clear high data, the result is certainly wrong. So there must be something special about the size of the array. By looking at the source, you can find that the HashMap is guaranteed that the number of the corresponding array of n times 2.

First, when put, call the Inflatetable method. The focus is on the RoundUpToPowerOf2 method, although its content contains a large number of bit-related operations and processing, not seen very clearly, but the annotation has been clear, will ensure that the number of arrays is 2 n times.

private void inflatetable (int tosize) {
//Find a power of 2 >= tosize
int capacity = ROUNDUPTOPOWEROF2 (tosize) ;

threshold = (int) math.min (capacity * Loadfactor, maximum_capacity + 1);
Table = new Entry[capacity];
Inithashseedasneeded (capacity);
}

Second, in other locations such as AddEntry, it will also use (2 * table.length), table.length << 1, and so on to ensure that the number of arrays is 2 n times.

<2> for (entry<k,v> e = table[i]; e!= null; e = e.next)

Because HashMap uses the form of an array-linked list, you get the position in the array by hashcode, not a entry<k,v>, but a entry<k,v> list, and you must cycle the list, Gets the value corresponding to the key.

<3> addentry (hash, key, value, I);

To determine whether the number of arrays exceeded the threshold, if more than, you need to increase the number of arrays. Then a new entry is created and added to the array.

/** * Adds A new entry with the specified key, value and hash code to * specified bucket.
   It is the responsibility of this * method to resize the table if appropriate.
   * * Subclass overrides this to alter the behavior. */void AddEntry (int hash, K key, V value, int bucketindex) {if (size >= threshold) && (null!= table[b
      Ucketindex]) {Resize (2 * table.length)); hash = (null!= key)?
      Hash (key): 0;
    Bucketindex = Indexfor (hash, table.length);
  } createentry (hash, key, value, Bucketindex); }/** * Like AddEntry except the This version is used when creating entries * as part of MAP construction or "PS Eudo-construction "(cloning, * deserialization).
   This version Needn ' t worry about resizing the table.
   * Subclass overrides this to alter the behavior of HASHMAP (MAP), * clone, and ReadObject. */void Createentry (int hash, K key, V value, int bucketindex) {entry<k,v> E =Table[bucketindex];
    Table[bucketindex] = new entry<> (hash, key, value, E);
  size++;

 }

2, Linkedhashmap

The Linkedhashmap was modified on the basis of HashMap. First, the entry is changed from one-way list to bidirectional linked list. Added references to before and after two teams entry.

 private static class Entry<k,v> extends Hashmap.entry<k,v> {//These fields comprise the doubly linked
    List used for iteration.

    Entry<k,v> before, after;
    Entry (int hash, K key, V value, hashmap.entry<k,v> next) {Super (hash, key, value, next);
     }/** * Removes this entry from the linked list.
      * * private void Remove () {before.after = after;
    After.before = before;
     }/** * Inserts this entry before the specified existing in the list.
      * * private void Addbefore (Entry<k,v> existingentry) {after = Existingentry;
      before = Existingentry.before;
      Before.after = this;
    After.before = this; }/** * This is invoked by the superclass whenever the value * of a pre-existing entry be read by Ma
     P.get or modified by Map.set. * If The enclosing Map is access-ordered, it moves the entry * to the end of the list; Otherwise, it does notHing.
      */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 ();

 }
  }

At the same time, Linkedhashmap provides a reference header for Entry (private transient entry<k,v> header). The header function is always just the head (header.after) and tail (Header.before) of all members in the HashMap. In this way, the HashMap itself is modified in the format of the array plus list. In the Linkedhashmap, the data save format of the HashMap array plus linked list is preserved, and a set header as the starting tag of the bidirectional linked list (which we call the header's bidirectional list) is added. Linkedhashmap is to realize LRU algorithm through the bidirectional linked list of header. Header.after always point to the most infrequently used node, delete, delete this header.after corresponding node. In contrast, Header.before is pointing to the node that has just been used.

Linkedhashmap did not provide the Put method, but Linkedhashmap rewrote the AddEntry and Createentry methods as follows:

/** * This override alters of behavior to put method. It causes newly * allocated entry to get inserted at the end of the linked list and * Removes the eldest entry if AP
   Propriate.

    * * 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); 
   }/** * This override differs from addentry into that it doesn ' t resize the * table or remove the eldest entry. * * void createentry (int hash, K key, V value, int bucketindex) {hashmap.entry<k,v> old = Table[bucketinde
    X];
    entry<k,v> e = new entry<> (hash, key, value, old);
    Table[bucketindex] = e;
    E.addbefore (header);
  size++; }

HashMap's Put method calls the AddEntry method, and the HashMap AddEntry method calls the Createentry method. Therefore, the above two methods can be put together with the contents of the HashMap, convenient analysis, forming the following methods:

void AddEntry (int hash, K key, V value, int bucketindex) {
    if (size >= threshold) && (null!= table[bucket Index]) {
      Resize (2 * table.length);
      hash = (null!= key)? Hash (key): 0;
      Bucketindex = Indexfor (hash, table.length);
    }

    hashmap.entry<k,v> old = Table[bucketindex];
    entry<k,v> e = new entry<> (hash, key, value, old);
    Table[bucketindex] = e;
    E.addbefore (header);
    size++;

    Remove eldest entry if instructed
    entry<k,v> eldest = Header.after;
    if (Removeeldestentry (eldest)) {
      removeentryforkey (eldest.key);
    }
  }

Again, first determine if the threshold is exceeded, and the number of arrays is increased. The entry object is then created and added to the hashmap corresponding array and linked list. Unlike HashMap, the Linkedhashmap adds E.addbefore (header) and Removeentryforkey (Eldest.key), two operations.

First, analyze the E.addbefore (header). where e is the Linkedhashmap.entry object, the Addbefore code is as follows, the header is associated with the current object, and the current object is added to the tail of the header's bidirectional list (Header.before):

private void Addbefore (Entry<k,v> existingentry) {after
      = Existingentry;
      before = Existingentry.before;
      Before.after = this;
      After.before = this;
    }

Followed by another key point, the code is as follows:

  Remove eldest entry if instructed
    entry<k,v> eldest = Header.after;
    if (Removeeldestentry (eldest)) {
      removeentryforkey (eldest.key);
    }

Among them, removeeldestentry to determine whether to delete the most recently used node. The Removeeldestentry (eldest) method in Linkedhashmap always returns false, and if we want to implement the LRU algorithm, we need to override this method to determine under what circumstances the most recently least used node is deleted. The function of Removeentryforkey is to delete the key node in the HASHMAP array plus list structure, the source code is as follows:

Final entry<k,v> Removeentryforkey (Object key) {
    if (size = = 0) {return
      null;
    }
    int hash = (key = null)? 0:hash (key);
    int i = indexfor (hash, table.length);
    Entry<k,v> prev = table[i];
    entry<k,v> e = prev;

    while (e!= null) {
      entry<k,v> next = e.next;
      Object K;
      if (E.hash = = Hash &&
        (k = e.key) = = Key | | (Key!= null && key.equals (k))) {
        modcount++;
        size--;
        if (prev = = e)
          table[i] = next;
        else
          prev.next = next;
        E.recordremoval (this);
        return e;
      }
      prev = e;
      e = next;
    }

    return e;
  }

Removeentryforkey is the HashMap method, Linkedhashmap in the header of the two-way linked list, and Linkedhashmap did not rewrite this method, the header of the two-way linked list how to deal with it.

With a closer look at the code, you can see that the E.recordremoval (this) was invoked after the node in HashMap was successfully deleted; This method is empty in HashMap, and Linkedhashmap entry implements this method. The two lines of code in the Remove () method delete the standard code for the current node in a doubly linked list, without explanation.

 /**
     * Removes this entry from the linked list.
     * *
    private void Remove () {
      before.after = after;
      After.before = before;
    } void Recordremoval (hashmap<k,v> m) {
      remove ();
    }

Above, Linkedhashmap adds the node's code analysis complete, can see the perfect to put the new node in the header bidirectional list end.

However, this is obviously a first-in, first-out algorithm, not the most frequently used algorithm recently. You need to update the header doubly linked list at get time and put the node just get in the end of the header bidirectional list. Let's look at the source of get:

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

Code is very short, and the first line of Getentry calls the HashMap Getentry method and does not need to be explained. The code that really handles the header doubly linked list is e.recordaccess (this). Take a look at the code:

/** * Removes this entry to the linked list.
      * * private void Remove () {before.after = after;
    After.before = before;
     }/** * Inserts this entry before the specified existing in the list.
      * * private void Addbefore (Entry<k,v> existingentry) {after = Existingentry;
      before = Existingentry.before;
      Before.after = this;
    After.before = this; }/** * This is invoked by the superclass whenever the value * of a pre-existing entry be read by Ma
     P.get or modified by Map.set. * If The enclosing Map is access-ordered, it moves the entry * to the end of the list;
     Otherwise, it does nothing.
      */void Recordaccess (hashmap<k,v> m) {linkedhashmap<k,v> LM = (linkedhashmap<k,v>) m;
        if (lm.accessorder) {lm.modcount++;
        Remove ();
      Addbefore (Lm.header); }
    }

First, delete the current node in the header bidirectional list, and then add the current node to the end of the header bidirectional list. Of course, when calling Linkedhashmap, you need to set the Accessorder to true, otherwise it is the FIFO algorithm.

Three, Android LRU algorithm

Android also provides HashMap and Linkedhashmap, and the general idea is somewhat similar, but the details of the implementation are markedly different. And Android offers a lrucache that uses linkedhashmap, but does not have the same idea. Java needs to rewrite Removeeldestentry to determine whether to delete a node, and Android needs to rewrite LRUCache's sizeof to return the size of the current node, and Android will judge whether it exceeds the limit based on that size. Make a call to the TrimToSize method to clear the redundant nodes.

The Android sizeof method returns 1 by default, and the default is to determine whether the number of data in the HashMap exceeds the set threshold. You can also override the sizeof method to return the size of the current node. The safesizeof of Android calls the sizeof method, and other methods of judging thresholds invoke the Safesizeof method to add and subtract operations and to determine thresholds. And then determine if you need to clear the node.

Java Removeeldestentry method, can also achieve the same effect. Java requires the user to provide the entire process of judgment, the two ideas are somewhat different.

Sizeof,safesizeof does not need to be explained, and the put and get methods, although not exactly the same as the Java implementation, but the idea is the same, do not need to analyze. At the end of the Put method in LRUCache, the TrimToSize method is called, which is used to clear the exceeded nodes. Its code is as follows:

public void trimtosize (int maxSize)
 {while
  (true)
  {
   Object key;
   Object value;
   Synchronized (this) {
    if (this.size < 0) | | | ((This.map.isEmpty ()) && (this.size!= 0)))       {
     throw new IllegalStateException (GetClass (). GetName () + ". SizeOf () is reporting inconsistent");
if (size <= maxSize) {break
;
}

    map.entry toevict = (map.entry) this.map.entrySet (). iterator (). Next ();

    Key = Toevict.getkey ();
    Value = Toevict.getvalue ();
    This.map.remove (key);
    This.size-= safesizeof (key, value);
    This.evictioncount + 1;
   }

   Entryremoved (True, key, value, NULL);
  }
 

The emphasis needs to be explained by map.entry toevict = (map.entry) this.map.entrySet (). iterator (). Next (); This line of code. The code in front of it determines whether you need to delete the most recently used node, and the following code deletes the specific node. This line of code is used to get the most recent node that is most infrequently used.

The first thing to be explained is that Android Linkedhashmap and Java Linkedhashmap, as well, use headers to save two-way lists. When you put and get, you update the corresponding node, save the Header.after point to the longest unused node, and Header.before to point to the node you just used. So map.entry toevict = (map.entry) this.map.entrySet (). iterator (). Next (); This line is definitely getting the Header.after node. The following step-by-step analysis of the code, you can see how it is achieved.

First, Map.entryset (), HashMap defines this method, Linkedhashmap does not override this method. Therefore, the HashMap corresponding method is invoked:

Public set<entry<k, V>> EntrySet () {
    set<entry<k, v>> es = entryset;
    return (es!= null)? Es: (entryset = new EntrySet ());
  }

The code above does not need to dwell on the new instance of a EntrySet class. And EntrySet is also defined in HashMap, Linkedhashmap.

 Private final class EntrySet extends Abstractset<entry<k, v>> {public I
    Terator<entry<k, V>> iterator () {return newentryiterator (); Public Boolean contains (Object o) {if (!) (
      o instanceof Entry) return false;
      entry<?,? > E = (entry<?,? >) o;
    Return containsmapping (E.getkey (), E.getvalue ()); public boolean remove (Object o) {if (!
      o instanceof Entry) return false;
      entry<?,? > E = (entry<?,? >) o;
    Return removemapping (E.getkey (), E.getvalue ());
    public int size () {return size;
    public Boolean IsEmpty () {return size = = 0;
    public void Clear () {HashMap.this.clear ();

} iterator<entry<k, V>> Newentryiterator () {return new Entryiterator ();} 

It is obvious in the code that map.entry toevict = (map.entry) this.map.entrySet (). iterator (). Next () is the call to

Newentryiterator (). Next () is called (New Entryiterator ()). Next (). The Entryiterator class is defined in Linkedhashmap.

  Private Final class Entryiterator extends Linkedhashiterator<map.entry<k, v>> {public final Map.
  Entry<k, V> next () {return nextentry ();} Private abstract class Linkedhashiterator<t> implements iterator<t> {linkedentry<k, v> next =
    HEADER.NXT;
    Linkedentry<k, v> lastreturned = null;

    int expectedmodcount = Modcount;
    Public Final Boolean Hasnext () {return next!= header; Final linkedentry<k, v> nextentry () {if (Modcount!= expectedmodcount) throw new Concurrentmod
      Ificationexception ();
      Linkedentry<k, v> e = next;
      if (e = header) throw new Nosuchelementexception ();
      Next = E.NXT;
    return lastreturned = e; Public final void Remove () {if (Modcount!= expectedmodcount) throw new concurrentmodificationexcept
      Ion ();
      if (lastreturned = null) throw new IllegalStateException (); Linkedhashmap.this.rEmove (Lastreturned.key);
      lastreturned = null;
    Expectedmodcount = Modcount;

 }
  }

It is now possible to conclude that the line of code in trimtosize gets the corresponding node of the Header.next, which is the most infrequently used node recently.

The above is the Android Java LRU cache Implementation of the principle of the detailed, follow-up to continue to supplement the relevant information, thank you for your support of this site!

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.