第二章 Google guava cache源碼解析1--構建緩存器

一、guava cachehtml

  • 當下最經常使用最簡單的本地緩存
  • 線程安全本地緩存
  • 相似於ConcurrentHashMap(或者說成就是一個ConcurrentHashMap,只是在其上多添加了一些功能)

二、使用實例java

具體在實際中使用的例子,去查看《第七章 企業項目開發--本地緩存guava cache》,下面只列出測試實例:算法

import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

public class Hello{
    
    LoadingCache<String, String> testCache = CacheBuilder.newBuilder()
            .expireAfterWrite(20, TimeUnit.MINUTES)// 緩存20分鐘
            .maximumSize(1000)// 最多緩存1000個對象
            .build(new CacheLoader<String, String>() {
                public String load(String key) throws Exception {
                    if(key.equals("hi")){
                        return null;
                    }
                    return key+"-world";
                }
            });
    
    public static void main(String[] args){
        Hello hello = new Hello();
        System.out.println(hello.testCache.getIfPresent("hello"));//null
        hello.testCache.put("123", "nana");//存放緩存
        System.out.println(hello.testCache.getIfPresent("123"));//nana
        try {
            System.out.println(hello.testCache.get("hello"));//hello-world
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println(hello.testCache.getIfPresent("hello"));//hello-world
        /***********測試null*************/
        System.out.println(hello.testCache.getIfPresent("hi"));//null
        try {
            System.out.println(hello.testCache.get("hi"));//拋異常
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        
    }
}
View Code

在這個方法中,基本已經覆蓋了guava cache經常使用的部分。數組

  • 構造緩存器
    • 緩存器的構建沒有使用構造器而不是使用了構建器模式,這是在存在多個可選參數的時候,最合適的一種配置參數的方式,具體參看《effective Java(第二版)》第二條建議。
  • 經常使用的三個方法
    • get(Object key)
    • getIfPresent(Object key)
    • put(Object key, Object value)

三、源代碼緩存

在閱讀源代碼以前,強烈建議,先看一下"Java併發包類源碼解析"中的《第二章 ConcurrentHashMap源碼解析》,連接以下:安全

http://www.cnblogs.com/java-zhao/p/5113317.html數據結構

對於源碼部分,因爲整個代碼的核心類LocalCache有5000多行,因此只介紹上邊給出的實例部分的相關源碼解析。本節只說一下緩存器的構建,即以下代碼部分:併發

    LoadingCache<String, String> testCache = CacheBuilder.newBuilder()
            .expireAfterWrite(20, TimeUnit.MINUTES)// 緩存20分鐘(時間起點:entry的建立或替換(即修改))
            //.expireAfterAccess(10, TimeUnit.MINUTES)//緩存10分鐘(時間起點:entry的建立或替換(即修改)或最後一次訪問)
            .maximumSize(1000)// 最多緩存1000個對象
            .build(new CacheLoader<String, String>() {
                public String load(String key) throws Exception {
                    if(key.equals("hi")){
                        return null;
                    }
                    return key+"-world";
                }
            });
View Code

說明:該代碼的load()方法會在以後將get(Object key)的時候再說,這裏先不說了。ide

對於這一起,因爲guava cache這一起的代碼雖然不難,可是容易看的跑偏,一下子就不知道跑到哪裏去了,因此我下邊先給出guava cache的數據結構以及上述代碼的執行流程,而後你們帶着這個數據結構和執行流程去分析下邊的源代碼,分析完源代碼以後,我在最後還會再將cache的數據結構和構建緩存器的執行流程給出,並會結合咱們給出的開頭實例代碼來套一下整個流程,最後畫出初始化構建出來的緩存器(其實,這個緩存器就是上邊以及文末給出的cache的數據結構圖)。post

 

guava cache的數據結構圖:

須要說明的是:

  • 每個Segment中的有效隊列(廢棄隊列不算)的個數最多可能不止一個
  • 上圖與ConcurrentHashMap及其相似,其中的ReferenceEntry[i]用於存放key-value
  • 每個ReferenceEntry[i]都會存放一個鏈表,固然採用的也是Entry替換的方式。
  • 隊列用於實現LRU緩存回收算法
  • 多個Segment之間互不打擾,能夠併發執行
  • 各個Segment的擴容只須要擴本身的就好,與其餘Segment無關
  • 根據須要設置好初始化容量與併發水平參數,能夠有效避免擴容帶來的昂貴代價,可是設置的太大了,又會耗費不少內存,要衡量好

後邊三條與ConcurrentHashMap同樣

 

guava cache的數據結構的構建流程:

1)構建CacheBuilder實例cacheBuilder

2)cacheBuilder實例指定緩存器LocalCache的初始化參數

