【安卓中的缓存策略系列】安卓缓存之内存缓存LruCache
缓存策略在移动端设备上是非常重要的,尤其是在图片加载这个场景下,因为图片相对而言比较大会花费用户较多的流量,因此可用缓存方式来解决,即当程序第一次从网络上获取图片的时候,就将其缓存到存储设备上,这样在用户下次使用这张图片时就不用从网络上再次获取,这样就能为用户节省一定的流量。这个功能目前绝大部分主流APP都会使用,如腾讯QQ,微信。但很多时候为了提高APP的用户体验,我们还需要把图片在内存中缓存一份,比如ListView,我们知道LIstView会在用户将某些图片移出屏幕后将其进行回收,此时垃圾回收器会认为你不再持有这些图片的引用,从而对这些图片进行GC操作。可是为了能让程序快速运行,在界面上迅速地加载图片,必须要考虑到某些图片被回收之后,用户又将它重新滑入屏幕这种情况,而这种情况在ListView,GridView这种控件中出现是非常频繁的。采用内存缓存技术可以很好的解决上述问题,内存缓存技术允许控件可以快速地重新加载那些处理过的图片。内存缓存技术主要是通过LruCache这个类来完成的,下面从源码的角度详细讲解LruCache这个类,然后在此基础上讲解如何使用LruCache,让读者知其然更知其所以然。
一LruCache类:
首先我们来看一下类的定义及其构造函数:
public class LruCache<K, V> public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}
可以看到LruCache类使用泛型参数,其构造器参数为int型,该参数用来指定LruCache的大小,另外从其构造函数的实现过程来看,可以知道LruCache的底层是使用LinkedHashMap<K, V>来实现的,即LruCache使用一个强引用(strong referenced)的LinkedHashMap保存最近引用的对象。(A cache that holds strong references to a limited number of values.)
然后我么来看一下其重要的方法:
public final V put(K key, V value) {
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
} V previous;
synchronized (this) {
putCount++;
size += safeSizeOf(key, value);
previous = map.put(key, value);
if (previous != null) {
size -= safeSizeOf(key, previous);
}
} if (previous != null) {
entryRemoved(false, key, previous, value);
} trimToSize(maxSize);
return previous;
} public final V get(K key) {
if (key == null) {
throw new NullPointerException("key == null");
} V mapValue;
synchronized (this) {
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
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.
*/ V createdValue = create(key);
if (createdValue == null) {
return null;
} synchronized (this) {
createCount++;
mapValue = map.put(key, createdValue); if (mapValue != null) {
// There was a conflict so undo that last put
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdValue);
}
} if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
trimToSize(maxSize);
return createdValue;
}
} protected int sizeOf(K key, V value) {
return 1;
}
之所以把这三个方法拿出来讲解,是因为这三个方法它对应着我们实现的LruCache内存缓存策略的创建缓存,添加key到缓存,从缓存中获取V这三个最重要的功能,通常我们在创建缓存时会重写上述的sizeOf(K key, V value),在该方法中返回我们要创建的内存缓存的大小。而put与get则相对复杂些,我们首先来分析一下put方法。
可以看到put方法作用是缓存key,同时会将key移动到队列头部Caches {@code value} for {@code key}. The value is moved to the head of the queue.,另外它会返回与的key对应的先前的V,(return the previous value mapped by {@code key})。采用的是同步方式来实现的。
接下来我们看一下get方法。
从get方法中可以看到get包括两种情况:
1取的时候命中:直接返回与key对应的V。
2取的时候未命中:根据key产生一个V,然后调用put方法将其放入。
当get方法会将返回的值移动到队列头部。If a value was returned, it is moved to the head of the queue。
从put与get方法可以看到,put与get时会将该元素放到队列的头部,因为无论是put还是get都表示该元素目前被使用过,所以会将其放到队列的头部,当缓存中的元素超过maxSize时,会通过trimToSize函数来去除缓存中最久的元素( Map.Entry<K, V> toEvict = map.eldest();),这就是所谓的LRU算法,即最近最少使用算法,即当当缓存中的元素超过maxSize时会淘汰最近最少使用的元素。下面是trimToSize的源码:
public void trimToSize(int maxSize) {
while (true) {
K key;
V value;
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new 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);
}
}
下面是LinkedHashMap中eldest()函数的代码:
public Entry<K, V> eldest() {
LinkedEntry<K, V> eldest = header.nxt;
return eldest != header ? eldest : null;
}
通过上述代码的分析相信看官对LruCache的思想已经非常熟悉了,LruCache的底层是通过LinkedHashMap来实现的,当创建一个LruCache的对象时会让我们传入一个int型的maxSize,当我们向LruCache中put与get元素时会将该元素放到缓存队列的对头,当put元素超过maxSize时(这也是为何要传入maxSize参数的原因),会通过trimToSize函数来去除缓存中最久的元素,具体是通过Map.Entry<K, V> toEvict = map.eldest();来获取最久的元素,然后通过remove(key)的方式将其移除。
二LruCache的使用:
这个类是3.1版本中提供的,如果要在更早的Android版本中使用,则需要导入android-support-v4的jar包。
我们以BitMap对象来创建LruCache缓存为例,首先正如我在前面讲述的,一个LruCache缓存策略至少包含三个功能,即创建缓存,向缓存中添加元素,从缓存中获取元素。
代码如下:
private LruCache<String, Bitmap> mMemoryCache; @Override
protected void onCreate(Bundle savedInstanceState) {
...
// Get max available VM memory, exceeding this amount will throw an
// OutOfMemory exception. Stored in kilobytes as LruCache takes an
// int in its constructor.
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); // Use 1/8th of the available memory for this memory cache.
final int cacheSize = maxMemory / 8; mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
// The cache size will be measured in kilobytes rather than
// number of items.
return bitmap.getByteCount() / 1024;
}
};
...
} public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
} public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
我们说过缓存的目的就是为了当加载某个图片时首先从LruCache 中检查是否存在这个Bitmap。如果确实存在,它会立即被用来显示到ImageView上,如果不存在,则会开启一个后台线程去处理显示该Bitmap任务。所以我们还需要为其添加一个loadBitmap的功能,代码如下:
public void loadBitmap(int resId, ImageView imageView) {
final String imageKey = String.valueOf(resId); final Bitmap bitmap = getBitmapFromMemCache(imageKey);
if (bitmap != null) {
mImageView.setImageBitmap(bitmap);
} else {
mImageView.setImageResource(R.drawable.image_placeholder);
BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
task.execute(resId);
}
}
其中的BitmapWorkerTask 需要把解析好的Bitmap添加到内存缓存中:
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
...
// Decode image in background.
@Override
protected Bitmap doInBackground(Integer... params) {
final Bitmap bitmap = decodeSampledBitmapFromResource(
getResources(), params[0], 100, 100));
addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
return bitmap;
}
...
}
好了,以上就是本人理解的关于LruCache的知识,看官如果觉得不错,请点击下方的”顶“或“赞”按钮给我一点小小的鼓励哦!,另外看官还可以看看我的其它博客的文章哦!
【安卓中的缓存策略系列】安卓缓存之内存缓存LruCache的更多相关文章
- 【安卓中的缓存策略系列】安卓缓存策略之综合应用ImageLoader实现照片墙的效果
在前面的[安卓缓存策略系列]安卓缓存之内存缓存LruCache和[安卓缓存策略系列]安卓缓存策略之磁盘缓存DiskLruCache这两篇博客中已经将安卓中的缓存策略的理论知识进行过详细讲解,还没看过这 ...
- 【安卓中的缓存策略系列】安卓缓存策略之磁盘缓存DiskLruCache
安卓中的缓存包括两种情况即内存缓存与磁盘缓存,其中内存缓存主要是使用LruCache这个类,其中内存缓存我在[安卓中的缓存策略系列]安卓缓存策略之内存缓存LruCache中已经进行过详细讲解,如看官还 ...
- 服务器后端开发系列——《实战Memcached内存缓存系统》
1.实战Memcached缓存系统(1)Memcached基础及示例程序 内容:建筑Memcached背景知识和入门示例程序. 2.实战Memcached缓存系统(2)Memcached Java A ...
- 【腾讯Bugly干货分享】彻底弄懂 Http 缓存机制 - 基于缓存策略三要素分解法
本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/qOMO0LIdA47j3RjhbCWUEQ 作者:李 ...
- Http协议:彻底弄懂 Http 缓存机制 - 基于缓存策略三要素分解法
转载:http://mp.weixin.qq.com/s/uWPls0qrqJKHkHfNLmaenQ 导语 Http 缓存机制作为 web 性能优化的重要手段,对从事 Web 开发的小伙伴们来说是必 ...
- ios 缓存策略
NSURLRequestCachePolicy 缓存策略 1> NSURLRequestUseProtocolCachePolicy = 0, 默认的缓存策略, 如果缓存不存在,直接从服务端 ...
- 安卓中的消息循环机制Handler及Looper详解
我们知道安卓中的UI线程不是线程安全的,我们不能在UI线程中进行耗时操作,通常我们的做法是开启一个子线程在子线程中处理耗时操作,但是安卓规定不允许在子线程中进行UI的更新操作,通常我们会通过Handl ...
- Android-Universal-Image-Loader学习笔记(3)--内存缓存
前面的两篇博客写了文件缓存.如今说说Android-Universal-Image-Loader的内存缓存.该内存缓存涉及到的类如图所看到的 这些类的继承关系例如以下图所看到的: 如同文件缓存一样,内 ...
- Bitmap之内存缓存和磁盘缓存详解
原文首发于微信公众号:躬行之(jzman-blog) Android 中缓存的使用比较普遍,使用相应的缓存策略可以减少流量的消耗,也可以在一定程度上提高应用的性能,如加载网络图片的情况,不应该每次都从 ...
随机推荐
- hdu 4533 线段树(问题转化+)
威威猫系列故事——晒被子 Time Limit: 3000/1000 MS (Java/Others) Memory Limit: 65535/32768 K (Java/Others) Tot ...
- 常用SQL Server命令(持续) | Commonly used SQL Server command list (Cont')
---------------------------------------------------- 1. 查看某数据库中某表详细信息 SP_HELP USE DB_NAME GO SP_HELP ...
- 修改表单元素中placeholder属性样式、清除IE浏览器中input元素的清除图标和眼睛图标
一.修改input元素placeholder属性样式 在做项目的时候,一般表单元素的placeholder属性样式都是使用浏览器默认的,但有时候为了追求设计上的美感需要修表单元素的placeholde ...
- 8.QT-对话框(模态与非模态)
对话框介绍 对话框是于用户进行简易交互的顶层窗口 QDialog是Qt中所有对话框窗口的父类,是一种容器类型的组件 QDialog继承于QWidget类,如下图所示: QWidget和QDialog有 ...
- IBM-x3650做RAID5更换硬盘后rebuild步骤分享
1.按Ctrl+H进入WebBIOS配置 2.点击start 3.点击Drives,对slot5进行配置 4.选择slot5,选择Properties,点击Go按钮 5.选择MakeUnconfGoo ...
- 在腾讯云的ubuntu服务器上面安装git服务器
GitHub是一个免费托管开源代码的远程仓库.但是对于某些视源代码如生命的商业公司来说,既不想公开源代码,又舍不得给GitHub交保护费,那就只能自己搭建一台Git服务器作为私有仓库使用.搭建Git服 ...
- log4j的终极封装
通用型(再也不用每个类new一个logger了) public class Log { private static Map<String,Logger> loggerMap = new ...
- Linux安装JProfiler监控tomcat
下载JProfiler包wget http://download-keycdn.ej-technologies.com/jprofiler/jprofiler_linux_9_2.rpm 安装JPro ...
- C stat函数的用法举例(转载)
stat函数讲解表头文件: #include <sys/stat.h> #include <unistd.h>定义函数: int stat( ...
- Docker容器的运用
Docker 利用容器来运行应用. 容器是从镜像创建的运行实例.它可以被启动.开始.停止.删除.每个容器都是相互隔离的.保证安全的平台. 可以把容器看做是一个简易版的 Linux 环境(包括root用 ...