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的時候再講
免費領取驗證碼、內容安全、短信發送、直播點播體驗包及雲服務器等套餐
更多網易技術、產品、運營經驗分享請點擊。
相關文章:
【推薦】 JVM內存回收區域+對象存活的判斷+引用類型+垃圾回收線程
【推薦】 JVM鎖實現探究2:synchronized深探
Google guava cache源碼解析1--構建緩存器(3)