1、guava cache
- 當下最常用最簡單的本地緩存
- 線程安全的本地緩存
- 類似於ConcurrentHashMap(或者說成就是一個ConcurrentHashMap,只是在其上多添加了一些功能)
2、使用實例
具體在實際中使用的例子,去查看《第七章 企業項目開發--本地緩存guava cache》,下面只列出測試實例:
import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; public class Hello{ LoadingCache<String, String> testCache = CacheBuilder.newBuilder() .expireAfterWrite(20, TimeUnit.MINUTES)// 緩存20分鍾 .maximumSize(1000)// 最多緩存1000個對象 .build(new CacheLoader<String, String>() { public String load(String key) throws Exception { if(key.equals("hi")){ return null; } return key+"-world"; } }); public static void main(String[] args){ Hello hello = new Hello(); System.out.println(hello.testCache.getIfPresent("hello"));//null hello.testCache.put("123", "nana");//存放緩存 System.out.println(hello.testCache.getIfPresent("123"));//nana try { System.out.println(hello.testCache.get("hello"));//hello-world } catch (ExecutionException e) { e.printStackTrace(); } System.out.println(hello.testCache.getIfPresent("hello"));//hello-world /***********測試null*************/ System.out.println(hello.testCache.getIfPresent("hi"));//null try { System.out.println(hello.testCache.get("hi"));//拋異常 } catch (ExecutionException e) { e.printStackTrace(); } } }
在這個方法中,基本已經覆蓋了guava cache常用的部分。
- 構造緩存器
- 緩存器的構建沒有使用構造器而不是使用了構建器模式,這是在存在多個可選參數的時候,最合適的一種配置參數的方式,具體參看《effective Java(第二版)》第二條建議。
- 常用的三個方法
- get(Object key)
- getIfPresent(Object key)
- put(Object key, Object value)
3、源代碼
在閱讀源代碼之前,強烈建議,先看一下"Java並發包類源碼解析"中的《第二章 ConcurrentHashMap源碼解析》,鏈接如下:
http://www.cnblogs.com/java-zhao/p/5113317.html
對於源碼部分,由於整個代碼的核心類LocalCache有5000多行,所以只介紹上邊給出的實例部分的相關源碼解析。本節只說一下緩存器的構建,即如下代碼部分:
LoadingCache<String, String> testCache = CacheBuilder.newBuilder() .expireAfterWrite(20, TimeUnit.MINUTES)// 緩存20分鍾(時間起點:entry的創建或替換(即修改)) //.expireAfterAccess(10, TimeUnit.MINUTES)//緩存10分鍾(時間起點:entry的創建或替換(即修改)或最后一次訪問) .maximumSize(1000)// 最多緩存1000個對象 .build(new CacheLoader<String, String>() { public String load(String key) throws Exception { if(key.equals("hi")){ return null; } return key+"-world"; } });
說明:該代碼的load()方法會在之后將get(Object key)的時候再說,這里先不說了。
對於這一塊兒,由於guava cache這一塊兒的代碼雖然不難,但是容易看的跑偏,一會兒就不知道跑到哪里去了,所以我下邊先給出guava cache的數據結構以及上述代碼的執行流程,然后大家帶着這個數據結構和執行流程去分析下邊的源代碼,分析完源代碼之后,我在最后還會再將cache的數據結構和構建緩存器的執行流程給出,並會結合我們給出的開頭實例代碼來套一下整個流程,最后畫出初始化構建出來的緩存器(其實,這個緩存器就是上邊以及文末給出的cache的數據結構圖)。
guava cache的數據結構圖:

需要說明的是:
- 每一個Segment中的有效隊列(廢棄隊列不算)的個數最多可能不止一個
- 上圖與ConcurrentHashMap及其類似,其中的ReferenceEntry[i]用於存放key-value
- 每一個ReferenceEntry[i]都會存放一個鏈表,當然采用的也是Entry替換的方式。
- 隊列用於實現LRU緩存回收算法
- 多個Segment之間互不打擾,可以並發執行
- 各個Segment的擴容只需要擴自己的就好,與其他Segment無關
- 根據需要設置好初始化容量與並發水平參數,可以有效避免擴容帶來的昂貴代價,但是設置的太大了,又會耗費很多內存,要衡量好
后邊三條與ConcurrentHashMap一樣
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緩存回收算法
類結構:(這個不看也罷)
- CacheBuilder:設置LocalCache的相關參數,並創建LocalCache實例
- CacheLoader:有用的部分就是一個load(),用於實現"取緩存-->若不存在,先計算,在緩存-->取緩存"的原子操作
- LocalCache:整個guava cache的核心類,包含了guava cache的數據結構以及基本的緩存的操作方法
- LocalLoadingCache:LocalCache的一個靜態內部類,這里的get(K key)是外部調用get(K key)入口
- LoadingCache接口:繼承於Cache接口,定義了get(K key)
- Cache接口:定義了getIfPresent(Object key)和put(K key, V value)
- LocalManualCache:LocalCache的一個靜態內部類,是LocalLoadingCache的父類,這里的getIfPresent(Object key)和put(K key, V value)也是外部方法的入口
關於上邊的這些說明,結合之后的源碼進行看就好了。
注:如果在源碼中有一些注釋與最后的套例子的注釋不同的話,以后者為准
3.1、構建CacheBuilder+為LocalCache設置相關參數+創建LocalCache實例
CacheBuilder的一些屬性:
private static final int DEFAULT_INITIAL_CAPACITY = 16;//用於計算每個Segment中的hashtable的大小 private static final int DEFAULT_CONCURRENCY_LEVEL = 4;//用於計算有幾個Segment private static final int DEFAULT_EXPIRATION_NANOS = 0;//默認的緩存過期時間 static final int UNSET_INT = -1; int initialCapacity = UNSET_INT;//用於計算每個Segment中的hashtable的大小 int concurrencyLevel = UNSET_INT;//用於計算有幾個Segment long maximumSize = UNSET_INT;//cache中最多能存放的緩存entry個數 long maximumWeight = UNSET_INT; Strength keyStrength;//鍵的引用類型(strong、weak、soft) Strength valueStrength;//值的引用類型(strong、weak、soft) long expireAfterWriteNanos = UNSET_INT;//緩存超時時間(起點:緩存被創建或被修改) long expireAfterAccessNanos = UNSET_INT;//緩存超時時間(起點:緩存被創建或被修改或被訪問)
CacheBuilder-->newCacheBuilder():創建一個CacheBuilder實例
/** * 采用默認的設置(如下)創造一個新的CacheBuilder實例 * 1、strong keys * 2、strong values * 3、no automatic eviction of any kind. */ public static CacheBuilder<Object, Object> newBuilder() { return new CacheBuilder<Object, Object>();//new 一個實例 }
接下來,使用構建器模式指定一些屬性值(這里的話,就是超時時間:expireAfterWriteNanos+cache中最多能放置的entry個數:maximumSize),這里的entry指的就是一個緩存(key-value對)
CacheBuilder-->expireAfterWrite(long duration, TimeUnit unit)
/** * 指明每一個entry(key-value)在緩存中的過期時間 * 1、時間的參考起點:entry的創建或值的修改 * 2、過期的entry也許會被計入緩存個數size(也就是說緩存個數不僅僅只有存活的entry) * 3、但是過期的entry永遠不會被讀寫 */ public CacheBuilder<K, V> expireAfterWrite(long duration, TimeUnit unit) { /* * 檢查之前是否已經設置過緩存超時時間 */ checkState(expireAfterWriteNanos == UNSET_INT,//正確條件:之前沒有設置過緩存超時時間 "expireAfterWrite was already set to %s ns",//不符合正確條件的錯誤信息 expireAfterWriteNanos); /* * 檢查設置的超時時間是否大於等於0,當然,通常情況下,我們不會設置緩存為0 */ checkArgument(duration >= 0, //正確條件 "duration cannot be negative: %s %s",//不符合正確條件的錯誤信息,下邊的是錯誤信息中的錯誤參數 duration, unit); this.expireAfterWriteNanos = unit.toNanos(duration);//根據輸入的時間值與時間單位,將時間值轉換為納秒 return this; }
注意:
- 設置超時時間,注意時間的起點是entry的創建或替換(修改)
- expireAfterAccess(long duration, TimeUnit unit)方法的時間起點:entry的創建或替換(修改)或被訪問
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實例的創建過程,非常重要!!!
下面介紹在LocalCache(CacheBuilder, CacheLoader)中調用的一些方法:
- CacheBuilder-->getConcurrencyLevel()
說明:檢查是否設置了concurrencyLevel,如果設置了,采用設置的值,如果沒有設置,采用默認值16
View Codeint getConcurrencyLevel() { return (concurrencyLevel == UNSET_INT) ? //是否設置了concurrencyLevel DEFAULT_CONCURRENCY_LEVEL//如果沒有設置,采用默認值16 : concurrencyLevel;//如果設置了,采用設置的值 }
- CacheBuilder-->getKeyStrength()
View Code//獲取鍵key的強度(默認為Strong,還有weak和soft) Strength getKeyStrength() { return MoreObjects.firstNonNull(keyStrength, Strength.STRONG); }
說明:獲取key的引用類型(強度),默認為Strong(強引用類型),下表列出MoreObjects的方法firstNonNull(@Nullable T first, @Nullable T second)
View Codepublic static <T> T firstNonNull(@Nullable T first, @Nullable T second) { return first != null ? first : checkNotNull(second); }
- CacheBuilder-->getValueStrength()
View CodeStrength getValueStrength() { return MoreObjects.firstNonNull(valueStrength, Strength.STRONG); }
說明:獲取value的引用類型(強度),默認為Strong(強引用類型)
- CacheBuilder-->getExpireAfterWriteNanos()
View Codelong getExpireAfterWriteNanos() { return (expireAfterWriteNanos == UNSET_INT) ? DEFAULT_EXPIRATION_NANOS : expireAfterWriteNanos; }
說明:獲取超時時間,如果設置了,就是設置值,如果沒設置,默認是0
- CacheBuilder-->getInitialCapacity()
View Codeint getInitialCapacity() { return (initialCapacity == UNSET_INT) ? DEFAULT_INITIAL_CAPACITY : initialCapacity; }
說明:獲取初始化容量,如果指定了就是用指定容量,如果沒指定,默認為16。值得注意的是,該容量是用於計算每個Segment的容量的,並不一定是每個Segment的容量,其具體使用的方法見LocalCache(CacheBuilder, CacheLoader)
- LocalCache-->evictsBySize()
View Code//這里maxWeight沒有設置值,默認為UNSET_INT,即-1 boolean evictsBySize() { return maxWeight >= 0; }
說明:這是一個與weight相關的方法,由於我們沒有設置weight,所以該方法對我們的程序沒有影響。
- EntryFactory-->getFatory()
View Code/** * 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工廠,該工廠代碼如下:
View CodeSTRONG_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)
View Code/** * 創建一個指定大小的Segment數組 */ @SuppressWarnings("unchecked") final Segment<K, V>[] newSegmentArray(int ssize) { return new Segment[ssize]; }
說明:該方法用於創建一個指定大小的Segment數組。關於Segment的介紹后邊會說。
-
LocalCache-->createSegment(initialCapacity,maxSegmentWeight,StatsCounter)
View CodeSegment<K, V> createSegment(int initialCapacity, long maxSegmentWeight, StatsCounter statsCounter) { return new Segment<K, V>(this, initialCapacity, maxSegmentWeight, statsCounter); }
該方法用於為之前創建的Segment數組的每一個元素賦值。
下邊列出Segment類的一些屬性和方法:
View Codefinal 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的時候再講
