Android性能优化(二)- 丝般顺滑地加载大量图片

一、SoftReference

备注:其实SoftReference已经弃用了,具体原因下面有讲到,但是为何还要讲它,下面也有原因,哈哈!

首先什么是softreference呢?直译就是软引用,看样子就是说,这是一个非强势的引用,当别人强硬起来时候,它就不行了,嘿嘿,确实是这样的!

软引用(SoftReference):

如果一个对象它只具有软引用,当内存足够的时候,GC是不会去回收它的,但是当内存不足时,就会回收这些对象所占用的内存啦。只要GC没有回收它,那么它就可以被程序使用。因此,软应用可以用来实现内存敏感的高速缓存。说得简单一点就是,当涉及会占用大量内存的图片操作时,比如加载相册,如果将其声明为软引用,那在报OOM之前,GC就去释放内存,所以就不会发生OOM的惨剧。

既然有软引用,自然就该有”硬引用”,好吧,其实没有硬,是强应用(StrongReference):结合软引用这个软柿子,这个就十分好理解了。如果一个对象具有强引用,那么GC是不会去回收它的,即便当内存不足时,JVM宁可抛出OOM的异常使程序crash,也不会靠回收强引用对象的内存来解决内存不足的问题。

软引用的实现:

private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();

public void addBitmapToCache(String path) {

// 强引用的Bitmap对象

Bitmap bitmap = BitmapFactory.decodeFile(path);

// 软引用的Bitmap对象

SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap);

// 添加该对象到Map中使其缓存

imageCache.put(path, softBitmap);

}


public Bitmap getBitmapByPath(String path) {

// 从缓存中取软引用的Bitmap对象

SoftReference<Bitmap> softBitmap = imageCache.get(path);

// 判断是否存在软引用

if (softBitmap == null) {

return null;

}

// 取出Bitmap对象,如果由于内存不足Bitmap被回收,将取得空

Bitmap bitmap = softBitmap.get();

if(bitmap==null){

return null;

}

return bitmap;

}

看到这里觉得SoftReference真的还不赖,因为会经常遇到加载大量强引用的对象啊,如果只是String那还好,但是套图啥的,额,这种东西吃内存的祖宗,OOM不来才怪,但是SoftReference确实可以有效避免OOM,万事大吉了!

BUT!!!….

二、Lrucache

Google从Android 2.3+开始宣布说,他们要从此版本开始,让GC更加频繁地去回收具有软引用对象的内存,好吧。。。动不动就被GC回收了,那我们的对象岂不就会经常丢失?对的,这样的话,SoftReference虽然不会造成OOM,但是我们的数据就会丢失,就会变的十分不可靠了(你看一组套图,结果关键部分丢了。。。++!)

可是,那怎么办呢?

果然哇,挖掘机技术还是Google强,Google提出了一个叫做LruCache的东西,这又是个什么鬼?

所谓LRU,即为 Least recently used,近期最少使用策略,唉,翻译成汉语果然就不高大上了…当然,其实很熟悉啦,操作系统还是学过的,嘿嘿。

这个LruCache要取代之前的SoftReference还是有两下子的。首先,它会把最近最常使用的对象的强引用放在LinkedHashMap里面,(因为软引用不可靠啊,既然最近常使用,一定要保证它们,嗯!),还有那些不常使用的对象兄弟们呢?就只能在内存到达警戒值得时候,被强行踢出去了。。。

LruCache出场之前,首先要知道风险啊,不然一不小心以强引用的方式存了太多的对象,那就OOM了,所以呢,首先要获得JVM能够给我分配多少内存,Runtime().getRuntime().maxmemory(),哎呀,这下我就放心了,先留个1/8给以强引用方式来存储对象,这样,这些危险的东西被锁在盒子里就不会OOM了~

但是咱也不能太小气了,如果一毛不拔的,只留给一丁点儿内存,那就还是要不断加载到内存,释放内存,加载到内存,释放内存。。。好了,这个问题,=嘛,究竟分多少内存合适呢?!好吧,咱自己心里清楚就行了,JVM给每个程序分配了32MB(有的机器不一样),结合每个对象可能占据的内存,大概能够知道该给分个多大的窝了。。。

好了,LruCache的具体实现:

private LruCache<String, Bitmap> mMemoryCache;

@Override

