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()

    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)//
    | (usesAccessQueue ? ACCESS_MASK : 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;//
    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的时候再讲

第二章 Google guava cache源码解析1--构建缓存器的更多相关文章

  1. Google guava cache源码解析1--构建缓存器(1)

    此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 1.guava cache 当下最常用最简单的本地缓存 线程安全的本地缓存 类似于ConcurrentHas ...

  2. Google guava cache源码解析1--构建缓存器(2)

    此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. CacheBuilder-->maximumSize(long size)     /**       ...

  3. Google guava cache源码解析1--构建缓存器(3)

    此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 下面介绍在LocalCache(CacheBuilder, CacheLoader)中调用的一些方法: Ca ...

  4. Guava Cache源码解析

    概述: 本次主要是分析cache的源码,基本概念官方简介即可. 基本类图: 在官方的文档说明中,Guava Cache实现了三种加载缓存的方式: LoadingCache在构建缓存的时候,使用buil ...

  5. [源码解析] PyTorch分布式优化器(1)----基石篇

    [源码解析] PyTorch分布式优化器(1)----基石篇 目录 [源码解析] PyTorch分布式优化器(1)----基石篇 0x00 摘要 0x01 从问题出发 1.1 示例 1.2 问题点 0 ...

  6. [源码解析] PyTorch分布式优化器(2)----数据并行优化器

    [源码解析] PyTorch分布式优化器(2)----数据并行优化器 目录 [源码解析] PyTorch分布式优化器(2)----数据并行优化器 0x00 摘要 0x01 前文回顾 0x02 DP 之 ...

  7. [源码解析] PyTorch分布式优化器(3)---- 模型并行

    [源码解析] PyTorch分布式优化器(3)---- 模型并行 目录 [源码解析] PyTorch分布式优化器(3)---- 模型并行 0x00 摘要 0x01 前文回顾 0x02 单机模型 2.1 ...

  8. 常用限流算法与Guava RateLimiter源码解析

    在分布式系统中,应对高并发访问时,缓存.限流.降级是保护系统正常运行的常用方法.当请求量突发暴涨时,如果不加以限制访问,则可能导致整个系统崩溃,服务不可用.同时有一些业务场景,比如短信验证码,或者其它 ...

  9. Guava Cache源码详解

    目录 一.引子 二.使用方法 2.1 CacheBuilder有3种失效重载模式 2.2 测试验证 三.源码剖析 3.1 简介 3.2 源码剖析 四.总结 优点: 缺点: 正文 回到顶部 一.引子 缓 ...

随机推荐

  1. Windows标准控件

    学习目的 学习创建, 使用Windows标准控件(按钮, 滚动条, 静态控件, 列表框, 编辑框, 组合框); 学习使用子窗口控件操作函数(EnableWindow, MoveWindow, SetW ...

  2. WebApiTestHelpPage

    这是个什么鬼,第一次见到的时候,我也不知道就花几天时间看了下它的代码 在网上搜索WebApiTestHelpPage会出来很多相关页面   但是它们都是介绍怎么用的,要么就是怎么添加注释   它是怎么 ...

  3. 第二十四天- 模块导入 import from xxx import xxx

    # 模块:# 模块就是⼀个包含了python定义和声明的⽂件,⽂件名就是模块的名字加上.py后缀# 换句话说我们⽬前写的所有的py⽂件都可以看成是⼀个模块# 为何用模块:写大项目时,把相关的功能进⾏分 ...

  4. LOJ#6463 AK YOI 树分治+线段树合并

    传送门 既然是树上路径统计问题,不难想到要使用树分治,这里以点分治为例 由点分治的性质,每层只需要考虑经过重心的路径 因为需要维护路径长度在一定范围内的最大权值和,所以要用一个数据结构维护一下到根节点 ...

  5. js-权威指南学习笔记3

    第四章 表达式和运算符 1.最简单的表达式是原始表达式,是表达式的最小单位——它们不再包含其他表达式.JS中原始表达式包含常量或直接量.关键字和变量. 2.一个对象的属性名不是固定值时,必须使用方括号 ...

  6. jQuery基础(样式篇,DOM对象,选择器,属性样式)

      1. $(document).ready 的作用是等页面的文档(document)中的节点都加载完毕后,再执行后续的代码,因为我们在执行代码的时候,可能会依赖页面的某一个元素,我们要确保这个元素真 ...

  7. FFmpeg实现将图片转换为视频

    ##名称:ffmpeg实现将图片转换为视频 ##平台:ubuntu(已经安装好了ffmpeg工具) ##日期:2017年12月10日 简介: 因为学习需要,需要将连续图片转换成视频,昨天和今天早上用o ...

  8. 根据自定义区域裁剪ArcGIS切片地图服务

    切片地图服务是访问地图最快捷的服务方式.假如要根据地理区域对切图进行访问控制,往往只能针对不同地理区域制作相应的地图,并发布为切片地图服务.而一般在切图的时候又是按全区域实施的,所以给切片管理者造成不 ...

  9. CentOS7系列--1.2CentOS7基本设置

    CentOS7基本设置 1. 查看相关信息 1.1. 查看系统信息 1.1.1. 查看系统位数 方法1: [root@centos7 ~]# uname -a Linux centos7.smartm ...

  10. 地图的可视化--Folium

    1.安装folium pip install MarkupSafe-0.23-cp34-none-win_amd64.whl pip install Jinja2-2.8-py2.py3-none-a ...