Glide缓存流程

Glide中缓存分为内存缓存和磁盘缓存,而内存缓存又分为当前活跃的缓存和未使用的缓存,磁盘缓存分为解码后的缓存以及原始缓存。前面流程中介绍过解码和编码,这里面介绍过如何保存的,那获取是什么样的,我们这节就分析下。

一级缓存

在Glide主流程中有提到过内存缓存,在主流程中都是按照加载网络数据进行分析的,我们从Engine的load方法开始讲解:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class Engine{
    public <R> LoadStatus load(
        GlideContext glideContext,
        Object model,
        Key signature,
        int width,
        int height,
        Class<?> resourceClass,
        Class<R> transcodeClass,
        Priority priority,
        DiskCacheStrategy diskCacheStrategy,
        Map<Class<?>, Transformation<?>> transformations,
        boolean isTransformationRequired,
        boolean isScaleOnlyOrNoTransform,
        Options options,
        boolean isMemoryCacheable,
        boolean useUnlimitedSourceExecutorPool,
        boolean useAnimationPool,
        boolean onlyRetrieveFromCache,
        ResourceCallback cb,
        Executor callbackExecutor) {
        EngineKey key =
            keyFactory.buildKey(
                model,
                signature,
                width,
                height,
                transformations,
                resourceClass,
                transcodeClass,
                options);
        EngineResource<?> memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
    }
}

先通过KeyFactory的buildKey创建一个缓存的key,model是load方法传进来的参数,宽和高是加载的图片尺寸。resourceClass在上一节中解码时候获取LoadPath介绍过,它主要影响到过滤DecodePath,默认是Object.class,可以通过RequestOptions的decodeTypeOf方法设置。transformations是load方法传进来的参数类型,比如默认传进来一个url,则transformations是一个Drawable.class。signature是一个签名参数,默认是EmptySignature,可以通过RequestOptions的signatureOf方法进行设置。options可以配置一些参数,比如解码方式等。此处使用EngineKeyFactory的buildKey直接构建,也是一种工厂模式,只不过此处不需要外界进行构建,所以直接通过普通类创建EngineKey。接着看下loadFromMemory方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
private EngineResource<?> loadFromMemory(
    EngineKey key, boolean isMemoryCacheable, long startTime) {
  if (!isMemoryCacheable) {
    return null;
  }
  EngineResource<?> active = loadFromActiveResources(key);
  if (active != null) {
    return active;
  }
  EngineResource<?> cached = loadFromCache(key);
  if (cached != null) {
    return cached;
  }
  return null;
}

isMemoryCacheable表示内存缓存是否可使用,如果可使用,调用过loadFromActiveResources方法获取一级缓存:

1
2
3
4
5
6
7
8
private final ActiveResources activeResources;
private EngineResource<?> loadFromActiveResources(Key key) {
  EngineResource<?> active = activeResources.get(key);
  if (active != null) {
    active.acquire();
  }
  return active;
}

通过ActiveResources的get方法获取一级缓存:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
synchronized EngineResource<?> get(Key key) {
  ResourceWeakReference activeRef = activeEngineResources.get(key);
  if (activeRef == null) {
    return null;
  }
  EngineResource<?> active = activeRef.get();
  if (active == null) {
    cleanupActiveReference(activeRef);
  }
  return active;
}

void cleanupActiveReference(@NonNull ResourceWeakReference ref) {
  synchronized (this) {
    activeEngineResources.remove(ref.key);
    if (!ref.isCacheable || ref.resource == null) {
      return;
    }
  }
  EngineResource<?> newResource =
      new EngineResource<>(
          ref.resource,
          /* isMemoryCacheable= */ true,
          /* isRecyclable= */ false,
          ref.key,
          listener);
  listener.onResourceReleased(ref.key, newResource);
}

在get方法中先从activeEngineResources这个map中获取ResourceWeakReference,它是一个弱引用,里面存储的对象是EngineResource:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
static final class ResourceWeakReference extends WeakReference<EngineResource<?>> {
    final Key key;

