編輯:關於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 EntryLinkedHashMap的節點Entry繼承自HashMap.Entry來實現,增加兩個引用來分別指向前一個元素和後一個元素。LinkedHashMap的實現是在HashMap的基礎上增加雙鏈表的結構。extends HashMap.Entry { // These fields comprise the doubly linked list used for iteration. Entry before, after;
我們再來看看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此處初始化頭指針給循環指定。下面我們重點來看看集合最總要的兩個方法put和get的實現。我們先看put方法,LinkedHashMap的put方法並沒有重寫HashMap的put方法。只是重寫了put方法下的addEntry方法,addEntry方法執行時候是當LinkedHashMap插入新的結點的時候執行。(-1, null, null, null); header.before = header.after = header; }
/** * 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我們看我們產生新結點的方法creatEntry實現,我們先給找到哈希表上對應的位置,然後給新的Entry指定前後的節點。執行方法e.addBefore(header)代碼如下: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++; }
/** * Inserts this entry before the specified existing entry in the list. */ private void addBefore(Entry我們看到代碼實現的功能就是把新結點添加到header結點前。我們再回到addEntry的代碼實現,我們看 EntryexistingEntry) { after = existingEntry; before = existingEntry.before; before.after = this; after.before = this; }
protected boolean removeEldestEntry(Map.Entryeldest) { 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我們看到get方法很簡單,首先我們去Map取key對應的value是否存在,如果不存在,我們返回null。如果存在,我們執行e.recoedAccess(this)方法。e = (Entry )getEntry(key); if (e == null) return null; e.recordAccess(this); return e.value; }
/** * 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(HashMaprecodeAccess方法中,當我們的排序順序為false,為按插入排序順序的時候,我們直接退出方法。當我們的排序順序為訪問排序順序的時候,我們執行remove方法,把結點從鏈表裡切除出來,然後執行addBefore方法時候,我們把結點插入到header結點的前面。這樣,我們就實習每次根據訪問位置來排序的操作。m) { LinkedHashMap lm = (LinkedHashMap )m; if (lm.accessOrder) { //此處的判斷LinkedHashMap的排序順序 lm.modCount++; remove(); addBefore(lm.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從LinkedHashMap實現的實現的迭代器內部類LinkedHashIterator看,我們迭代器的元素訪問順序是從header節點開始往after方向開始迭代。以上為對LinkedHashMap的分析簡單分析,下一節我們會針對LinkedHashMap的數據結構的特性來說說,用LinkedHashMap結構實現緩沖的快速訪問的優勢,還會結合常用的開源實現,來說說實現一定並發的Android下緩存實現。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; } }
前言Runtime是一套比較底層的純C語言API,包含了很多底層的C語言API。在我們平時編寫的OC代碼中,程序運行時,其實最終都是轉成了Runtime的C語言代碼。Ru
P2 正菜環節今天上的是一個通知的工具庫,作者寫的比較全,使用起來頁比較方便,而且內容少,直接Copy就好了。(國內很多廠商有自己的ROM定制,還有些野生大牛的自己創作,
寫在前面因為有這樣的一個場景,需要實現豎直方向的多色進度條,然後在網上也找了下,沒看到符合需要的,於是自定義了一個,效果如下:具體實現本來想定義水平的,然後旋轉一下,後來
隨著現在手機硬件不斷的提升,分辨率提高手機的安裝包也是越來越大了。當年NOKIA,MOTO時代,一個手機APP如果有1MB那都是算大的,2MB已經不得了了。雖然網