3)cacheBuilder實例使用build()方法建立LocalCache實例(簡單說成這樣,實際上覆雜一些)

3.1)首先爲各個類變量賦值(經過第二步中cacheBuilder指定的初始化參數以及本來就定義好的一堆常量)

3.2)以後建立Segment數組

3.3)最後初始化每個Segment[i]

3.3.1)爲Segment屬性賦值

3.3.2)初始化Segment中的table,即一個ReferenceEntry數組(每個key-value就是一個ReferenceEntry)

3.3.3)根據以前類變量的賦值狀況,建立相應隊列,用於LRU緩存回收算法

 

類結構:(這個不看也罷)

 

  • CacheBuilder:設置LocalCache的相關參數,並建立LocalCache實例
  • CacheLoader:有用的部分就是一個load(),用於實現"取緩存-->若不存在,先計算,在緩存-->取緩存"的原子操做
  • LocalCache:整個guava cache的核心類,包含了guava cache的數據結構以及基本的緩存的操做方法
  • LocalLoadingCache:LocalCache的一個靜態內部類,這裏的get(K key)是外部調用get(K key)入口
  • LoadingCache接口:繼承於Cache接口,定義了get(K key)
  • Cache接口:定義了getIfPresent(Object key)和put(K key, V value)
  • LocalManualCache:LocalCache的一個靜態內部類,是LocalLoadingCache的父類,這裏的getIfPresent(Object key)和put(K key, V value)也是外部方法的入口

關於上邊的這些說明,結合以後的源碼進行看就行了。

注:若是在源碼中有一些註釋與最後的套例子的註釋不一樣的話,之後者爲準

3.一、構建CacheBuilder+爲LocalCache設置相關參數+建立LocalCache實例

CacheBuilder的一些屬性:

    private static final int DEFAULT_INITIAL_CAPACITY = 16;//用於計算每一個Segment中的hashtable的大小
    private static final int DEFAULT_CONCURRENCY_LEVEL = 4;//用於計算有幾個Segment
    private static final int DEFAULT_EXPIRATION_NANOS = 0;//默認的緩存過時時間
    
    static final int UNSET_INT = -1;
    
    int initialCapacity = UNSET_INT;//用於計算每一個Segment中的hashtable的大小
    int concurrencyLevel = UNSET_INT;//用於計算有幾個Segment
    long maximumSize = UNSET_INT;//cache中最多能存放的緩存entry個數
    long maximumWeight = UNSET_INT;
    
    Strength keyStrength;//鍵的引用類型(strong、weak、soft)
    Strength valueStrength;//值的引用類型(strong、weak、soft)

    long expireAfterWriteNanos = UNSET_INT;//緩存超時時間(起點:緩存被建立或被修改)
    long expireAfterAccessNanos = UNSET_INT;//緩存超時時間(起點:緩存被建立或被修改或被訪問)
View Code

CacheBuilder-->newCacheBuilder():建立一個CacheBuilder實例

    /**
     * 採用默認的設置(以下)創造一個新的CacheBuilder實例
     * 一、strong keys
     * 二、strong values
     * 三、no automatic eviction of any kind.
     */
    public static CacheBuilder<Object, Object> newBuilder() {
        return new CacheBuilder<Object, Object>();//new 一個實例
    }
View Code

接下來,使用構建器模式指定一些屬性值(這裏的話,就是超時時間:expireAfterWriteNanos+cache中最多能放置的entry個數:maximumSize),這裏的entry指的就是一個緩存(key-value對)

