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的資料結構:
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基礎映象製作