protected void onCreate(Bundle savedInstanceState) {

// 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。

// LruCache通过构造函数传入缓存值,以KB为单位。

int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

// 使用最大可用内存值的1/8作为缓存的大小。

int cacheSize = maxMemory / 8;

mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {

@Override

protected int sizeOf(String key, Bitmap bitmap) {

// 必须重写此方法来衡量每张图片的大小,默认返回图片数量。

return bitmap.getByteCount() / 1024;

}

};

}

&nbsp;

public void addBitmapToMemoryCache(String key, Bitmap bitmap) {

if (getBitmapFromMemCache(key) == null) {

mMemoryCache.put(key, bitmap);

}

}


public Bitmap getBitmapFromMemCache(String key) {

return mMemoryCache.get(key);

}

假设一个全屏幕的GridView使用4张800*480的图片来填充,这时大约会占用1.5MB的内存(800 * 480 * 4 / 1024 / 1024 MB),所以这个设置的LruCache可以用来缓存2.5页,所以,分配的大小判断就是这么来的。

这里可以简单看一下,分配了1/8的内存,即4MB的缓存空间,根据需要将对象存进去,存取就是键值对啦,键的话就是url,而值就是这个Bitmap的对象。

但是问题来了,我分配的这1/8的内存满了怎么办?

Cache保存一个强引用来限制内容数量,每当Item被访问的时候,此Item就会移动到队列的头部。当cache已满的时候加入新的item时,在队列尾部的item会被回收。

Soga!!原来如此~这样看来,我们貌似并没有必要去考虑这4MB内存的心酸,他自己知道怎么做,嗯!我们在实现的时候,只要首先去这块缓存中查找,如果有,那就太好了,它在LruCache中的位置也会被提升,但是如果没有的话,那我们只能去加载了。。。然后再把它添加到缓存里面。到这里貌似可以结束了。。

BUT!!!。。。

三、结合SoftReference和LruCache的二级缓存结构

这里废话这么多,终究说的是LruCache,即为缓存,既然缓存嘛,之前还听过一个东西叫做多级缓存的,不知道有木有关联呢。。。。

难道LruCache中木有,我就只能屁颠屁颠地跑去加载么。。。。既然SoftReference不可靠,那我不依赖它,但是如果作为一个辅助还是可以的嘛,至少比直接去加载资源到内存,不停地加载,释放,加载,释放要好嘛,当SoftReference实在也没有了,那我再去加载资源到内存也行啊!对啊,我貌似可以把LruCache作为一级缓存,SoftReference作为二级缓存呀!好像挺高大上的,其实就是个补丁,哈哈!这里借助用了一下SoftReferenceManager(其实就是个map嘛)

试试嘛~具体实现:

import android.graphics.Bitmap;

import android.support.v4.util.LruCache;

public class LruCacheManager {

 private static LruCacheManager lruCacheManager;

 private SoftReferenceCacheManager softReferenceCacheManager;

 private LruCache<String, Bitmap> lruCache;

 // 配置一级缓存设置LruCache

 private LruCacheManager() {

  // (Runtime.getRuntime().maxMemory()运行时系统所分配的内存资源

  lruCache = new LruCache<String, Bitmap>((int) (Runtime.getRuntime()

    .maxMemory() / 8)) {

   @Override

   protected int sizeOf(String key, Bitmap value) {

    if (value != null) {

     return value.getRowBytes() * value.getHeight();

    }

    return 0;

   }

   // 当内存资源不足时 进行调用

   @Override

   protected void entryRemoved(boolean evicted, String key,

     Bitmap oldValue, Bitmap newValue) {

    if (evicted) {

     // 放到软引用当中

     softReferenceCacheManager.put(key, oldValue);

    }

   }

  };

  // 初始化softRefrenceCacheManager

  softReferenceCacheManager = SoftReferenceCacheManager

    .getSoftReferenceCacheManager();

 }

 // 初始化LruCachaManager操作

 public static LruCacheManager getLruCacheManager() {

  if (lruCacheManager == null) {

   lruCacheManager = new LruCacheManager();

  }

  return lruCacheManager;

 }

 // 将图片添加到lrucache中

 public void putBitmap(String url, Bitmap bm) {

  lruCache.put(url, bm);

 }

 // 从lrucache中取出图片

