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介紹