CacheBuilder-->expireAfterWrite(long duration, TimeUnit unit)

    /**
     * 指明每個entry(key-value)在緩存中的過時時間
     * 一、時間的參考起點:entry的建立或值的修改
     * 二、過時的entry也許會被計入緩存個數size(也就是說緩存個數不只僅只有存活的entry)
     * 三、可是過時的entry永遠不會被讀寫
     */
    public CacheBuilder<K, V> expireAfterWrite(long duration, TimeUnit unit) {
        /*
         * 檢查以前是否已經設置過緩存超時時間
         */
        checkState(expireAfterWriteNanos == UNSET_INT,//正確條件:以前沒有設置過緩存超時時間
                   "expireAfterWrite was already set to %s ns",//不符合正確條件的錯誤信息
                   expireAfterWriteNanos);
        /*
         * 檢查設置的超時時間是否大於等於0,固然,一般狀況下,咱們不會設置緩存爲0
         */
        checkArgument(duration >= 0, //正確條件
                      "duration cannot be negative: %s %s",//不符合正確條件的錯誤信息,下邊的是錯誤信息中的錯誤參數
                      duration, 
                      unit);
        this.expireAfterWriteNanos = unit.toNanos(duration);//根據輸入的時間值與時間單位,將時間值轉換爲納秒
        return this;
    }
View Code

注意:

  • 設置超時時間,注意時間的起點是entry的建立或替換(修改)
  • expireAfterAccess(long duration, TimeUnit unit)方法的時間起點:entry的建立或替換(修改)或被訪問

CacheBuilder-->maximumSize(long size)

    /**
     * 指定cache中最多能存放的entry(key-value)個數maximumSize
     * 注意:
     * 一、在entry個數還未達到這個指定個數maximumSize的時候,可能就會發生緩存回收
     * 上邊這種狀況發生在cache size接近指定個數maximumSize,
     * cache就會回收那些不多會再被用到的緩存(這些緩存會使最近沒有被用到或不多用到的),其實說白了就是LRU算法回收緩存
     * 二、maximumSize與maximumWeight不能一塊兒使用,其實後者也不多會使用
     */
    public CacheBuilder<K, V> maximumSize(long size) {
        /* 檢查maximumSize是否已經被設置過了 */
        checkState(this.maximumSize == UNSET_INT,
                   "maximum size was already set to %s", 
                   this.maximumSize);
        /* 檢查maximumWeight是否已經被設置過了(這就是上邊說的第二條)*/
        checkState(this.maximumWeight == UNSET_INT,
                   "maximum weight was already set to %s", 
                   this.maximumWeight);
        /* 這是與maximumWeight配合的一個屬性 */
        checkState(this.weigher == null,
                   "maximum size can not be combined with weigher");
        /* 檢查設置的maximumSize是否是>=0,一般不會設置爲0,不然不會起到緩存做用 */
        checkArgument(size >= 0, "maximum size must not be negative");
        this.maximumSize = size;
        return this;
    }
View Code

注意:

  • 設置整個cache(而非每一個Segment)中最多可存放的entry的個數

CacheBuilder-->build(CacheLoader<? super K1, V1> loader)

    /**
     * 創建一個cache,該緩存器經過使用傳入的CacheLoader,
     * 既能夠獲取已給定key的value,也可以自動的計算和獲取緩存(這說的就是get(Object key)的三步原子操做)
     * 固然,這裏是線程安全的,線程安全的運行方式與ConcurrentHashMap一致
     */
    public <K1 extends K, V1 extends V> LoadingCache<K1, V1> build(CacheLoader<? super K1, V1> loader) {
        checkWeightWithWeigher();
        return new LocalCache.LocalLoadingCache<K1, V1>(this, loader);
    }
View Code

注意:

  • 要看懂該方法,須要瞭解一些泛型方法的使用方式與泛型限界
  • 該方法的返回值是一個LoadingCache接口的實現類LocalLoadingCache實例
  • 在build方法須要傳入一個CacheLoader的實例,實際使用中使用了匿名內部類來實現的,源碼的話,就是一個無參構造器,什麼也沒作,傳入CacheLoader實例的意義就是"類結構"部分所說的load()方法

 在上邊調用build時,整個代碼的執行權其實就交給了LocalCache.

 

3.二、LocalCache