    final boolean isCacheable;

    Resource<?> resource;

    ResourceWeakReference(
        @NonNull Key key,
        @NonNull EngineResource<?> referent,
        @NonNull ReferenceQueue<? super EngineResource<?>> queue,
        boolean isActiveResourceRetentionAllowed) {
        super(referent, queue);
        this.key = Preconditions.checkNotNull(key);
        this.resource =
            referent.isMemoryCacheable() && isActiveResourceRetentionAllowed
                ? Preconditions.checkNotNull(referent.getResource())
                : null;
        isCacheable = referent.isMemoryCacheable();
    }

    void reset() {
        resource = null;
        clear();
    }
}

在get方法中如果若引用没有获取到,则返回null,如果获取到,则继续获取弱引用中的对象(EngineResource),如果弱引用中的对象为空,则调用cleanupActiveReference方法,在该方法里面通过弱引用中的key将该弱引用从map中移除掉,并回调onResourceReleased方法。

弱引用有个特点,如果在GC的时候,发现弱引用包装的对象没有强引用指向的时候,此时对象只被弱引用所引用了,变成”弱可达“,下一次GC时,弱引用指向的对象被立即回收。比如下面代码:

1
2
Object obj = new Object();
WeakReference<Object> weakRef = new WeakReference<>(obj);

此时对象生命周期: • 有一个 强引用(obj) • 有一个 弱引用(weakRef)

对象不会被回收,因为有强引用。 当obj = null时,此时: • 对象只被弱引用引用 • 变成了 “弱可达”(Weakly reachable) 下一阶段就是GC回收弱引用指向的对象。 GC 动作: 1. GC 扫描弱引用所引用的对象 2. 发现其没有强引用 → 立即清除对象 3. weakRef.get() 返回 null 4. 同时将 weakRef 放入 ReferenceQueue(如果你创建了的话) 下一阶段:弱引用本身仍存在,但已经失效

1
weakRef.get() == null

弱引用对象(WeakReference 实例)本身不会被立即回收,它只是不再指向有效对象。

所以上面当弱引用所指向的对象为空的时候,说明EngineResource已经被回收了。该从下一个缓存取了。若找到了,调用EngineResource的acquire:

1
2
3
4
5
6
7
8
class EngineResource{
  synchronized void acquire() {
    if (isRecycled) {
      throw new IllegalStateException("Cannot acquire a recycled resource");
    }
    ++acquired;
  }
}

它表示当前图片正在使用的次数,后面会用到,知道当前给acquired累加次数就行。在一级缓存中,可以看到弱引用使用了引用队列,它是用来检测我们弱引用对象是否被回收的标志,在ActiveResources的cleanReferenceQueue方法中判断队列中是否存在弱引用对象:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
void cleanReferenceQueue() {
  while (!isShutdown) {
    try {
      ResourceWeakReference ref = (ResourceWeakReference) resourceReferenceQueue.remove();
      cleanupActiveReference(ref);
      // This section for testing only.
      DequeuedResourceCallback current = cb;
      if (current != null) {
        current.onResourceDequeued();
      }
      // End for testing only.
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
    }
  }
}

该方法是一个while循环,只要没有isShutdown就一直从引用队列中检索弱引用对象,从引用队列中获取到弱引用对象之后,接着也是调用了cleanupActiveReference方法,这个在上面get方法中有讲到过。它是从map中移除ResourceWeakReference。注意了cleanReferenceQueue方法是在外界传进来的线程池(Executors.newSingleThreadExecutor)中执行的。它是在ActiveResources构造方法中初始化的。

二级缓存

接着回到上面Engine的loadFromMemory方法,如果在一级缓存中找不到,接着调用了loadFromCache方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private EngineResource<?> loadFromCache(Key key) {
  EngineResource<?> cached = getEngineResourceFromCache(key);
  if (cached != null) {
    cached.acquire();
    activeResources.activate(key, cached);
  }
  return cached;
}