 public Bitmap getBitmap(String url) {

  Bitmap bm = null;

  bm = lruCache.get(url);

  if (bm == null) {

   // 软引用当中取

   bm = softReferenceCacheManager.get(url);

   // 将从软引用中取出的图片添加到lrucache中,并将软引用中的删除掉

   if (bm != null) {

    lruCache.put(url, bm);

    softReferenceCacheManager.remove(url);

   }

  }

  return bm;

 }

改到这里,貌似异步加载吃内存的图片已经可以飞起了,OOM神马的,就不提了!到这里可以结束了嘛~~

BUT!!!…

四、DiskLruCache

从始至终,都是在内存里面啊,无论是软引用还是LruCache,万一内存吃紧,又来了个优先级超级高的phoneCall,好了,全shi了,啥都没了,从零开始。。。

但是为了有效避免这种情况,DiskCache又出场了!

DiskCache和LruCache用法是相似的:

private DiskLruCache mDiskLruCache;

private final Object mDiskCacheLock = new Object();

private boolean mDiskCacheStarting = true;

private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB

private static final String DISK_CACHE_SUBDIR = "thumbnails";

@Override

protected void onCreate(Bundle savedInstanceState) {

    ...

    // Initialize memory cache

    ...

    // Initialize disk cache on background thread

    File cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR);

    new InitDiskCacheTask().execute(cacheDir);

    ...

}

class InitDiskCacheTask extends AsyncTask<File, Void, Void> {

    @Override

    protected Void doInBackground(File... params) {

        synchronized (mDiskCacheLock) {

            File cacheDir = params[0];

//初始化DiskLruCache对象          

  mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);

            mDiskCacheStarting = false; // Finished initialization

            mDiskCacheLock.notifyAll(); // Wake any waiting threads

        }

        return null;

    }

}

//在需要获取图片的时候,先去DiskCache中找,实在没有再去加载到内存

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {

    ...

    // Decode image in background.

    @Override

    protected Bitmap doInBackground(Integer... params) {

        final String imageKey = String.valueOf(params[0]);

        // Check disk cache in background thread

        Bitmap bitmap = getBitmapFromDiskCache(imageKey);

        if (bitmap == null) { // Not found in disk cache

            // Process as normal

            final Bitmap bitmap = decodeSampledBitmapFromResource(

                    getResources(), params[0], 100, 100));

        }

        // Add final bitmap to caches:源码是有缺漏的,其实应该是先去内存中//找该图,比如LruCache,找不到再去DiskCache中,都没有才去加载,并且要//把它加进这两个缓存数据结构

        addBitmapToCache(imageKey, bitmap);

        return bitmap;

    }

    ...

}

public void addBitmapToCache(String key, Bitmap bitmap) {

    // Add to memory cache as before

    if (getBitmapFromMemCache(key) == null) {

        mMemoryCache.put(key, bitmap);

    }

    // Also add to disk cache

    synchronized (mDiskCacheLock) {

        if (mDiskLruCache != null && mDiskLruCache.get(key) == null) {

            mDiskLruCache.put(key, bitmap);

        }

    }

}

public Bitmap getBitmapFromDiskCache(String key) {

    synchronized (mDiskCacheLock) {

        // Wait while disk cache is started from background thread

        while (mDiskCacheStarting) {

            try {

                mDiskCacheLock.wait();

            } catch (InterruptedException e) {}

        }

        if (mDiskLruCache != null) {

            return mDiskLruCache.get(key);

        }

    }

    return null;

}

// Creates a unique subdirectory of the designated app cache directory. Tries to use external

// but if not mounted, falls back on internal storage.

public static File getDiskCacheDir(Context context, String uniqueName) {

    // Check if media is mounted or storage is built-in, if so, try and use external cache dir

    // otherwise use internal cache dir

    final String cachePath =

            Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||

                    !isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :

                            context.getCacheDir().getPath();

    return new File(cachePath + File.separator + uniqueName);

}

好吧,这个google的源码,是有缺漏的,其实应该是先去内存中找该图,比如LruCache,找不到再去DiskCache中,都没有才去加载,并且要把它加进这两个缓存数据结构,以便下次的使用。

还有个问题,为啥这里加了锁(synchronized ),这个因为在DiskCache初始化以及置值的时候,可能是一个耗时操作,在其还没有完成的时候,程序就可能去找图片了,这可不行,所以就在该过程执行过程中,将Diskcache锁上,这样就让这个请求乖乖等着咯,不过对于人类使用来说,还是非常快的,等不了多少毫秒!