LocalLoadingCahe構造器

    static class LocalLoadingCache<K, V> extends LocalManualCache<K, V>
                                         implements LoadingCache<K, V> {

        LocalLoadingCache(CacheBuilder<? super K, ? super V> builder,
                          CacheLoader<? super K, V> loader) {
            super(new LocalCache<K, V>(builder, checkNotNull(loader)));
        }
View Code

說明:在該內部類的無參構造器的調用中,

1)首先要保證傳入的CacheLoader實例非空,

2)其次建立了一個LocalCache的實例出來,

3)最後調用父類LocalManualCache的私有構造器將第二步建立出來的LocalCache實例賦給LocalCache的類變量,完成初始化。

這裏最重要的就是第二步,下面着重講第二步:

LocalCache的一些屬性

    /** 最大容量(2的30次方),即最多可存放2的30次方個entry(key-value) */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /** 最多多少個Segment(2的16次方)*/
    static final int MAX_SEGMENTS = 1 << 16;

    /** 用於選擇Segment */
    final int segmentMask;

    /** 用於選擇Segment,儘可能將hash打散 */
    final int segmentShift;

    /** 底層數據結構,就是一個Segment數組,而每個Segment就是一個hashtable */
    final Segment<K, V>[] segments;

    /** 
     * 併發水平,這是一個用於計算Segment個數的一個數,
     * Segment個數是一個剛剛大於或等於concurrencyLevel的數
     */
    final int concurrencyLevel;

    /** 鍵的引用類型(strong、weak、soft) */
    final Strength keyStrength;

    /** 值的引用類型(strong、weak、soft) */
    final Strength valueStrength;

    /** The maximum weight of this map. UNSET_INT if there is no maximum. 
     * 若是沒有設置,就是-1
     */
    final long maxWeight;

    final long expireAfterAccessNanos;

    final long expireAfterWriteNanos;

    /** Factory used to create new entries. */
    final EntryFactory entryFactory;

    /** 默認的緩存加載器,用於作一些緩存加載操做(其實就是load),實現三步原子操做*/
    @Nullable
    final CacheLoader<? super K, V> defaultLoader;

    /** 默認的緩存加載器,用於作一些緩存加載操做(其實就是load),實現三步原子操做*/
    @Nullable
    final CacheLoader<? super K, V> defaultLoader;
View Code

說明:關於這些屬性的含義,看註釋+CacheBuilder部分的屬性註釋+ConcurrentHashMap的屬性註釋

 

LocalCache-->LocalCache(CacheBuilder, CacheLoader)

    /**
     * 建立一個新的、空的map(而且指定策略、初始化容量和併發水平)
     */
    LocalCache(CacheBuilder<? super K, ? super V> builder,
               @Nullable CacheLoader<? super K, V> loader) {
        /*
         * 默認併發水平是4,即四個Segment(但要注意concurrencyLevel不必定等於Segment個數)
         * Segment個數:一個剛剛大於或等於concurrencyLevel且是2的幾回方的一個數
         */
        concurrencyLevel = Math
                .min(builder.getConcurrencyLevel(), MAX_SEGMENTS);

        keyStrength = builder.getKeyStrength();//默認爲Strong,即強引用
        valueStrength = builder.getValueStrength();//默認爲Strong,即強引用

        // 緩存超時(時間起點:entry的建立或替換(即修改))
        expireAfterWriteNanos = builder.getExpireAfterWriteNanos();
        // 緩存超時(時間起點:entry的建立或替換(即修改)或最後一次訪問)
        expireAfterAccessNanos = builder.getExpireAfterAccessNanos();
        //建立entry的工廠
        entryFactory = EntryFactory.getFactory(keyStrength,
                                                  usesAccessEntries(), 
                                                  usesWriteEntries());
        //默認的緩存加載器
        defaultLoader = loader;

        // 初始化容量爲16,整個cache能夠放16個緩存entry
        int initialCapacity = Math.min(builder.getInitialCapacity(),
                                       MAXIMUM_CAPACITY);

        int segmentShift = 0;
        int segmentCount = 1;
        //循環條件的&&後邊的內容是關於weight的,因爲沒有設置maxWeight,因此其值爲-1-->evictsBySize()返回false
        while (segmentCount < concurrencyLevel
                && (!evictsBySize() || segmentCount * 20 <= maxWeight)) {
            ++segmentShift;
            segmentCount <<= 1;//找一個剛剛大於或等於concurrencyLevel的Segment數
        }
        this.segmentShift = 32 - segmentShift;
        segmentMask = segmentCount - 1;

        this.segments = newSegmentArray(segmentCount);//建立指定大小的數組

        int segmentCapacity = initialCapacity / segmentCount;//計算每個Segment中的容量的值,剛剛大於等於initialCapacity/segmentCount
        if (segmentCapacity * segmentCount < initialCapacity) {
            ++segmentCapacity;
        }

        int segmentSize = 1;//每個Segment的容量
        while (segmentSize < segmentCapacity) {
            segmentSize <<= 1;//剛剛>=segmentCapacity&&是2的幾回方的數
        }

        if (evictsBySize()) {//因爲沒有設置maxWeight,因此其值爲-1-->evictsBySize()返回false
            // Ensure sum of segment max weights = overall max weights
            long maxSegmentWeight = maxWeight / segmentCount + 1;
            long remainder = maxWeight % segmentCount;
            for (int i = 0; i < this.segments.length; ++i) {
                if (i == remainder) {
                    maxSegmentWeight--;
                }
                this.segments[i] = createSegment(segmentSize, 
                                                 maxSegmentWeight,
                                                 builder.getStatsCounterSupplier().get());
            }
        } else {
            for (int i = 0; i < this.segments.length; ++i) {
                this.segments[i] = createSegment(segmentSize, 
                                                 UNSET_INT,
                                                 builder.getStatsCounterSupplier().get());
            }
        }
    }
