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

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

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

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


CacheBuilder-->maximumSize(long size)

    /**
     * 指定cache中最多能存放的entry(key-value)個數maximumSize
     * 注意:
     * 1、在entry個數還未達到這個指定個數maximumSize的時候,可能就會發生快取回收
     * 上邊這種情況發生在cache size接近指定個數maximumSize,
     * cache就會回收那些很少會再被用到的快取(這些快取會使最近沒有被用到或很少用到的),其實說白了就是LRU演算法回收快取
     * 2、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;
    }

注意:

  • 設定整個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);
    }

注意:

  • 要看懂該方法,需要了解一些泛型方法的使用方式與泛型限界

  • 該方法的返回值是一個LoadingCache介面的實現類LocalLoadingCache例項

  • 在build方法需要傳入一個CacheLoader的例項,實際使用中使用了匿名內部類來實現的,原始碼的話,就是一個無參構造器,什麼也沒做,傳入CacheLoader例項的意義就是"類結構"部分所說的load()方法

 在上邊呼叫build時,整個程式碼的執行權其實就交給了LocalCache.

 

3.2、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)));
        }

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

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;

說明:關於這些屬性的含義,看註釋+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());
            }
        }
    }

說明:這裡的程式碼就是整個LocalCache例項的建立過程,非常重要!!!


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

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


相關文章:
【推薦】 流式斷言器AssertJ介紹