1. 程式人生 > >Google guava cache原始碼解析1--構建快取器(3)

Google guava cache原始碼解析1--構建快取器(3)

此文已由作者趙計剛授權網易雲社群釋出。

歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗。


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

  • CacheBuilder-->getConcurrencyLevel()

    int getConcurrencyLevel() {
            return (concurrencyLevel == UNSET_INT) ? //是否設定了concurrencyLevel
                    DEFAULT_CONCURRENCY_LEVEL//如果沒有設定,採用預設值16
                    : concurrencyLevel;//如果設定了,採用設定的值
        }

    說明:檢查是否設定了concurrencyLevel,如果設定了,採用設定的值,如果沒有設定,採用預設值16

 

  • CacheBuilder-->getKeyStrength()

    //獲取鍵key的強度(預設為Strong,還有weak和soft)
        Strength getKeyStrength() {
            return MoreObjects.firstNonNull(keyStrength, Strength.STRONG);
        }

    說明:獲取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);
      }

 

  • CacheBuilder-->getValueStrength()

        Strength getValueStrength() {
            return MoreObjects.firstNonNull(valueStrength, Strength.STRONG);
        }

    說明:獲取value的引用型別(強度),預設為Strong(強引用型別)

 

  • CacheBuilder-->getExpireAfterWriteNanos()

    long getExpireAfterWriteNanos() {
            return (expireAfterWriteNanos == UNSET_INT) ? 
                    DEFAULT_EXPIRATION_NANOS
                    : expireAfterWriteNanos;
        }

    說明:獲取超時時間,如果設定了,就是設定值,如果沒設定,預設是0

 

  • CacheBuilder-->getInitialCapacity()

    int getInitialCapacity() {
            return (initialCapacity == UNSET_INT) ? 
                    DEFAULT_INITIAL_CAPACITY
                    : initialCapacity;
        }

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

 

  • LocalCache-->evictsBySize()

    //這裡maxWeight沒有設定值,預設為UNSET_INT,即-1
    
        boolean evictsBySize() {
            return maxWeight >= 0;
        }

    說明:這是一個與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
            }

    說明: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;
                }
            }

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

     

  • LocalCache-->newSegmentArray(int ssize)

    /**
         * 建立一個指定大小的Segment陣列
         */
        @SuppressWarnings("unchecked")
        final Segment<K, V>[] newSegmentArray(int ssize) {
            return new Segment[ssize];
        }

    說明:該方法用於建立一個指定大小的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);
        }

    該方法用於為之前建立的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;
            }

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

    • initTable(newEntryArray(initialCapacity))原始碼在Segment類中已給出:初始化table的步驟簡述為:建立一個指定個數的ReferenceEntry陣列,計算擴容值。

    • 其他佇列不說了,這裡實際上只用到了WriteQueue,建立該Queue的目的是用於實現LRU快取回收演算法

 

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

guava cache的資料結構:


 201812181342597c9faf90-ab5b-454a-af55-21b8c0d5490f.jpg

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的時候再講


免費領取驗證碼、內容安全、簡訊傳送、直播點播體驗包及雲伺服器等套餐

更多網易技術、產品、運營經驗分享請點選


相關文章:
【推薦】 IT和非IT人士:2分鐘瞭解什麼是區塊鏈
【推薦】 【譯文】不是所有的 bug 都值得修復的
【推薦】 測試環境docker化(一)—基於ndp部署模式的docker基礎映象製作