五、结合SoftReference、LruCache,DiskLruCache的三级缓存结构

但是,是不是可以再结合呢,所以呢。。。结合DiskCache,我们可以建立一个三级缓存啦!

类似于Lrucache和SoftReference的二级缓存的逻辑,我们再在此基础上加上DiskCache的第三级缓存,即过程便是:

首先去LruCache一级缓存中查找该图,如果没有,就去保存有SoftReference的map二级缓存中找,如果还没有,那就去DiskCache的三级缓存中找,最后实在没有了就去加载或者下载咯。具体用法,其实是一致的,就是在最初的加载或者下载时,需要将该对象同时加到LruCache、SoftReference的Map结构以及DiskCache中。

六、ContentProvider

到这里,基本上已经可以完结了,但是google又来个挑逗的note:

Note: A ContentProvider might be a more appropriate place to store cached images if they are accessed more frequently, for example in an image gallery application.

好吧,问题还没有结束,继续挖掘。。。

怎么用ContentProvider还Cache我的图片呢?

好吧,关于如果使用ContentProvider以及其原理,下篇接着讲。

BUT!!!…

七、Lrucache源码分析

额,太罗嗦了,还是不说了,附上LruCache的源码,哈哈!

packageandroid.util;  

importjava.util.LinkedHashMap;  
importjava.util.Map;  

/** 
 * A cache that holds strong references to a limited number of values. Each time 
 * a value is accessed, it is moved to the head of a queue. When a value is 
 * added to a full cache, the value at the end of that queue is evicted and may 
 * become eligible for garbage collection. 
 * Cache保存一个强引用来限制内容数量,每当Item被访问的时候,此Item就会移动到队列的头部。
 * 当cache已满的时候加入新的item时,在队列尾部的item会被回收。
 * <p>If your cached values hold resources that need to be explicitly released, 
 * override {@link #entryRemoved}. 
 * 如果你cache的某个值需要明确释放,重写entryRemoved()
 * <p>If a cache miss should be computed on demand for the corresponding keys, 
 * override {@link #create}. This simplifies the calling code, allowing it to 
 * assume a value will always be returned, even when there's a cache miss. 
 * 如果key相对应的item丢掉啦,重写create().这简化了调用代码,即使丢失了也总会返回。
 * <p>By default, the cache size is measured in the number of entries. Override 
 * {@link #sizeOf} to size the cache in different units. For example, this cache 
 * is limited to 4MiB of bitmaps: 默认cache大小是测量的item的数量,重写sizeof计算不同item的
 *  大小。
 * <pre>   {@code 
 *   int cacheSize = 4 * 1024 * 1024; // 4MiB 
 *   LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) { 
 *       protected int sizeOf(String key, Bitmap value) { 
 *           return value.getByteCount(); 
 *       } 
 *   }}</pre> 
 * 
 * <p>This class is thread-safe. Perform multiple cache operations atomically by 
 * synchronizing on the cache: <pre>   {@code 
 *   synchronized (cache) { 
 *     if (cache.get(key) == null) { 
 *         cache.put(key, value); 
 *     } 
 *   }}</pre> 
 * 
 * <p>This class does not allow null to be used as a key or value. A return 
 * value of null from {@link #get}, {@link #put} or {@link #remove} is 
 * unambiguous: the key was not in the cache.
 * 不允许key或者value为null
 *  当get(),put(),remove()返回值为null时,key相应的项不在cache中
 */ 