View Code

說明:這裏的代碼就是整個LocalCache實例的建立過程,很是重要!!!

 

下面介紹在LocalCache(CacheBuilder, CacheLoader)中調用的一些方法:

  • CacheBuilder-->getConcurrencyLevel()
    int getConcurrencyLevel() {
            return (concurrencyLevel == UNSET_INT) ? //是否設置了concurrencyLevel
                    DEFAULT_CONCURRENCY_LEVEL//若是沒有設置,採用默認值16
                    : concurrencyLevel;//若是設置了,採用設置的值
        }
    View Code
    說明:檢查是否設置了concurrencyLevel,若是設置了,採用設置的值,若是沒有設置,採用默認值16

 

  • CacheBuilder-->getKeyStrength()
    //獲取鍵key的強度(默認爲Strong,還有weak和soft)
        Strength getKeyStrength() {
            return MoreObjects.firstNonNull(keyStrength, Strength.STRONG);
        }
    View Code

    說明:獲取key的引用類型(強度),默認爲Strong(強引用類型),下表列出MoreObjects的方法firstNonNull(@Nullable T first, @Nullable T second)

    public static <T> T firstNonNull(@Nullable T first, @Nullable T second) {
        return first != null ? first : checkNotNull(second);
      }
    View Code

 

  • CacheBuilder-->getValueStrength()
        Strength getValueStrength() {
            return MoreObjects.firstNonNull(valueStrength, Strength.STRONG);
        }
    View Code

    說明:獲取value的引用類型(強度),默認爲Strong(強引用類型)

 

  • CacheBuilder-->getExpireAfterWriteNanos()
    long getExpireAfterWriteNanos() {
            return (expireAfterWriteNanos == UNSET_INT) ? 
                    DEFAULT_EXPIRATION_NANOS
                    : expireAfterWriteNanos;
        }
    View Code

    說明:獲取超時時間,若是設置了,就是設置值,若是沒設置,默認是0

 

  • CacheBuilder-->getInitialCapacity()
    int getInitialCapacity() {
            return (initialCapacity == UNSET_INT) ? 
                    DEFAULT_INITIAL_CAPACITY
                    : initialCapacity;
        }
    View Code

    說明:獲取初始化容量,若是指定了就是用指定容量,若是沒指定,默認爲16。值得注意的是,該容量是用於計算每一個Segment的容量的,並不必定是每一個Segment的容量,其具體使用的方法見LocalCache(CacheBuilder, CacheLoader)

 

  • LocalCache-->evictsBySize()
    //這裏maxWeight沒有設置值,默認爲UNSET_INT,即-1
    
        boolean evictsBySize() {
            return maxWeight >= 0;
        }
    View Code

    說明:這是一個與weight相關的方法,因爲咱們沒有設置weight,因此該方法對咱們的程序沒有影響。

 

  • EntryFactory-->getFatory()
    /**
             * Masks used to compute indices in the following table.
             */
            static final int ACCESS_MASK = 1;
            static final int WRITE_MASK = 2;
            static final int WEAK_MASK = 4;
    
            /**
             * Look-up table for factories.
             */
            static final EntryFactory[] factories = { STRONG, STRONG_ACCESS,
                    STRONG_WRITE, STRONG_ACCESS_WRITE, WEAK, WEAK_ACCESS,
                    WEAK_WRITE, WEAK_ACCESS_WRITE, };
    
            static EntryFactory getFactory(Strength keyStrength,
                                           boolean usesAccessQueue, 
                                           boolean usesWriteQueue) {
                int flags = ((keyStrength == Strength.WEAK) ? WEAK_MASK : 0)//0
                        | (usesAccessQueue ? ACCESS_MASK : 0)//0
                        | (usesWriteQueue ? WRITE_MASK : 0);//WRITE_MASK-->2
                return factories[flags];//STRONG_WRITE
            }
    View Code

    說明:EntryFactory是LocalCache的一個內部枚舉類,經過上述方法,獲取除了相應的EntryFactory,這裏選出的是STRONG_WRITE工廠,該工廠代碼以下:

            STRONG_WRITE {
                /**
                 * 建立新的Entry
                 */
                @Override
                <K, V> ReferenceEntry<K, V> newEntry(Segment<K, V> segment, 
                                                     K key,
                                                     int hash, 
                                                     @Nullable ReferenceEntry<K, V> next) {
                    return new StrongWriteEntry<K, V>(key, hash, next);
                }
    
                /**
                 * 將原來的Entry(original)拷貝到當下的Entry(newNext)
                 */
                @Override
                <K, V> ReferenceEntry<K, V> copyEntry(Segment<K, V> segment,
                                                      ReferenceEntry<K, V> original, 
                                                      ReferenceEntry<K, V> newNext) {
                    ReferenceEntry<K, V> newEntry = super.copyEntry(segment,
                            original, newNext);
                    copyWriteEntry(original, newEntry);
                    return newEntry;
                }
            }
    View Code

    在該工廠中,指定了建立新entry的方法與複製原有entry爲另外一個entry的方法。

     

  • LocalCache-->newSegmentArray(int ssize)
    /**
         * 建立一個指定大小的Segment數組
         */
        @SuppressWarnings("unchecked")
        final Segment<K, V>[] newSegmentArray(int ssize) {
            return new Segment[ssize];
        }
    View Code

    說明:該方法用於建立一個指定大小的Segment數組。關於Segment的介紹後邊會說。

 

  • LocalCache-->createSegment(initialCapacity,maxSegmentWeight,StatsCounter)

        Segment<K, V> createSegment(int initialCapacity, 
                                    long maxSegmentWeight,
                                    StatsCounter statsCounter) {
            return new Segment<K, V>(this, 
                                     initialCapacity, 
                                     maxSegmentWeight,
                                     statsCounter);
        }
    View Code

    該方法用於爲以前建立的Segment數組的每個元素賦值。

    下邊列出Segment類的一些屬性和方法:

    final LocalCache<K, V> map;// 外部類的一個實例
    
            /** 該Segment中已經存在緩存的個數  */
            volatile int count;
    
            /**
             * 指定是下邊的AtomicReferenceArray<ReferenceEntry<K, V>> table,即擴容也是隻擴本身的Segment
             * The table is expanded when its size exceeds this threshold. (The
             * value of this field is always {@code (int) (capacity * 0.75)}.)
             */
            int threshold;
    
            /**
             * 每一個Segment中的table
             */
            volatile AtomicReferenceArray<ReferenceEntry<K, V>> table;
    
            /**
             * The maximum weight of this segment. UNSET_INT if there is no maximum.
             */
            final long maxSegmentWeight;
    
            /**
             * map中當前元素的一個隊列,隊列元素根據write time進行排序,每write一個元素就將該元素加在隊列尾部
             */
            @GuardedBy("this")
            final Queue<ReferenceEntry<K, V>> writeQueue;
    
            /**
             * A queue of elements currently in the map, ordered by access time.
             * Elements are added to the tail of the queue on access (note that
             * writes count as accesses).
             */
            @GuardedBy("this")
            final Queue<ReferenceEntry<K, V>> accessQueue;
    
            Segment(LocalCache<K, V> map, int initialCapacity,
                    long maxSegmentWeight, StatsCounter statsCounter) {
                this.map = map;
                this.maxSegmentWeight = maxSegmentWeight;//0
                this.statsCounter = checkNotNull(statsCounter);
                initTable(newEntryArray(initialCapacity));
    
                writeQueue = map.usesWriteQueue() ? //過時時間>0
                             new WriteQueue<K, V>() //WriteQueue
                             : LocalCache.<ReferenceEntry<K, V>> discardingQueue();
    
                accessQueue = map.usesAccessQueue() ? //false
                              new AccessQueue<K, V>()
                              : LocalCache.<ReferenceEntry<K, V>> discardingQueue();
            }
    
            AtomicReferenceArray<ReferenceEntry<K, V>> newEntryArray(int size) {
                return new AtomicReferenceArray<ReferenceEntry<K, V>>(size);//new Object[size];
            }
    
            void initTable(AtomicReferenceArray<ReferenceEntry<K, V>> newTable) {
                this.threshold = newTable.length() * 3 / 4; // 0.75
                if (!map.customWeigher() && this.threshold == maxSegmentWeight) {
                    // prevent spurious expansion before eviction
                    this.threshold++;
                }
                this.table = newTable;
            }
    View Code

    Segment的構造器完成了三件事兒:爲變量複製 + 初始化Segment的table + 構建相關隊列

    • initTable(newEntryArray(initialCapacity))源代碼在Segment類中已給出:初始化table的步驟簡述爲:建立一個指定個數的ReferenceEntry數組,計算擴容值。
    • 其餘隊列不說了,這裏實際上只用到了WriteQueue,創建該Queue的目的是用於實現LRU緩存回收算法

 

