Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 說說Android LRU緩存算法實現學習筆記(一)

說說Android LRU緩存算法實現學習筆記(一)

編輯:關於Android編程

在我們的手機應用開發時候,我們經常會遇到大數據訪問的時候,我們通常會考慮以下幾個方面的情況。一、手機內存的限制還必須保證應用反應的流暢;二、盡量小的流量消耗,不然,你的應用流暢度再好體驗再好,用戶還是會毫不猶豫的卸載掉你的應用。大數據量訪問的情況下,數據緩存是我們一定會考慮到的解決方案。而作為緩存,我們很重要的會考慮以下幾點:1.訪問速度;2.逐出舊的緩存策略;3.最好還能考慮到一定的並發度。這篇我們主要說說LRU策略的緩存算法實現,我們就用圖片緩存為例來談談Android應用開發中的緩存實現。

首先我們來看看谷歌官方的推薦的緩存:在Android3.0加入的LruCache和 DiskLruCache(硬盤緩存結構)類。我們從代碼的實現知道,LruCache和DiskLruCache緩存的實現都是基於JDK的LinkedHashMap集合來實現。下面我們來從LinkedHashMap的源碼的分析來開始學習。

通過源碼我們知道,LinkedHashMap是繼承HashMap,底層結構不僅使用HashMap來保存元素,同時通過繼承HashMapEntry 實現雙向鏈表的結構來關聯其他的元素。我們先來看LInkedHashMap的節點實現:

    /**
     * LinkedHashMap entry.
     */
    private static class Entry extends HashMap.Entry {
        // These fields comprise the doubly linked list used for iteration.
        Entry before, after;
LinkedHashMap的節點Entry繼承自HashMap.Entry來實現,增加兩個引用來分別指向前一個元素和後一個元素。LinkedHashMap的實現是在HashMap的基礎上增加雙鏈表的結構。

我們再來看看LinkedHashMap的初始化,我們看到構造函數參數最多的情況:

/**
     * Constructs an empty LinkedHashMap instance with the
     * specified initial capacity, load factor and ordering mode.
     *
     * @param  initialCapacity the initial capacity
     * @param  loadFactor      the load factor
     * @param  accessOrder     the ordering mode - true for
     *         access-order, false 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; //accessOrder指定排序,默認為false,為fasle的時候,插入順序排序,為true時候,訪問順序排序
    }
我們看到重寫的基類的初始化方法init實習:

/**
     * Called by superclass constructors and pseudoconstructors (clone,
     * readObject) before any entries are inserted into the map.  Initializes
     * the chain.
     */
    void init() {
        header = new Entry(-1, null, null, null);
        header.before = header.after = header;
    }
此處初始化頭指針給循環指定。下面我們重點來看看集合最總要的兩個方法put和get的實現。我們先看put方法,LinkedHashMap的put方法並沒有重寫HashMap的put方法。只是重寫了put方法下的addEntry方法,addEntry方法執行時候是當LinkedHashMap插入新的結點的時候執行。

/**
     * This override alters behavior of superclass put method. It causes newly
     * allocated entry to get inserted at the end of the linked list and
     * removes the eldest entry if appropriate.
     */
    void addEntry(int hash, K key, V value, int bucketIndex) {
        createEntry(hash, key, value, bucketIndex);

        // Remove eldest entry if instructed, else grow capacity if appropriate
        Entry eldest = header.after;
        if (removeEldestEntry(eldest)) {
            removeEntryForKey(eldest.key);
        } else {
            if (size >= threshold)
                resize(2 * table.length);
        }
    }

    /**
     * This override differs from addEntry in that it doesn't resize the
     * table or remove the eldest entry.
     */
    void createEntry(int hash, K key, V value, int bucketIndex) {
        HashMap.Entry old = table[bucketIndex];
	Entry e = new Entry(hash, key, value, old);
        table[bucketIndex] = e;
        e.addBefore(header);
        size++;
    }
我們看我們產生新結點的方法creatEntry實現,我們先給找到哈希表上對應的位置,然後給新的Entry指定前後的節點。執行方法e.addBefore(header)代碼如下:

 /**
         * Inserts this entry before the specified existing entry in the list.
         */
        private void addBefore(Entry existingEntry) {
            after  = existingEntry;
            before = existingEntry.before;
            before.after = this;
            after.before = this;
        }
我們看到代碼實現的功能就是把新結點添加到header結點前。我們再回到addEntry的代碼實現,我們看 Entry eldest = header.after;if (removeEldestEntry(eldest))的執行,我們把header後結點理解成我們最舊的節點,removeEldestEntry(eldest)方法的實現:

protected boolean removeEldestEntry(Map.Entry eldest) {
        return false;
    }

removeEldestEntry方法執行默認返回false,意思就是我們LinkedHashMap默認不會逐去舊的節點。我們可以通過在子類重載來重寫removeEldestEntry的實現,來修改LRU策略。下面我們用圖示來表示put方法的執行一下代碼的執行過程:

	   map.put("22", "xxx"); 
首先map執行前的結構是這樣:

\

為了不讓圖上密密麻麻的都是引用箭頭亂竄,我省略了HashMap的很多Entry結點的before和after的指向,我們只是重點表示header的引用指向。我們執行方法map.put("22","xxx")後的數據結構變成這樣:

\


我們把key為22的節點通過hash算法,指定到eee的後面,同時我們把header的before指向修改為指向key為22的節點。分析完我們的put方法,我們接著來分析get方法的實現,代碼如下:

public V get(Object key) {
        Entry e = (Entry)getEntry(key);
        if (e == null)
            return null;
        e.recordAccess(this);
        return e.value;
    }
我們看到get方法很簡單,首先我們去Map取key對應的value是否存在,如果不存在,我們返回null。如果存在,我們執行e.recoedAccess(this)方法。

 /**
         * This method is invoked by the superclass whenever the value
         * of a pre-existing entry is read by Map.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 m) {
            LinkedHashMap lm = (LinkedHashMap)m;
            if (lm.accessOrder) { //此處的判斷LinkedHashMap的排序順序
                lm.modCount++;
                remove();
                addBefore(lm.header);
            }
        }
recodeAccess方法中,當我們的排序順序為false,為按插入排序順序的時候,我們直接退出方法。當我們的排序順序為訪問排序順序的時候,我們執行remove方法,把結點從鏈表裡切除出來,然後執行addBefore方法時候,我們把結點插入到header結點的前面。這樣,我們就實習每次根據訪問位置來排序的操作。

下面我們通過圖的例子來說明,當LinkedHashMap的排序為訪問排序的時候,我們get操作的過程,LinkedHashMap的數據結構的變化。我們執行的代碼如下:

put.get("15");//我們假設key為15對應的value為"bbb"
我們在執行完map.put("22","xxx")後,再執行put.get("15")的變化如下:

\


以上為執行get方法之後的代碼發生變化,"bbb”對應的節點其實沒有發生變化,只是在鏈表結構中的指針發生了變化,因此對應的迭代器訪問的時候,我們讀取位置不一樣。

因為我們可以知道我們LRU的節點肯定是在header的after指向節點。當我們重寫removeEldestEntry來修改LRU策略的時候,我們也是首先逐出header的after所指向的節點。然後,我們最後來看LinkedHashmap的迭代器的實現如下:

private abstract class LinkedHashIterator implements Iterator {
        Entry nextEntry    = header.after;
        Entry lastReturned = null;

        /**
         * The modCount value that the iterator believes that the backing
         * List should have.  If this expectation is violated, the iterator
         * has detected concurrent modification.
         */
        int expectedModCount = modCount;

        public boolean hasNext() {
            return nextEntry != header;
        }

        public void remove() {
            if (lastReturned == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();

            LinkedHashMap.this.remove(lastReturned.key);
            lastReturned = null;
            expectedModCount = modCount;
        }

        Entry nextEntry() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (nextEntry == header)
                throw new NoSuchElementException();

            Entry e = lastReturned = nextEntry;
            nextEntry = e.after; //此處我們可知我們的迭代器實現訪問節點的after引用指向的節點。
            return e;
        }
    }
從LinkedHashMap實現的實現的迭代器內部類LinkedHashIterator看,我們迭代器的元素訪問順序是從header節點開始往after方向開始迭代。以上為對LinkedHashMap的分析簡單分析,下一節我們會針對LinkedHashMap的數據結構的特性來說說,用LinkedHashMap結構實現緩沖的快速訪問的優勢,還會結合常用的開源實現,來說說實現一定並發的Android下緩存實現。









  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved