Android 缓存 - LruCache

Android内存缓存之LruCache LruCache是什么? LruCache 是least recentlly used 的简称,意思是最近最少使用算法,它可以实现将最近最少使用的对象回收掉,从而保证内存不会超出范围可将LruCache当成一个容器,它就相当于一个map集合。   为什么使用LruCache来管理内存?          因为从Android 2.3以后,垃圾回收机制会更倾向于回收持有软引用和弱引... 阅读详情

在开发Android过程中,多少会涉及到缓存。例如加载网络图片,不能每次要显示某张网络图片,都要从网络下载,这样的话,不仅浪费用户流量,还可能会造成不好的体验。一般的做法都是先将图片加载到本地保存起来,下次还需要显示同一张图片,直接从内存中获取就行,无需通过网络。这就是缓存了。

我们都知道,Android的内存少的可怜,动不动就OOM,所以缓存需要一定的策略,如LRU,LFU,FOFI。

LRU(Least Recently Used),最近最少使用原则,也就是淘汰最长时间未使用的对象。如缓存的顺序为
A -> B -> C -> D -> A
假如已缓存满了,此时要假如一个E,根据最近最少使用原则,则会淘汰B,那么容器中只剩下ACDE。

我们先看一下怎么使用这个LruCache缓存数据:

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

从源码分析LruCache,从属性看起:

// 保存数据的Map
private final LinkedHashMap<K, V> map;
// 当前size,不一定是个数,从上面的例子可以看出,我们可以自定义size
private int size;
// 最大size,同上
private int maxSize;
// put个数
private int putCount;
// get没击中后,创建个数
private int createCount;
// 剔除的个数
private int evictionCount;
// get击中(根据key找到value)个数
private int hitCount;
// get没击中个数
private int missCount;

LruCache使用了LinkedHashMap保存数据。LinkedHashMap有个特性,就是保存的数据按访问顺序来排列的,举个例子就很清楚了:

public class Tester {
    private static int MAX=5;
    public static void main(String[] args) {
        HashMap<String,String> map = new LinkedHashMap<String, String>(MAX/2,0.5f,true) {
            @Override
            protected boolean removeEldestEntry(Map.Entry<String, String> eldest){
                // 如果超过了MAX个,就允许删除最老一个
                if(size() > MAX) {
                    return true;
                }
                return false;
            }
        };
        map.put("a", "a");
        map.put("b", "b");
        map.put("c", "c");
        map.put("d", "d");
        map.put("e", "e");
        map.put("c", "c");
        for (Map.Entry<String, String> entry : map.entrySet()) {  
            System.out.print(entry.getValue() + ", ");  
        }  
        System.out.println();
        map.get("b");
        for (Map.Entry<String, String> entry : map.entrySet()) {  
            System.out.print(entry.getValue() + ", ");  
        }  
        System.out.println();
        map.put("f", "f");
        for (Map.Entry<String, String> entry : map.entrySet()) {  
            System.out.print(entry.getValue() + ", ");  
        }  
        System.out.println();
    }
}

打印结果为:

a, b, d, e, c, 
a, d, e, c, b, 
d, e, c, b, f,

LruCache正好利用了LinkedHashMap这个特性做到最近最少使用原则的。再看构造函数:

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);
}

设置了最大size,并初始化了LinkedHashMap,我们结合一下LinkedHashMap构造函数了解初始化参数:

public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {  
    super(initialCapacity, loadFactor);  
    init();  
    this.accessOrder = accessOrder;  
}  

initialCapacity是初始化空间大小
loadFactor是加载因子,就是当他的size大于initialCapacity*loadFactor的时候就会扩容
accessOrder是否按照访问顺序排列数据

LruCache的put方法是缓存数据的,代码如下:

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增加新值大小
        size += safeSizeOf(key, value);
        previous = map.put(key, value);
        if (previous != null) {
            // 如果之前有值,size减去旧值大小
            size -= safeSizeOf(key, previous);
        }
    }
    if (previous != null) {
        // 提供子类释放旧的资源
        entryRemoved(false, key, previous, value);
    }
    // 限制大小
    trimToSize(maxSize);
    return previous;
}

put方法先调用了safeSizeOf方法计算要缓存数据的大小:

private int safeSizeOf(K key, V value) {
    int result = sizeOf(key, value);
    if (result < 0) {
        throw new IllegalStateException("Negative size: " + key + "=" + value);
    }
    return result;
}

safeSizeOf调用了sizeOf方法计算:

protected int sizeOf(K key, V value) {
   return 1;
}

sizeOf方法默认返回1,也就是LruCache默认计算大小的方式是,我们put一个数据,size加1,所以一般我们会重写这个方法,如:

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

我们继续看put方法,计算完大小之后,然后调用map.put(key, value)方法把数据加入到LinkedHashMap中。如果之前有数据,也就是previous不为null,size要减去previous的大小。如果previous不为null,还调用了entryRemoved方法:

protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}

竟然是一个空的方法,也就是子类可以重写这个方法。根据官方解释,如果你的缓存值,需要显式释放的资源,你可以调用这个方法。明白了吧!!!继续put方法,最后调用了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;
            }
            // 如果当前size大于最大size,取出最老(不常)的一个
            Map.Entry<K, V> toEvict = map.eldest();
            if (toEvict == null) {
                break;
            }
            key = toEvict.getKey();
            value = toEvict.getValue();
            // 从LinkedHashMap移除
            map.remove(key);
            // 减去移除数据的大小
            size -= safeSizeOf(key, value);
            evictionCount++;
        }
        entryRemoved(true, key, value, null);
    }
}

从代码注释的分析来看,trimToSize方法是限制大小,如果当前size超过最大的size,剔除最近最不常用的数据。put方法分析完毕,一般的套路都是,有put就是get,我们看看get方法:

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.
     */
    // 尝试根据key创建新的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增加新创建的值大小
            size += safeSizeOf(key, createdValue);
        }
    }
    if (mapValue != null) {
        entryRemoved(false, key, createdValue, mapValue);
        // 返回查找的值
        return mapValue;
    } else {
        // 限制大小
        trimToSize(maxSize);
        // 返回新创建的值
        return createdValue;
    }
}

先从map中根据key找value,如果value不为空,直接返回value,结束查找。如果没找到,调用create(key)方法尝试创建一个,如果不为null,就保存到map中:

protected V create(K key) {
    return null;
}

默认返回一个null对象,所以这个方法和entryRemoved方法一样,提供子类重写,意思是,如果我们获取不到key对应的值,重写之后,可以提供一个默认的值,并保存到map中。保存的过程中,会涉及到线程不同问题,还要判断一下,是不是已经有值了(如果你觉得:就是因为找不到value,才创建新的,为何还要判断map的key是否有值,那证明你线程同步还得学习一下)。如果有值,就不把新创建的加入到map中,并返回该值。如果没有值,就把心创建的加入map中,调用trimToSize重新限制大小,并返回新创建的值。

LruCache提供了remove方法删除缓存,代码:

public final V remove(K key) {
    if (key == null) {
        throw new NullPointerException("key == null");
    }
    V previous;
    synchronized (this) {
        previous = map.remove(key);
        // 如果删除成功,size减去移除数据的大小
        if (previous != null) {
            size -= safeSizeOf(key, previous);
        }
    }
    // 提供子类释放资源
    if (previous != null) {
        entryRemoved(false, key, previous, null);
    }
 // 返回当前移除的对象
    return previous;
}

还提供了清除所有缓存的方法:

public final void evictAll() {
    trimToSize(-1); // -1 will evict 0-sized elements
}

LruCache允许调用resize方法重新定义最大size:

public void resize(int maxSize) {
    if (maxSize <= 0) {
        throw new IllegalArgumentException("maxSize <= 0");
    }
    synchronized (this) {
        this.maxSize = maxSize;
    }
    trimToSize(maxSize);
}

LruCache源码并不多,也比较容易理解。

微信小程序后台定位与实时位置更新的实战指南 本文提供了微信小程序实现后台定位与实时位置更新的完整实战指南。针对小程序退到后台后位置更新中断的痛点,详细解析了基于`startLocationUpdateBackground`API的后台定位解决方案。内容涵盖从隐私协议配置、项目权限声明,到授权、启动、监听、停止的核心代码流程,并分享了真机调试、性能优化及权限引导等关键避坑经验,帮助开发者构建稳定可靠的后台位置服务。 阅读详情

相关推荐

基于STM32和K210的自动驾驶小车设计

自动驾驶技术正在迅速发展,成为了汽车行业和智能交通领域的热点之一。随着科技的发展,STM32和K210这两款芯片为自动驾驶小车提供了优秀的硬件支持。本文我们将学习如何使用STM32和K210打造一个简单的自动驾驶小车。提示:以下是本篇文章正文内容,下面案例可供参考通过这篇文章,掌握了如何使用STM32和K210构建一个简单的自动驾驶小车。通过了解基本的电路设计,并编写了STM32和K210的代码。通过这个例子,可以更好地理解如何在嵌入式系统中使用STM32和K210,以及如何实现自动驾驶小车的简单功能。

Kyle_Chan_zMH的博客 2219

Android 关于使用LruCache缓存你想缓存的数据

今天我们来一起学习一下缓存技术,相信大家做开发的时候都知道请求网络数据的重要,但是有一些只用请求一次就过时性的消息比如某些新闻信息,如果我们每次进入新闻界面就从新从网络上获取势必会给用户带来不好的体验,所以我们需要缓存技术来帮我们解决这一问题。 1,LruCache介绍 核心的类是LruCache (此类在android-support-v4的包中提供) 。这个类非常适合用来缓存图片,它的主要

llixiangjian的博客 532

解决西门子Step 7 basic 找不到许可证问题

你在使用博图软件是否遇到过Step 7 basic 找不到许可证的问题呢!不言怕,今天我就来告诉你该如何处理吧!看步骤: 1.删除原授权的License: 打开Automation License Manager应用,然后我们可以发现仅有的License Key前面有一个时钟,说明它已经过期不可用,我们就要去寻求新的Key. 打开“我的电脑”,选择C盘,点击上方查看 点击选项–>查看 然...

让技术没有门槛 14万+

【Android】LruCache简单用法示例

LruCache简单用法总结LruCache相当于一个Map,存储的是键值对.public class BitmapCache { private static BitmapCache cache; private LruCache<String, Bitmap> mMemoryCache; public static BitmapCache getInstance() {

succ 995

SpringBoot+VUE3实战:如何用WebSocket搞定网页端语音通话(避坑指南)

本文详细介绍了如何使用SpringBoot和VUE3结合WebSocket技术实现网页端语音通话功能。从环境配置、WebSocket服务端实现到前端音频处理,提供了完整的开发指南和避坑建议,帮助开发者快速构建稳定高效的实时语音通信系统。

weixin_29021195的博客 221

新手必看:CiscoPacket Tracer从零搭建局域网(含ARP协议详解)

本文为网络新手提供了一份使用Cisco Packet Tracer从零搭建局域网的详细指南。通过构建基础网络拓扑、配置IP地址,并深入剖析ARP协议的工作原理,帮助读者理解IP地址与MAC地址的映射关系,掌握局域网通信的核心机制。

weixin_29275257的博客 205

Android缓存机制——LruCache

LruCache 顾名思义就是使用LRU缓存策略的缓存,那么LRU是什么呢?最近最少使用到的(least recently used),就是当超出缓存容量的时候,就优先淘汰链表中最近最少使用的那个数据。讲到LruCache,其实最关键的还是LinkedHashMap。LruCache的源码本身很少,而实现了LRU的功能都是靠LinkedHashMap。

chuyouyinghe的专栏 1228

【黑马点评】项目知识点及面经整理

【黑马点评】项目知识点及面经整理

m0_52031708的博客 6605

Android LruCache

当调用put()方法时,就会在集合中添加元素,并调用trimToSize()判断缓存是否已满,如果满了就用LinkedHashMap的迭代器删除队首元素,即近期最少访问的元素。当调用LruCache的get()方法获取集合中的缓存对象时,就代表访问了一次该元素,将会更新队列,保持整个队列是按照访问顺序排序。有以上结果可以看出,这个设置为true时,如果对一个元素进行了操作(put,get),就会把那个元素放到集合的最后,设置为false时,无论怎么操作,集合元素的顺序都是按照插入的顺序来进行存储的。

热爱不止,前行不息 2155

LCZ数据下载及处理-以重庆市为例(完整Python代码)

LCZ数据下载及处理-以重庆市为例(完整Python代码)

WW、forever的博客 611

高性能算力中心 — RDMA — Overview

目录 文章目录目录RDMA基于以太网的 RoCE/RDMA 计算优化NVMeNVMe Over Fabrics基于以太网的 NVMe 存储优化 RDMA RDMA(Remote DMA,远程直接内存访问)是一种新的内存访问技术,RDMA 让计算机可以直接存取其他计算机的内存,而不需要经过 CPU 耗时的处理。RDMA 将数据从一个系统快速移动到远程系统存储器中,而不对操作系统造成任何影响。RDMA 技术的原理及其与 TCP/IP 架构的对比如下图所示。 InfiniBand(IB):从一开始就支持 R

烟云的计算 2366

Android 三级缓存 LruCache缓存机制

Android三级缓存机制 LruCache缓存机制

zhentouleiyu的博客 3020

2篇5章1节:实验流行病学的核心特征和设计原则

本文系统阐释实验流行病学的核心定义、四大核心特征、四大基本原则及主流设计类型,结合骨科、肿瘤科、儿科、精神科、内外妇儿及公共卫生等多学科临床实践案例,具象化解读人为干预、随机分组、平行对照、前瞻性设计的核心内涵与应用价值。同时,规范梳理对照、随机、盲法、重复四大研究基本原则,详细介绍临床试验、现场试验、社区试验三类主流研究设计的适配场景与应用差异。

MD Code 176

Android LruCache 缓存

使用场景 LruCache使用最多的场景是图片缓存。 优点:可以规定缓存大小、有效避免OOM、自动移除队尾不用的图片缓存。

Beluga_白鲸 1301

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

【安卓中的缓存策略系列】安卓缓存之内存缓存LruCache

缓存策略在移动端设备上是非常重要的,尤其是在图片加载这个场景下,因为图片相对而言比较大会花费用户较多的流量,因此可用缓存方式来解决,即当程序第一次从网络上获取图片的时候,就将其缓存到存储设备上,这样在用户下次使用这张图片时就不用从网络上再次获取,这样就能为用户节省一定的流量。这个功能目前绝大部...

chicaochan5767的博客 109

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

Android之内存缓存——LruCache的使用及原理

Android的缓存方法有内存缓存和磁盘缓存,像一些实时的图片展示一般都是用内存缓存的,例如微博、朋友圈的图片;而一些相对固定的图片数据比如有些客户端的图片资源需要从网上下载然后存到磁盘中,以后每次启动只需从缓存中获取即可。今天就来探究一下内存缓存的实现及其原理。 Android的内存缓存使用LruCache类实现,如果容器已满,就使用LRU置换算法替换掉某个数据。什么是LRU置换算法呢? L

Jali_li的博客 1575

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

Android 使用 LruCache 缓存图片详解

本文详细介绍了Android中使用LruCache实现图片内存缓存的方法。针对列表等场景下大量图片加载导致的内存问题,提出通过LruCache建立最近最少使用算法缓存,并给出具体实现代码,包括缓存大小计算、图片存取等。同时补充了DiskLruCache磁盘缓存方案,建议采用内存+磁盘的二级缓存策略,优先内存读取,其次磁盘获取,最后网络下载。这种方案能提升加载速度、减少流量消耗,并支持离线访问。文中还对比了新旧缓存技术的差异,强调了合理设置缓存大小的重要性,为Android图片加载优化提供了完整解决方案。

xingfuyisheng的专栏 750

彻底解析Android缓存机制——LruCache

关于Android的三级缓存,其中主要的就是内存缓存和硬盘缓存。这两种缓存机制的实现都应用到了LruCache算法,今天我们就从使用到源码解析,来彻底理解Android中的缓存机制。 一、Android中的缓存策略 一般来说,缓存策略主要包含缓存的添加、获取和删除这三类操作。如何添加和获取缓存这个比较好理解,那么为什么还要删除缓存呢?这是因为不管是内存缓存还是硬盘缓存,它们的缓存大小都是有限的。当缓存满了之后,再想其添加缓存,这个时候就需要删除一些旧的缓存并添加新的缓存。 因此LRU(Least .

ZyClient的博客 2406

Android | LruCache缓存策略

LruCache是Android中的一个缓存类,它使用了最近最少使用(Least Recently Used)的策略来管理缓存数据。LruCache可以用于缓存一些频繁使用的数据,以提高应用程序的性能。LruCache的工作原理是通过一个双向链表和一个哈希表来实现的。双向链表用于按照访问顺序来存储缓存数据,最近访问的数据会被放在链表的头部,最久未访问的数据会被放在链表的尾部。哈希表用于快速查找缓存数据。

star_nwe的博客 1369

理解Android缓存机制——LruCache

关于Android的三级缓存,其中主要的就是内存缓存和硬盘缓存。这两种缓存机制的实现都应用到了LruCache算法,今天我们就从使用到源码解析,来彻底理解Android中的缓存机制。 一、Android中的缓存策略 一般来说,缓存策略主要包含缓存的添加、获取和删除这三类操作。如何添加和获取缓存这个比较好理解,那么为什么还要删除缓存呢?这是因为不管是内存缓存还是硬盘缓存,它们的缓存大小都是有限的。当缓存满了之后,再想其添加缓存,这个时候就需要删除一些旧的缓存并添加新的缓存。 因此LRU(Least .

qijingwang的博客 429

Android中的缓存策略--LruCache

一、Android中的缓存策略一般来说,缓存策略主要包含缓存的添加、获取和删除这三类操作。如何添加和获取缓存这个比较好理解,那么为什么还要删除缓存呢?这是因为不管是内存缓存还是硬盘缓存,它们的缓存大小都是有限的。当缓存满了之后,再想其添加缓存,这个时候就需要删除一些旧的缓存并添加新的缓存。因此LRU(Least Recently Used)缓存算法便应运而生,LRU是近期最少使用的算法,它的核心思想

u014732103的博客 1955

android 文章缓存,Android使用 LruCache 缓存图片

使用图片缓存技术--------------------------------------------------------------------------------------------------------------------------------------在你应用程序的UI界面加载一张图片是一件很简单的事情,但是当你需要在界面上加载一大堆图片的时候,情况就变得复杂起来。...

weixin_42297591的博客 209

android 缓存技术lrucache

Lrucache入门,简单易懂

胡世林的博客 509

android lrucache,Android——使用LruCache缓存图片

为什么要缓存图片:(1)根据具体设备的不同Android系统为每个应用分配了固定内存空间供应用使用;(2)图片是一种很占内存的资源文件,如果界面中加载一张图片还好说,如果要加载大量的图片就会超过系统为应用分配的空间发生OOM异常,使程序奔溃;2.LruCache:主要算法原理:将最近使用的对象用强应用存储到LinkedHashMap中,在缓存值达到最大是把最近最少使用的对象从缓存区中移除;3.实现...

weixin_36136552的博客 425

安卓缓存LruCache、DiskLruCache、SharedPreference、SqlLite-1LruCache

安卓缓存LruCache、DiskLruCache、SharedPreference、SqlLite

junjle的专栏 424
上一篇: Android View - RemoteViews
下一篇: Android 缓存 - DiskLruCache
JohanMan
博客等级 码龄10年 72粉丝 131原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值