publicclass LruCache<K, V> {  
    privatefinal LinkedHashMap<K, V> map;  

    /** Size of this cache in units. Not necessarily the number of elements. */ 
    privateint size; //已经存储的大小
    privateint maxSize; //规定的最大存储空间

    privateint putCount;  //put的次数
    privateint createCount;  //create的次数
    privateint evictionCount;  //回收的次数
    privateint hitCount;  //命中的次数
    privateint missCount;  //丢失的次数

    /** 
     * @param maxSize for caches that do not override {@link #sizeOf}, this is 
     *     the maximum number of entries in the cache. For all other caches, 
     *     this is the maximum sum of the sizes of the entries in this cache. 
     */ 
    publicLruCache(intmaxSize) {  
        if(maxSize <= 0) {  
            thrownew IllegalArgumentException("maxSize <= 0");  
        }  
        this.maxSize = maxSize;  
        this.map = newLinkedHashMap<K, V>(0, 0.75f, true);  
    }  

    /** 
     * Returns the value for {@code key} if it exists in the cache or can be 
     * created by {@code #create}. If a value was returned, it is moved to the 
     * head of the queue. This returns null if a value is not cached and cannot 
     * be created. 通过key返回相应的item,或者创建返回相应的item。相应的item会移动到队列的头部,
     * 如果item的value没有被cache或者不能被创建,则返回null。
     */ 
    publicfinal V get(K key) {  
        if(key == null) {  
            thrownew NullPointerException("key == null");  
        }  

        V mapValue;  
        synchronized(this) {  
            mapValue = map.get(key);  
            if(mapValue != null) {  
                hitCount++;  //命中
                returnmapValue;  
            }  
            missCount++;  //丢失
        }  

        /* 
         * Attempt to create a value. This may take a long time, and the map 
         * may be different when create() returns. If a conflicting value was 
         * added to the map while create() was working, we leave that value in 
         * the map and release the created value. 
         * 如果丢失了就试图创建一个item
         */ 

        V createdValue = create(key);  
        if(createdValue == null) {  
            returnnull;  
        }  

        synchronized(this) {  
            createCount++;//创建++  
            mapValue = map.put(key, createdValue);  

            if(mapValue != null) {  
                // There was a conflict so undo that last put  
                //如果前面存在oldValue,那么撤销put() 
                map.put(key, mapValue);  
            } else{  
                size += safeSizeOf(key, createdValue);  
            }  
        }  

        if(mapValue != null) {  
            entryRemoved(false, key, createdValue, mapValue);  
            returnmapValue;  
        } else{  
            trimToSize(maxSize);  
            returncreatedValue;  
        }  
    }  

    /** 
     * Caches {@code value} for {@code key}. The value is moved to the head of 
     * the queue. 
     * 
     * @return the previous value mapped by {@code key}. 
     */ 
    publicfinal V put(K key, V value) {  
        if(key == null|| value == null) {  
            thrownew NullPointerException("key == null || value == null");  
        }  

        V previous;  
        synchronized(this) {  
            putCount++;  
            size += safeSizeOf(key, value);  
            previous = map.put(key, value);  
            if(previous != null) {  //返回的先前的value值
                size -= safeSizeOf(key, previous);  
            }  
        }  

        if(previous != null) {  
            entryRemoved(false, key, previous, value);  
        }  

        trimToSize(maxSize);  
        returnprevious;  
    }  

    /** 
     * @param maxSize the maximum size of the cache before returning. May be -1 
     *     to evict even 0-sized elements. 
     *  清空cache空间
     */ 
    privatevoid trimToSize(int maxSize) {  
        while(true) {  
            K key;  
            V value;  
            synchronized(this) {  
                if(size < 0|| (map.isEmpty() && size != 0)) {  
                    thrownew IllegalStateException(getClass().getName()  
                            + ".sizeOf() is reporting inconsistent results!");  
                }  

                if(size <= maxSize) {  
                    break;  
                }  

                Map.Entry<K, V> toEvict = map.eldest();  
                if(toEvict == null) {  
                    break;  
                }  

                key = toEvict.getKey();  
                value = toEvict.getValue();  
                map.remove(key);  
                size -= safeSizeOf(key, value);  
                evictionCount++;  
            }  

            entryRemoved(true, key, value, null);  
        }  
    }  

    /** 
     * Removes the entry for {@code key} if it exists. 
     * 删除key相应的cache项,返回相应的value
     * @return the previous value mapped by {@code key}. 
     */ 
    publicfinal V remove(K key) {  
        if(key == null) {  
            thrownew NullPointerException("key == null");  
        }  

        V previous;  
        synchronized(this) {  
            previous = map.remove(key);  
            if(previous != null) {  
                size -= safeSizeOf(key, previous);  
            }  
        }  

        if(previous != null) {  
            entryRemoved(false, key, previous, null);  
        }  

        returnprevious;  
    }  

    /** 
     * Called for entries that have been evicted or removed. This method is 
     * invoked when a value is evicted to make space, removed by a call to 
     * {@link #remove}, or replaced by a call to {@link #put}. The default 
     * implementation does nothing. 
     * 当item被回收或者删掉时调用。改方法当value被回收释放存储空间时被remove调用,
     * 或者替换item值时put调用,默认实现什么都没做。
     * <p>The method is called without synchronization: other threads may 
     * access the cache while this method is executing. 
     * 
     * @param evicted true if the entry is being removed to make space, false 
     *     if the removal was caused by a {@link #put} or {@link #remove}. 
     * true---为释放空间被删除;false---put或remove导致
     * @param newValue the new value for {@code key}, if it exists. If non-null, 
     *     this removal was caused by a {@link #put}. Otherwise it was caused by 
     *     an eviction or a {@link #remove}. 
     */ 
    protectedvoid entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}  

    /** 
     * Called after a cache miss to compute a value for the corresponding key. 
     * Returns the computed value or null if no value can be computed. The 
     * default implementation returns null. 
     * 当某Item丢失时会调用到,返回计算的相应的value或者null
     * <p>The method is called without synchronization: other threads may 
     * access the cache while this method is executing. 
     * 
     * <p>If a value for {@code key} exists in the cache when this method 
     * returns, the created value will be released with {@link #entryRemoved} 
     * and discarded. This can occur when multiple threads request the same key 
     * at the same time (causing multiple values to be created), or when one 
     * thread calls {@link #put} while another is creating a value for the same 
     * key. 
     */ 
    protectedV create(K key) {  
        returnnull;  
    }  

    privateint safeSizeOf(K key, V value) {  
        intresult = sizeOf(key, value);  
        if(result < 0) {  
            thrownew IllegalStateException("Negative size: "+ key + "="+ value);  
        }  
        returnresult;  
    }  

    /** 
     * Returns the size of the entry for {@code key} and {@code value} in 
     * user-defined units.  The default implementation returns 1 so that size 
     * is the number of entries and max size is the maximum number of entries. 
     * 返回用户定义的item的大小,默认返回1代表item的数量,最大size就是最大item值
     * <p>An entry's size must not change while it is in the cache. 
     */ 
    protectedint sizeOf(K key, V value) {  
        return1;  
    }  

    /** 
     * Clear the cache, calling {@link #entryRemoved} on each removed entry. 
     * 清空cacke
     */ 
    publicfinal void evictAll() {  
        trimToSize(-1); // -1 will evict 0-sized elements  
    }  

    /** 
     * For caches that do not override {@link #sizeOf}, this returns the number 
     * of entries in the cache. For all other caches, this returns the sum of 
     * the sizes of the entries in this cache. 
     */ 
    publicsynchronized final int size() {  
        returnsize;  
    }  

    /** 
     * For caches that do not override {@link #sizeOf}, this returns the maximum 
     * number of entries in the cache. For all other caches, this returns the 
     * maximum sum of the sizes of the entries in this cache. 
     */ 
    publicsynchronized final int maxSize() {  
        returnmaxSize;  
    }  

    /** 
     * Returns the number of times {@link #get} returned a value that was 
     * already present in the cache. 
     */ 
    publicsynchronized final int hitCount() {  
        returnhitCount;  
    }  

    /** 
     * Returns the number of times {@link #get} returned null or required a new 
     * value to be created. 
     */ 
    publicsynchronized final int missCount() {  
        returnmissCount;  
    }  

    /** 
     * Returns the number of times {@link #create(Object)} returned a value. 
     */ 
    publicsynchronized final int createCount() {  
        returncreateCount;  
    }  

    /** 
     * Returns the number of times {@link #put} was called. 
     */ 
    publicsynchronized final int putCount() {  
        returnputCount;  
    }  

    /** 
     * Returns the number of values that have been evicted. 
     * 返回被回收的数量
     */ 
    publicsynchronized final int evictionCount() {  
        returnevictionCount;  
    }  

    /** 
     * Returns a copy of the current contents of the cache, ordered from least 
     * recently accessed to most recently accessed. 返回当前cache的副本,从最近最少访问到最多访问
     */ 
    publicsynchronized final Map<K, V> snapshot() {  
        returnnew LinkedHashMap<K, V>(map);  
    }  

    @Overridepublic synchronized final String toString() {  
        intaccesses = hitCount + missCount;  
        inthitPercent = accesses != 0? (100* hitCount / accesses) : 0;  
        returnString.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",  
                maxSize, hitCount, missCount, hitPercent);  
    }  
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值