private final MemoryCache cache;
private EngineResource<?> getEngineResourceFromCache(Key key) {
  Resource<?> cached = cache.remove(key);
  final EngineResource<?> result;
  if (cached == null) {
    result = null;
  } else if (cached instanceof EngineResource) {
    result = (EngineResource<?>) cached;
  } else {
    result =
        new EngineResource<>(
            cached,
            /* isMemoryCacheable= */ true,
            /* isRecyclable= */ true,
            key,
            /* listener= */ this);
  }
  return result;
}

此处从cache中取Resource,如果取到了返回EngineResource,此处的cache是LruResourceCache对象,它是LruCache实现的。可以看到它是获取的时候直接remove掉了,紧接着调用acquire,并把缓存放到一级缓存中(activeResources.activate)。也就是说如果二级缓存中有数据,获取到后从二级缓存中移除掉并放到一级缓存中,然后将acquired变量+1。上面会涉及到二级缓存的最大缓存的内存计算,这个在后面讲内存的时候再说。

三级缓存

如果一级和二级都没有找到,则会在Engine的load方法中调用waitForExistingOrStartNewJob,在主流程加载图片中讲过ResourceCacheGenerator和DataCacheGenerator的区别,ResourceCacheGenerator是获取本地磁盘编码后的缓存,DataCacheGenerator中是获取本地磁盘原始图片的缓存,接着又讲到了SourceGenerator中通过HttpUrlFetcher获取网络图片资源。当第一次加载完图片后,会保存原始图片到磁盘中,这个在SourceGenerator的cacheData中有讲过。解码完后,也会将bitmap进行保存到本地磁盘中,这个在DecodeJob中的notifyEncodeAndRelease方法有讲解。所以当第二次获取图片的时候有了原始图片和解码后的图片缓存了,看下ResourceCacheGenerator中如何获取解码后的缓存:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class ResourceCacheGenerator{
  public boolean startNext() {
    currentKey =
    new ResourceCacheKey(
        helper.getArrayPool(),
        sourceId,
        helper.getSignature(),
        helper.getWidth(),
        helper.getHeight(),
        transformation,
        resourceClass,
        helper.getOptions());
    cacheFile = helper.getDiskCache().get(currentKey);
    if (cacheFile != null) {
      sourceKey = sourceId;
      modelLoaders = helper.getModelLoaders(cacheFile);
      modelLoaderIndex = 0;
    }
    while (!started && hasNextModelLoader()) {
      ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
      loadData =
          modelLoader.buildLoadData(
              cacheFile, helper.getWidth(), helper.getHeight(), helper.getOptions());
      if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
        started = true;
        loadData.fetcher.loadData(helper.getPriority(), this);
      }
    }
  }
}

先创建了ResourceCacheKey的key,这个和前面在解码完回调中,也就是DecodeJob的onResourceDecoded中也是创建了ResourceCacheKey:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class DecodeJob{
  <Z> Resource<Z> onResourceDecoded(DataSource dataSource, @NonNull Resource<Z> decoded) {
    Key key =
        new ResourceCacheKey(
            decodeHelper.getArrayPool(),
            currentSourceKey,
            signature,
            width,
            height,
            appliedTransformation,
            resourceSubClass,
            options);
    deferredEncodeManager.init(key, encoder, lockedResult);
  }
}

创建完ResourceCacheKey后,继续调用了helper.getDiskCache().get(currentKey)获取缓存的file文件,此处的diskCache和前面介绍的解码完成后,进行编码保存bitmap所使用的DiskLruCacheWrapper是一致的。获取到file文件后,通过file获取到modelLoaders集合。这个其实跟前面介绍sourceGenerator获取到网络数据后,调用了DataCacheGenerator的startNext方法,在该方法里面也是通过file获取到ByteBufferFetcher将File转化成ByteBuffer。转化完后回调到ResourceCacheGenerator的onDataReady方法:

1
2
3
4
5
6
class ResourceCacheGenerator{
  public void onDataReady(Object data) {
    cb.onDataFetcherReady(
        sourceKey, data, loadData.fetcher, DataSource.RESOURCE_DISK_CACHE, currentKey);
  }
}

然后继续回调到DecodeJob的onDataFetcherReady方法,注意了此处的dataSource回传的是DataSource.RESOURCE_DISK_CACHE,在前面下载完网络资源后,将File转化成ByteBuffer,也是回调给DecodeJob的onDataFetcherReady方法,只不过那个dataSource回传的是DataSource.DATA_DISK_CACHE。也就是说DataSource.RESOURCE_DISK_CACHE表示获取到相同尺寸的bitmap缓存,而DataSource.DATA_DISK_CACHE表示获取到原始尺寸的bitmap缓存。回到DecodeJob的onDataFetcherReady后,就回到了解码流程,获取到相同尺寸的图片后,为什么还要进行解码呢?因为目前拿到的还是byteBuffer的数据,需要将它包装成流,然后解码成bitmap数据,只不过在解码的时候,图片的采样率=1,inTargetDensity和inDensity的比例等于1,所以只不过是将流转化成bitmap而已。解码完后回到了DecodeJob的onResourceDecoded方法,在该方法里面梳理过:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class DecodeJob{
  <Z> Resource<Z> onResourceDecoded(DataSource dataSource, @NonNull Resource<Z> decoded) {
    @SuppressWarnings("unchecked")
    Class<Z> resourceSubClass = (Class<Z>) decoded.get().getClass();
    Transformation<Z> appliedTransformation = null;
    Resource<Z> transformed = decoded;
    //如果dataSource是RESOURCE_DISK_CACHE就不进行transformation处理
    if (dataSource != DataSource.RESOURCE_DISK_CACHE) {
      appliedTransformation = decodeHelper.getTransformation(resourceSubClass);
      transformed = appliedTransformation.transform(glideContext, decoded, width, height);
    }
    if (!decoded.equals(transformed)) {
      decoded.recycle();
    }

    final EncodeStrategy encodeStrategy = encoder.getEncodeStrategy(options);
    final ResourceEncoder<Z> encoder = decodeHelper.getResultEncoder(transformed);

    Resource<Z> result = transformed;
    boolean isFromAlternateCacheKey = !decodeHelper.isSourceKey(currentSourceKey);
    //如果dataSource是RESOURCE_DISK_CACHE就不进行编码的参数初始化
    if (diskCacheStrategy.isResourceCacheable(
        isFromAlternateCacheKey, dataSource, encodeStrategy)) {
      if (encoder == null) {
        throw new Registry.NoResultEncoderAvailableException(transformed.get().getClass());
      }
      final Key key;
      switch (encodeStrategy) {
        case SOURCE:
          key = new DataCacheKey(currentSourceKey, signature);
          break;
        case TRANSFORMED:
          key =
              new ResourceCacheKey(
                  decodeHelper.getArrayPool(),
                  currentSourceKey,
                  signature,
                  width,
                  height,
                  appliedTransformation,
                  resourceSubClass,
                  options);
          break;
        default:
          throw new IllegalArgumentException("Unknown strategy: " + encodeStrategy);
      }

      LockedResource<Z> lockedResult = LockedResource.obtain(transformed);
      deferredEncodeManager.init(key, encoder, lockedResult);
      result = lockedResult;
    }
    return result;
  }
}

在onResourceDecoded方法中,如果dataSource是RESOURCE_DISK_CACHE就不进入到transformation的逻辑,并且也不进行编码的参数初始化,所以在DecodeJob的notifyEncodeAndRelease方法中不进行编码了,因为磁盘中已经存在该缓存。

四级缓存

四级缓存在glide中是存储原始文件,在上一篇节中讲过SourceGenerator通过HttpUrlFetcher下载完网络资源后,会保存原始文件,这个在startNext中调用cacheData方法实现的。而获取原始文件的缓存是在DataCacheGenerator的startNext实现的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class DataCacheGenerator{
  public boolean startNext() {
    while (modelLoaders == null || !hasNextModelLoader()) {
      sourceIdIndex++;
      if (sourceIdIndex >= cacheKeys.size()) {
        return false;
      }

      Key sourceId = cacheKeys.get(sourceIdIndex);
      Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
      cacheFile = helper.getDiskCache().get(originalKey);
      if (cacheFile != null) {
        this.sourceKey = sourceId;
        modelLoaders = helper.getModelLoaders(cacheFile);
        modelLoaderIndex = 0;
      }
    }

    loadData = null;
    boolean started = false;
    while (!started && hasNextModelLoader()) {
      ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
      loadData =
          modelLoader.buildLoadData(
              cacheFile, helper.getWidth(), helper.getHeight(), helper.getOptions());
      if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
        started = true;
        loadData.fetcher.loadData(helper.getPriority(), this);
      }
    }
    return started;
  }
}

和前面介绍的cacheData保存原始文件是相呼应的,首先创建了DataCacheKey,然后通过DiskLruCacheWrapper获取到cacheFile文件,然后和上面获取解码后的图片通过ByteBufferFetcher转化成byteBuffer一样的,转化完byteBuffer后,回调到DataCacheGenerator的onDataReady方法,继而回调到DecodeJob的onDataFetcherReady方法,回调的dataSource是DataSource.DATA_DISK_CACHE,表示原始图片的缓存。最后经过解码,解码完成后会进行transformation操作,最后完成bitmap的编码操作。

一级缓存的存储

在前面的章节中都讲过三级缓存和四级缓存是在什么时候存储的,那一级缓存是在什么时候存储的呢?在DecodeJob中编码前会调用notifyComplete方法,在该方法回调到EngineJob的onResourceReady方法,然后会调用到notifyCallbacksOfResult,接着会回调到Engine的onEngineJobComplete方法:

1
2
3
4
5
6
7
8
class Engine{
  public synchronized void onEngineJobComplete(
      EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
    if (resource != null && resource.isMemoryCacheable()) {
      activeResources.activate(key, resource);
    }
  }
}

如果memory缓存可用,则调用ActiveResources的activate方法:

1
2
3
4
5
6
7
8
9
synchronized void activate(Key key, EngineResource<?> resource) {
  ResourceWeakReference toPut =
      new ResourceWeakReference(
          key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);
  ResourceWeakReference removed = activeEngineResources.put(key, toPut);
  if (removed != null) {
    removed.reset();
  }
}

创建了弱引用,然后把弱引用指向的强引用对象包装起来,最后放到一级缓存的map中,put完后会把老的弱引用对象给重置掉。其实activate方法在前面介绍从二级缓存中获取到后,会从二级缓存中移除掉并加入到一级缓存中也是调用该方法。

二级缓存的存储

二级缓存是LRU算法的内存缓存,它的存储时机是在什么时候呢?我们可以通过反查代码来实现二级缓存在哪些场景被存储:

  • 一级缓存被检测到被GC回收,然后会被加入到二级缓存中。
  • 当检测到图片资源没有被使用到,也就是前面讲到的acquired变量为0的时候也会加入到二级缓存中。 下面根据这两种情况从源码角度分下下。
1
2
3
4
5
6
7
class Engine{
  private final MemoryCache cache;
  @Override
  public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    cache.put(cacheKey, resource);
  }
}

其中onResourceReleased是由两处调用,一处是ActiveResources在cleanupActiveReference中调用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class ActiveResources{
  void cleanupActiveReference(@NonNull ResourceWeakReference ref) {
    synchronized (this) {
      activeEngineResources.remove(ref.key);
      if (!ref.isCacheable || ref.resource == null) {
        return;
      }
    }
    EngineResource<?> newResource =
        new EngineResource<>(
            ref.resource,
            /* isMemoryCacheable= */ true,
            /* isRecyclable= */ false,
            ref.key,
            listener);
    listener.onResourceReleased(ref.key, newResource);
  }
}

