LRUMap 源代碼實現解讀

本文通過對Apache Commons Collections 項目中LRUMap這個集合類的源代碼進行詳細解讀,爲幫助大家更好的瞭解這個集合類的實現原理以及使用如何該集合類。

首先介紹一下LRU算法. LRU是由Least Recently Used的首字母組成,表示最近最少使用的含義,一般使用在對象淘汰算法上。也是比較常見的一種淘汰算法。

 

LRUMap 則是實現的LRP算法的Map集合類,它繼承於AbstractLinkedMap 抽象類。LRUMap的使用說明如下:

LRUMap的初始化時需要指定最大集合元素個數,新增的元素個數大於允許的最大集合個數時,則會執行LRU淘汰算法。所有的元素在LRUMap中會根據最近使用情況進行排序。最近使用的會放在元素的最前面(LRUMap是通過鏈表來存儲元素內容). 所以LRUMap進行淘汰時只需要刪除鏈表最後一個即可(即header.after所指的元素對象)

那麼那些操作會影響元素的使用情況:

1.       put 當新增加一個集合元素對象,則表示該對象是最近被訪問的

2.       get 操作會把當前訪問的元素對象作爲最近被訪問的,會被移到鏈接表頭

注:當執行containsKeycontainsValue操作時,不會影響元素的訪問情況。

               LRUMap也是非線程安全。在多線程下使用可通過 Collections.synchronizedMap(Map)操作來保證線程安全。

 

LRUMap的一個簡單使用示例:

    public static void main(String[] args) {

       

        LRUMap lruMap = new LRUMap(2);

       

        lruMap.put("a1", "1");

        lruMap.put("a2", "2");

        lruMap.get("a1");//mark as recent used

        lruMap.put("a3", "3");

        System.out.println(lruMap);

    }

               上面的示例,當增加”a3”值時,會淘汰最近最少使用的”a2”, 最後輸出的結果爲:

                                   {a1=1, a3=3}

 

下面根據LRUMap的源碼來解讀一下LRUMap的實現原理

                                              整體類圖

類圖:

LRUMap類的關鍵代碼說明如下:

1.       get操作

    public Object get(Object key) {

        LinkEntry entry = (LinkEntry) getEntry(key);

        if (entry == null) {

            return null;

        }

        moveToMRU(entry); //執行LRU操作

        return entry.getValue();

}

moveToMRU方法代碼如下:

    protected void moveToMRU(LinkEntry entry) {

        if (entry.after != header) {

            modCount++;

            // remove 從鏈接中移除當前節點

            entry.before.after = entry.after;

            entry.after.before = entry.before;

            // add first 把節點增加到鏈接頭部

            entry.after = header;

            entry.before = header.before;

            header.before.after = entry;

            header.before = entry;

        } else if (entry == header) {

            throw new IllegalStateException("Can't move header to MRU" +

                " (please report this to [email protected])");

        }

    }

2.       put新增操作

  由於繼承的AbstractLinkedMap,所以put操作會調用addMapping 方法,完整代碼如下:

    protected void addMapping(int hashIndex, int hashCode, Object key, Object value) {

        if (isFull()) {

            LinkEntry reuse = header.after;

            boolean removeLRUEntry = false;

            if (scanUntilRemovable) {

                while (reuse != header && reuse != null) {

                    if (removeLRU(reuse)) {

                        removeLRUEntry = true;

                        break;

                    }

                    reuse = reuse.after;

                }

                if (reuse == null) {

                    throw new IllegalStateException(

                        "Entry.after=null, header.after" + header.after + " header.before" + header.before +

                        " key=" + key + " value=" + value + " size=" + size + " maxSize=" + maxSize +

                        " Please check that your keys are immutable, and that you have used synchronization properly." +

                        " If so, then please report this to [email protected] as a bug.");

                }

            } else {

                removeLRUEntry = removeLRU(reuse);

            }

           

            if (removeLRUEntry) {

                if (reuse == null) {

                    throw new IllegalStateException(

                        "reuse=null, header.after=" + header.after + " header.before" + header.before +

                         " key=" + key + " value=" + value + " size=" + size + " maxSize=" + maxSize +

                         " Please check that your keys are immutable, and that you have used synchronization properly." +

                         " If so, then please report this to [email protected] as a bug.");

                }

                reuseMapping(reuse, hashIndex, hashCode, key, value);

            } else {

                super.addMapping(hashIndex, hashCode, key, value);

            }

        } else {

            super.addMapping(hashIndex, hashCode, key, value);

        }

    }

          當集合的個數超過指定的最大個數時,會調用 reuseMapping函數,把要刪除的對象的keyvalue更新爲新加入的值,並再次加入到鏈接表中,並重新排定次序。

reuseMapping函數代碼如下:

    protected void reuseMapping(LinkEntry entry, int hashIndex, int hashCode, Object key, Object value) {

        // find the entry before the entry specified in the hash table

        // remember that the parameters (except the first) refer to the new entry,

        // not the old one

        try {

            int removeIndex = hashIndex(entry.hashCode, data.length);

            HashEntry[] tmp = data// may protect against some sync issues

            HashEntry loop = tmp[removeIndex];

            HashEntry previous = null;

            while (loop != entry && loop != null) {

                previous = loop;

                loop = loop.next;

            }

            if (loop == null) {

                throw new IllegalStateException(

                    "Entry.next=null, data[removeIndex]=" + data[removeIndex] + " previous=" + previous +

                    " key=" + key + " value=" + value + " size=" + size + " maxSize=" + maxSize +

                    " Please check that your keys are immutable, and that you have used synchronization properly." +

                    " If so, then please report this to [email protected] as a bug.");

            }

           

            // reuse the entry

            modCount++;

            removeEntry(entry, removeIndex, previous);

            reuseEntry(entry, hashIndex, hashCode, key, value);

            addEntry(entry, hashIndex);

        } catch (NullPointerException ex) {

            throw new IllegalStateException(

                    "NPE, entry=" + entry + " entryIsHeader=" + (entry==header) +

                    " key=" + key + " value=" + value + " size=" + size + " maxSize=" + maxSize +

                    " Please check that your keys are immutable, and that you have used synchronization properly." +

                    " If so, then please report this to [email protected] as a bug.");

        }

    }

    上面的代碼中:

          removeEntry 方法是刪除最近最少使用的節點在鏈接中的引用

    reuseEntry方法則把該節點的keyvalue賦新加入的節點的keyvalue

    addEntry 方法則把該節點加入到鏈接表,並保障相關的鏈接順序

    /**

     * Adds an entry into this map, maintaining insertion order.

     * <p>

     * This implementation adds the entry to the data storage table and

    * to the end of the linked list.

     *

     * @param entry the entry to add

     * @param hashIndex the index into the data array to store at

     */

    protected void addEntry(HashEntry entry, int hashIndex) {

        LinkEntry link = (LinkEntry) entry;

        link.after = header;

        link.before = header.before;

        header.before.after = link;

        header.before = link;

        data[hashIndex] = entry;

 }

       LRUMap的主要源碼實現就解讀到這裏,實現思路還是比較好理解的。

LRUMap的使用場景會比較多,例如可以很方便的幫我們實現基於內存的 LRU 緩存服務實現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章