到目前爲止,guava cache的完整的一個數據結構基本上就創建起來了。最後再總結一下。

guava cache的數據結構:

 

guava cache的數據結構的構建流程:

1)構建CacheBuilder實例cacheBuilder

2)cacheBuilder實例指定緩存器LocalCache的初始化參數

3)cacheBuilder實例使用build()方法建立LocalCache實例(簡單說成這樣,實際上覆雜一些)

3.1)首先爲各個類變量賦值(經過第二步中cacheBuilder指定的初始化參數以及本來就定義好的一堆常量)

3.2)以後建立Segment數組

3.3)最後初始化每個Segment[i]

3.3.1)爲Segment屬性賦值

3.3.2)初始化Segment中的table,即一個ReferenceEntry數組(每個key-value就是一個ReferenceEntry)

3.3.3)根據以前類變量的賦值狀況,建立相應隊列,用於LRU緩存回收算法

 

這裏,咱們就用開頭給出的代碼實例,來看一下,最後構建出來的cache結構是個啥:

顯示指定:

expireAfterWriteNanos==20min   maximumSize==1000

默認值:

concurrency_level==4(用於計算Segment個數)     initial_capcity==16 (用於計算每一個Segment容量)  

keyStrength==STRONG    valueStrength==STRONG

計算出:

entryFactory==STRONG_WRITE

segmentCount==4:Segment個數,一個剛剛大於等於concurrency_level且是2的幾回方的一個數

segmentCapacity==initial_capcity/segmentCount==4:用來計算每一個Segment能放置的entry個數的一個值,一個剛剛等於initial_capcity/segmentCount或者比initial_capcity/segmentCount大1的數(關鍵看是否除盡)

segmentSize==4:每一個Segment能放置的entry個數,剛剛>=segmentCapacity&&是2的幾回方的數

segments==Segment[segmentCount]==Segment[4]

segments[i]:

  • 包含一個ReferenceEntry[segmentSize]==ReferenceEntry[4]
  • WriteQueue:用於LRU算法的隊列
  • threshold==newTable.length()*3/4==segmentSize*3/4==3:每一個Segment中有了3個Entry(key-value),就會擴容,擴容機制之後在添加Entry的時候再講
相關文章
相關標籤/搜索