說說Android LRU緩衝演算法實現學習筆記(一),androidlru

來源:互聯網
上載者:User

說說Android LRU緩衝演算法實現學習筆記(一),androidlru

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

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

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

    /**     * LinkedHashMap 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;
LinkedHashMap的節點Entry繼承自HashMap.Entry來實現,增加兩個引用來分別指向前一個元素和後一個元素。LinkedHashMap的實現是在HashMap的基礎上增加雙鏈表的結構。

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

/**     * Constructs an empty <tt>LinkedHashMap</tt> 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 - <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; //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<K,V>(-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<K,V> 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<K,V> old = table[bucketIndex];Entry<K,V> e = new Entry<K,V>(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<K,V> existingEntry) {            after  = existingEntry;            before = existingEntry.before;            before.after = this;            after.before = this;        }
我們看到代碼實現的功能就是把新結點添加到header結點前。我們再回到addEntry的代碼實現,我們看 Entry<K,V> eldest = header.after;if (removeEldestEntry(eldest))的執行,我們把header後結點理解成我們最舊的節點,removeEldestEntry(eldest)方法的實現:

protected boolean removeEldestEntry(Map.Entry<K,V> 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<K,V> e = (Entry<K,V>)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<K,V> m) {            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)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<T> implements Iterator<T> {        Entry<K,V> nextEntry    = header.after;        Entry<K,V> 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<K,V> nextEntry() {            if (modCount != expectedModCount)                throw new ConcurrentModificationException();            if (nextEntry == header)                throw new NoSuchElementException();            Entry<K,V> e = lastReturned = nextEntry;            nextEntry = e.after; //此處我們可知我們的迭代器實現訪問節點的after引用指向的節點。            return e;        }    }
從LinkedHashMap實現的實現的迭代器內部類LinkedHashIterator看,我們迭代器的元素訪問順序是從header節點開始往after方向開始迭代。以上為對LinkedHashMap的分析簡單分析,下一節我們會針對LinkedHashMap的資料結構的特性來說說,用LinkedHashMap結構實現緩衝的快速存取的優勢,還會結合常用的開源實現,來說說實現一定並發的Android下緩衝實現。









聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

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.