该方法在获取一级缓存的时候,如果发现弱引用包装的强引用为空,或者在检测到弱引用队列中存在弱引用的时候,就会调用cleanupActiveReference方法,也就是发生GC的时候,发现外界强引用不再被使用的时候调用该方法。
另外一处是由EngineResource的release方法调用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
void release() {
  boolean release = false;
  synchronized (this) {
    if (acquired <= 0) {
      throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
    }
    if (--acquired == 0) {
      release = true;
    }
  }
  if (release) {
    listener.onResourceReleased(key, this);
  }
}

可以看到当发现acquired等于0的时候,就可以将缓存存到二级缓存中。而release方法调用用很多,一处是由Engine的release方法调用,一处是由Engine的decrementPendingCallbacks方法调用。也就是说当前图片资源如果没有被其他地方使用也会被加入到二级缓存中。
整体流程图如下: alt text

总结

  • glide缓存分为内存和硬盘两类,其中内存缓存又分为活跃缓存和LRU缓存,硬盘缓存分为解码后按照view尺寸展示的bitmap缓存,另外一部分是原始图片的缓存。
  • 活跃缓存:它的结构是一个hashmap,其中key是按照url、尺寸等信息拼成的,value是一个弱引用对象,其中弱引用中放的才是图片缓存对象,特点如下:
    • 存储正在使用的图片资源
    • 使用弱引用(WeakReference)
    • 通过引用计数管理生命周期
    • 不会被LRU算法回收
  • LRU缓存:它底层是一个LRU策略的缓存,key也是和上面活跃缓存用的是同一个。value存的是解码后的图片缓存。特点如下:
    • 存储最近使用过但当前未使用的图片
    • 使用强引用
    • LRU淘汰策略(最近最少使用原则)
    • 有大小限制
  • 解码后的硬盘缓存:也是使用的LRU策略的缓存,其中key是按照指定尺寸来构建的,然后从DiskLruCache中获取缓存。
  • 原始图片的硬盘缓存:也是使用的LRU策略的缓存,其中key不是通过指定尺寸来构建的,然后也是从DiskLruCache中获取缓存。
  • LRU的内存缓存是在什么存储的?
    • 从上面流程图来看每次从非活跃缓存中获取到图片缓存后,都会放入到活跃缓存中,那什么时候会放到内存的LRU缓存中呢?当view触发onViewDetachedFromWindow的时候,也就是当前view销毁后,会查看当前view绑定的图片被正在使用标记的次数,如果次数为0了,则将当前图片加入到LRU的内存缓存中。
  • glide为什么要设计两层的内存缓存?
    • 内存缓存分为活跃的缓存,它是一个map数据结构,value存储的是弱引用包装了缓存数据,另外一层是LRU级别的缓存。活跃数据指的是当前正在使用的资源,比如正在显示的图片,这部分用弱引用来保存,这样当图片不再被使用时,垃圾回收器可以自动回收,避免内存泄漏。而LRU缓存则是最近最少使用的缓存,使用强引用,当内存不足时,会移除最久未使用的资源。
    • 如果只有LRU缓存的时候,正在使用的图片可能是长时间没有被访问的图片,而此时如果只有LRU缓存的时候,可能会把正在使用的图片缓存给移除掉了,这样的话,当前正在显示的图片会出现空白,并且当再使用该图片的时候,会从磁盘中去获取该图片,这样增大了获取图片的时间。如果只有弱引用缓存的时候,此时图片不被使用了,被GC给回收了,那此时也只能从磁盘中获取,增大了获取图片的时间。如果此时有LRU缓存,能在内存中保留一段时间,降低从磁盘中读取的可能。
Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计