精华内容
下载资源
问答
  • 内存缓存之间有什么区别?

    万次阅读 2012-03-04 18:53:48
    实际工作时,CPU往往需要重复读取同样的数据块,而缓存容量的增大,可以大幅度提升CPU内部读取数据的命中率,而不用再到内存或者硬盘上寻找,以此提高系统性能。 内存则是作为CPU与硬盘间的存储
     
    
    	缓存是集成于CPU当中,作为CPU运算的存储支撑。由于CPU芯片面积和成本的因素来考虑,缓存都很小。现在一般的缓存不过几M。CPU内缓存的运行频率极高,一般是和处理器同频运作,工作效率远远大于系统内存和硬盘。实际工作时,CPU往往需要重复读取同样的数据块,而缓存容量的增大,可以大幅度提升CPU内部读取数据的命中率,而不用再到内存或者硬盘上寻找,以此提高系统性能。
    	内存则是作为CPU与硬盘间的存储支撑。插在主板的内存槽中。现在内存一般为1~2G。即1G=1024M
    它是与CPU进行沟通的桥梁。计算机中所有程序的运行都是在内存中进行的,因此内存的性能对计算机的影响非常大。 内存(Memory)也被称为内存储器,其作用是用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据。只要计算机在运行中,CPU就会把需要运算的数据调到内存中进行运算,当运算完成后CPU再将结果传送出来,内存的运行也决定了计算机的稳定运行。
    展开全文
  • iOS 内存缓存和磁盘缓存

    千次阅读 2017-02-26 12:48:59
    在项目中我们难免会用到一些缓存方式来保存服务器传过来的数据,以减少服务器的压力。 缓存的方式分为两种分别为内存缓存和磁盘缓存内存缓存速度快容量小,磁盘缓存容量大速度慢可持久化。

    在项目中我们难免会用到一些缓存方式来保存服务器传过来的数据,以减少服务器的压力。 缓存的方式分为两种分别为内存缓存和磁盘缓存,内存缓存速度快容量小,磁盘缓存容量大速度慢可持久化。常见的内存缓存框架有NSCache、TMMemoryCachePINMemoryCacheYYMemoryCache。常见的磁盘缓框架存有TMDiskCache、PINDiskCache、YYCache.

    应用需要离线工作的主要原因就是改善应用所表现出的性能。将应用内容缓存起来就可以支持离线。我们可以用两种不同的缓存来使应用离线工作。第一种是**按需缓存**,这种情况下应用缓存起请求应答,就和Web浏览器的工作原理一样;第二种是**预缓存**,这种情况是缓存全部内容(或者最近n条记录)以便离线访问。
    我们可以理解为按需缓存一般是缓存在内存中的,而预缓存是缓存在磁盘中。

    缓存的策略:

    上一节中讨论到按需缓存和预缓存,它们在设计和实现上有很大的不同。按需缓存是指把从服务器获取的内容以某种格式存放在本地文件系统,之后对于每次请求,检查缓存中是否存在这块数据,只有当数据不存在(或者过期)的情况下才从服务器获取。这样的话,缓存层就和处理器的高速缓存差不多。获取数据的速度比数据本身重要。而预缓存是把内容放在本地以备将来访问。对预缓存来说,数据丢失或者缓存不命中是不可接受的,比方用户下载了文章准备在地铁上看,但却发现设备上不存在这些文章。

    像Twitter、Facebook这样的应用属于按需缓存,而腾讯视频的视频下载等则属于预缓存。

    实现预缓存可能需要一个后台线程访问数据并以有意义的格式保存,以便本地缓存无需重新连接服务器即可被编辑。编辑可能是“标记记录为已读”或“加入收藏”,或其他类似的操作。这里**有意义的格式**是指可以用这种方式保存内容,不用和服务器通信就可以在本地作出上面提到的修改,并且一旦再次连上网就可以把变更发送回服务器。

    按需缓存工作原理类似于浏览器缓存。它允许我们查看以前查看或者访问过的内容。按需缓存可以通过在打开一个视图控制器时按需地缓存数据模型(创建一个数据模型缓存)来实现,而不是在一个后台线程上做这件事。也可以在一个URL请求返回成功(200 OK)应答时实现按需缓存(创建一个URL缓存)。

    选择使用按需缓存还是预缓存的一个简便方法是判断是否需要在下载数据之后处理数据。后期处理数据可能是以用户产生编辑的形式,也可能是更新下载的数据,比如重写HTML页面里的图片链接以指向本地缓存图片。如果一个应用需要做上面提到的任何后期处理,就必须实现预缓存。

    存储缓存:

    第三方应用只能把信息保存在应用程序的沙盒中。因为缓存数据不是用户产生的,所以它应该被保存在NSCachesDirectory,而不是NSDocumentsDirectory。为缓存数据创建独立目录是一项不错的实践,通常在Library/caches文件夹下创建子文件夹。MyAppCache的目录。

    把缓存存储在缓存文件夹下的原因是iCloud(和iTunes)的备份不包括此目录。如果在Documents目录下创建了大尺寸的缓存文件,它们会在备份的时候被上传到iCloud并且很快就用完有限的空间(写作本书时大约为5 GB)。你不会这么干的——谁不想成为用户iPhone上的良民?NSCachesDirectory正是解决这个问题的。

    预缓存是用高级数据库(比如原始的SQLite)或者对象序列化框架(比如Core Data)实现的。

    展开全文
  • 缓存分为内存缓存和硬盘缓存

    CPU缓存(Cache Memory)位于CPU与内存之间的临时存储器,,它存在于CPU中,它的容量比内存小但交换速度快。CPU中加入缓存是一种高效的处理方法,这样整个内存储器(缓存+内存)就变成了既有缓存的高速度,又有内存的大容量的存储系统(System)了。

    缓存是为了处理CPU速度和内存速度的速度差异问题。内存中被CPU访问最频繁的数据和指令被拷贝入CPU中的缓存。CPU存取数据的速度非常的快,一秒钟能够存取、处理十亿条指令和数据(术语:CPU主频1G),而内存就慢很多,快的内存能够达到几十兆就不错了,可见两者的速度差异是多么的大。

    缓存的工作原理是当CPU要读取1个数据时,首先从缓存中查找,假如找到就立即读取并送给CPU处理;假如没有找到,就使用相对慢的速度从内存中读取并送给CPU处理,同时把这样个数据所在的数据块调入缓存中,能以使得以后对整块数据的读取都从缓存中进行,不必再调使用内存。CPU读取数据的顺序是先缓存后内存。  

    一级缓存和二级缓存  RAMROM相对的,RAM是掉电以后,其中的信息就消失那一种,ROM在掉电以后信息也不太会消失那一种。  RAM又分两种,一种是静态RAMSRAM;一种是动态RAMDRAM。前者的存储速度要比后者快得多,我们目前使用的内存一般都是动态RAM。有的菜鸟就说了,为了增加系统(System)的速度,把缓存扩大不就行了吗,扩大的越大,缓存的数据越多,系统(System)不就越快了吗?缓存通常都是静态RAM,速度是非常的快, 能是静态RAM集成度低(存储相同的数据,静态RAM的体积是动态RAM6倍), 价钱高(同容量的静态RAM是动态RAM的四倍), 由此能以见,扩大静态RAM作为缓存是1个非常愚蠢的行为, 能是为了提高系统(System)的性能和速度,我们必须要扩大缓存, 这样个样个就有了1个折中的方法,不扩大原来的静态RAM缓存,而是增加多个高速动态RAM做为缓存, 这样个样个些高速动态RAM速度要比常规动态RAM快,但比原来的静态RAM缓存慢, 我们把原来的静态ram缓存叫一级缓存,而把之后增加的动态RAM叫二级缓存。一级缓存和二级缓存中的内容都是内存中访问频率高的数据的拷贝品(映射),它们的存在都是为了减少高速CPU对慢速内存的访问。 通常CPU找数据或者指令的顺序是:先到一级缓存中找,找不到再到二级缓存中找,假如还找不到就仅有到内存中找了。 

    硬盘缓存Cache memory)是硬盘控制器上的一块内存芯片,具有极快的存取速度,它是硬盘内部存储和外界接口之间的缓冲器。由于硬盘的内部数据传输速度和外界介面传输速度不同,缓存在其中起到一个缓冲的作用。缓存的大小与速度是直接关系到硬盘的传输速度的重要因素,能够大幅度地提高硬盘整体性能。当硬盘存取零碎数据时需要不断地在硬盘与内存之间交换数据,如果有大缓存,则可以将那些零碎数据暂存在缓存中,减小外系统的负荷,也提高了数据的传输速度。 

    硬盘的缓存主要起三种作用一是预读取。当硬盘受到CPU指令控制开始读取数据时,硬盘上的控制芯片会控制磁头把正在读取的簇的下一个或者几个簇中的数据读到缓存中(由于硬盘上数据存储时是比较连续的,所以读取命中率较高),当需要读取下一个或者几个簇中的数据的时候,硬盘则不需要再次读取数据,直接把缓存中的数据传输到内存中就可以了,由于缓存的速度远远高于磁头读写的速度,所以能够达到明显改善性能的目的;二是对写入动作进行缓存。当硬盘接到写入数据的指令之后,并不会马上将数据写入到盘片上,而是先暂时存储在缓存里,然后发送一个“数据已写入”的信号给系统,这时系统就会认为数据已经写入,并继续执行下面的工作,而硬盘则在空闲(不进行读取或写入的时候)时再将缓存中的数据写入到盘片上。虽然对于写入数据的性能有一定提升,但也不可避免地带来了安全隐患——如果数据还在缓存里的时候突然掉电,那么这些数据就会丢失。对于这个问题,硬盘厂商们自然也有解决办法:掉电时,磁头会借助惯性将缓存中的数据写入零磁道以外的暂存区域,等到下次启动时再将这些数据写入目的地;第三个作用就是临时存储最近访问过的数据。有时候,某些数据是会经常需要访问的,硬盘内部的缓存会将读取比较频繁的一些数据存储在缓存中,再次读取时就可以直接从缓存中直接传输 

     内存与储存的差别大多数人常将内存 (Memory) 与储存空间 (Storage) 两个名字混为一谈 尤其是在谈到两者的容量的时候 内存是指 (Memory) 计算机中所安装的随机存取内存的容量;储存 (Storage) 是指计算机内硬盘的容量 另一个内存与储存最重要的差别在于 储存于硬盘中的信息在关机后能够保持完整,但任何储存在内存中的数据在计算机关机后便会全部流失。

     闪存(Flash Memory)是一种长寿命的非易失性(在断电情况下仍能保持所存储的数据信息)的存储器,数据删除不是以单个的字节为单位而是以固定的区块为单位,区块大小一般为256KB20MB。闪存是电子可擦除只读存储器(EEPROM)的变种,EEPROM与闪存不同的是,它能在字节水平上进行删除和重写而不是整个芯片擦写,这样闪存就比EEPROM的更新速度快。闪存分类:U盘、CF卡、SM卡、SD/MMC卡、记忆棒、XD卡、MS卡、TF

     还有缓存分为三级的说法: L1Cache(一级缓存)CPU第一层高速缓存,分为数据缓存和指令缓存。内置的L1高速缓存的容量和结构对CPU的性能影响较大,不过高速缓冲存储器均由静态RAM组成,结构较复杂,在CPU管芯面积不能太大的情况下,L1级高速缓存的容量不可能做得太大。一般服务器CPUL1缓存的容量通常在32256KB。  L2Cache(二级缓存)CPU的第二层高速缓存,分内部和外部两种芯片。内部的芯片二级缓存运行速度与主频相同,而外部的二级缓存则只有主频的一半。L2高速缓存容量也会影响CPU的性能,原则是越大越好,现在家庭用CPU容量最大的是512KB,而服务器和工作站上用CPUL2高速缓存更高达256-1MB,有的高达2MB或者3MB。  L3Cache(三级缓存),分为两种,早期的是外置,现在的都是内置的。而它的实际作用即是,L3缓存的应用可以进一步降低内存延迟,同时提升大数据量计算时处理器的性能。而在服务器领域增加L3缓存在性能方面仍然有显著的提升。比方具有较大L3缓存的配置利用物理内存会更有效,故它比较慢的磁盘I/O子系统可以处理更多的数据请求。具有较大L3缓存的处理器提供更有效的文件系统缓存行为及较短消息和处理器队列长度。  其实最早的L3缓存被应用在AMD发布的K6-III处理器上,当时的L3缓存受限于制造工艺,并没有被集成进芯片内部,而是集成在主板上。在只能够和系统总线频率同步的L3缓存同主内存其实差不了多少。后来使用L3缓存的是英特尔为服务器市场所推出的Itanium处理器。接着就是P4EE和至强MPIntel还打算推出一款9MBL3缓存的Itanium2处理器,和以后24MBL3缓存的双核心Itanium2处理器。  但基本上L3缓存对处理器的性能提高显得不是很重要,比方配备1MBL3缓存的XeonMP处理器却仍然不是Opteron的对手,由此可见前端总线的增加,要比缓存增加带来更有效的性能提升。


    展开全文
  • Android中缓存的必要性: 1、没有缓存的弊端: 流量开销:对于客户端——服务器端应用,从远程获取图片算是经常要用的一个功能,而图片资源往往会消耗比较大的流量。 加载速度:如果应用中图片加载...

    Android中缓存的必要性:


    1、没有缓存的弊端:
    • 流量开销:对于客户端——服务器端应用,从远程获取图片算是经常要用的一个功能,而图片资源往往会消耗比较大的流量。
    • 加载速度:如果应用中图片加载速度很慢的话,那么用户体验会非常糟糕。
    • 那么如何处理好图片资源的获取和管理呢?异步下载+本地缓存
    2、缓存带来的好处:
    • 1. 服务器的压力大大减小;
    • 2. 客户端的响应速度大大变快(用户体验好);
    • 3. 客户端的数据加载出错情况大大较少,大大提高了应有的稳定性(用户体验好);
    • 4. 一定程度上可以支持离线浏览(或者说为离线浏览提供了技术支持)。
    3、缓存管理的应用场景:
    • 1. 提供网络服务的应用;
    • 2. 数据更新不需要实时更新,即便是允许3-5分钟的延迟也建议采用缓存机制;
    • 3. 缓存的过期时间是可以接受的(不会因为缓存带来的好处,导致某些数据因为更新不及时而影响产品的形象等)
    4、大位图导致内存开销大的原因是什么?
    • 1.下载或加载的过程中容易导致阻塞;
    • 大位图Bitmap对象是png格式的图片的30至100倍;
    • 2.大位图在加载到ImageView控件前的解码过程;BitmapFactory.decodeFile()会有内存消耗。(decodeByteArray())
    5、缓存设计的要点:
    • 1.命中率;
    • 2.合理分配占用的空间;
    • 3.合理的缓存层级。

    1.1 内存缓存——LruCache源码分析

        1.1.1 LRU

        LRU,全称Least Rencetly Used,即最近最少使用,是一种非常常用的置换算法,也即淘汰最长时间未使用的对象。LRU在操作系统中的页面置换算法中广泛使用,我们的内存或缓存空间是有限的,当新加入一个对象时,造成我们的缓存空间不足了,此时就需要根据某种算法对缓存中原有数据进行淘汰货删除,而LRU选择的是将最长时间未使用的对象进行淘汰。
        

      
    1.1.2 LruCache实现原理

        根据LRU算法的思想,要实现LRU最核心的是要有一种数据结构能够基于 访问顺序来保存缓存中的对象,这样我们就能够很方便的知道哪个对象是最近访问的,哪个对象是最长时间未访问的。LruCache选择的是LinkedHashMap这个数据结构, LinkedHashMap是一个双向循环链表,在构造LinkedHashMap时,通过一个boolean值来指定LinkedHashMap中保存数据的方式,LinkedHashMap的一个构造方法如下:  
      
    /*
         * 初始化LinkedHashMap
         * 第一个参数:initialCapacity,初始大小
         * 第二个参数:loadFactor,负载因子=0.75f
         * 第三个参数:accessOrder=true,基于访问顺序;accessOrder=false,基于插入顺序
         */
        public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
            super(initialCapacity, loadFactor);
            init();
            this.accessOrder = accessOrder;
        }
    显然,在LruCache中选择的是 accessOrder = true;此时,当accessOrder 设置为 true时,每当我们更新(即调用put方法)访问(即调用get方法)map中的结点时,LinkedHashMap内部都会将这个结点移动到链表的尾部,因此,在链表的尾部是最近刚刚使用的结点,在链表的头部是是最近最少使用的结点,当我们的缓存空间不足时,就应该持续把链表头部结点移除掉,直到有剩余空间放置新结点。
    可以看到,LinkedHashMap完成了LruCache中的核心功能,那LruCache中剩下要做的就是定义缓存空间总容量,当前保存数据已使用的容量,对外提供put、get方法。
        

        1.1.3 LruCache源码分析

        在了解了LruCache的核心原理之后,就可以开始分析 LruCache的源码了。
         1)关键字段
        根据上面的分析,首先要有总容量、已使用容量、linkedHashMap这几个关键字段,LruCache中提供了下面三个关键字段:     
    //核心数据结构
        private final LinkedHashMap map;
        // 当前缓存数据所占的大小
        private int size;
        //缓存空间总容量
        private int maxSize;
          要注意的是size字段,因为map中可以存放各种类型的数据,这些数据的大小测量方式也是不一样的,比如Bitmap类型的数据和String类型的数据计算他们的大小方式肯定不同,因此,LruCache中在计算放入数据大小的方法sizeOf中,只是简单的返回了1,需要我们重写这个方法,自己去定义数据的测量方式。因此,我们在使用LruCache的时候,经常会看到这种方式:  
      
    private static final int CACHE_SIZE = 4 * 1024 * 1024;//4Mib
        LruCache bitmapCache = new LruCache(CACHE_SIZE){
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getByteCount();//自定义Bitmap数据大小的计算方式
            }
        };

         (2)构造方法  
    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只有一个唯一的构造方法,在构造方法中,给定了缓存空间的总大小,初始化了 LinkedHashMap核心数据结构,在LinkedHashMap中的第三个参数指定为true,也就设置了accessOrder=true,表示这个LinkedHashMap将是基于数据的访问顺序进行排序。

         (3)sizeOf()和safeSizeOf()方法
        根据上面的解释,由于各种数据类型大小测量的标准不统一,具体测量的方法应该由使用者来实现,如上面给出的一个在实现LruCache时重写sizeOf的一种常用实现方式。通过多态的性质,再具体调用sizeOf时会调用我们重写的方法进行测量,LruCache对sizeOf()的调用进行一层封装,如下:  
      
    private int safeSizeOf(K key, V value) {
    int result = sizeOf(key, value);
    if (result < 0) {
    throw new IllegalStateException("Negative size: " + key + "=" + value);
    }
    return result;
    }
    里面其实就是调用sizeOf()方法,返回sizeOf计算的大小。
    上面就是LruCache的基本内容,下面就需要提供 LruCache的核心功能了。

         (4)put方法缓存数据
        首先看一下它的源码实现:     
    /**
    * 给对应key缓存value,并且将该value移动到链表的尾部。
    */
    public final V put(K key, V value) {
    if (key == null || value == null) {
    throw new NullPointerException("key == null || value == null");
    }

    V previous;
    synchronized (this) {
    // 记录 put 的次数
    putCount++;
    // 通过键值对,计算出要保存对象value的大小,并更新当前缓存大小
    size += safeSizeOf(key, value);
    /*
    * 如果 之前存在key,用新的value覆盖原来的数据, 并返回 之前key 的value
    * 记录在 previous
    */
    previous = map.put(key, value);
    // 如果之前存在key,并且之前的value不为null
    if (previous != null) {
    // 计算出 之前value的大小,因为前面size已经加上了新的value数据的大小,此时,需要再次更新size,减去原来value的大小
    size -= safeSizeOf(key, previous);
    }
    }

    // 如果之前存在key,并且之前的value不为null
    if (previous != null) {
    /*
    * previous值被剔除了,此次添加的 value 已经作为key的 新值
    * 告诉 自定义 的 entryRemoved 方法
    */
    entryRemoved(false, key, previous, value);
    }
    //裁剪缓存容量(在当前缓存数据大小超过了总容量maxSize时,才会真正去执行LRU)
    trimToSize(maxSize);
    return previous;
    }
    可以看到,put()方法主要有以下几步:
    1)key和value判空,说明LruCache中不允许key和value为null;
    2)通过safeSizeOf()获取要加入对象数据的大小,并更新当前缓存数据的大小;
    3)将新的对象数据放入到缓存中,即调用 LinkedHashMap的put方法,如果原来存在该key时,直接替换掉原来的value值,并返回之前的value值,得到之前value的大小,更新当前缓存数据的size大小;如果原来不存在该key,则直接加入缓存即可;
    4)清理缓存空间,如下;

         (5)trimToSize()清理缓存空间
        当我们加入一个数据时(put),为了保证当前数据的缓存所占大小没有超过我们指定的总大小,通过调用trimToSize()来对缓存空间进行管理控制。如下:
    public void trimToSize(int maxSize) {
    /*
    * 循环进行LRU,直到当前所占容量大小没有超过指定的总容量大小
    */
    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 || map.isEmpty()) {
    break;
    }
    /**
    * 执行到这,表示当前缓存数据已超过了总容量,需要执行LRU,即将最近最少使用的数据清除掉,直到数据所占缓存空间没有超标;
    * 根据前面的原理分析,知道,在链表中,链表的头结点是最近最少使用的数据,因此,最先清除掉链表前面的结点
    */
    Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
    key = toEvict.getKey();
    value = toEvict.getValue();
    map.remove(key);
    // 移除掉后,更新当前数据缓存的大小
    size -= safeSizeOf(key, value);
    // 更新移除的结点数量
    evictionCount++;
    }
    /*
    * 通知某个结点被移除,类似于回调
    */
    entryRemoved(true, key, value, null);
    }
    }

    trimToSize()方法的作用就是为了保证当前数据的缓存大小不能超过我们指定的缓存总大小,如果超过了,就会开始移除最近最少使用的数据,直到size符合要求。trimToSize()方法在put()的时候一定会调用,在get()的时候有可能会调用。


        (6)get方法获取缓存数据
        get方法源码如下:  

    /**
    * 根据key查询缓存,如果该key对应的value存在于缓存,直接返回value
    * 访问到这个结点时,LinkHashMap会将它移动到双向循环链表的的尾部。
    * 如果如果没有缓存的值,则返回null。(如果开发者重写了create()的话,返回创建的value
    */
    public final V get(K key) {
    if (key == null) {
    throw new NullPointerException("key == null");
    }

    V mapValue;
    synchronized (this) {
    // LinkHashMap 如果设置按照访问顺序的话,这里每次get都会重整数据顺序
    mapValue = map.get(key);
    // 计算 命中次数
    if (mapValue != null) {
    hitCount++;
    return mapValue;
    }
    // 计算 丢失次数
    missCount++;
    }

    /*
    * 官方解释:
    * 尝试创建一个值,这可能需要很长时间,并且Map可能在create()返回的值时有所不同。如果在create()执行的时
    * 候,用这个key执行了put方法,那么此时就发生了冲突,我们在Map中删除这个创建的值,释放被创建的值,保留put进去的值。
    */
    V createdValue = create(key);
    if (createdValue == null) {
    return null;
    }

    /***************************
    * 不覆写create方法走不到下面 *
    ***************************/
    /*
    * 正常情况走不到这里
    * 走到这里的话 说明 实现了自定义的 create(K key) 逻辑
    * 因为默认的 create(K key) 逻辑为null
    */
    synchronized (this) {
    // 记录 create 的次数
    createCount++;
    // 将自定义create创建的值,放入LinkedHashMap中,如果key已经存在,会返回 之前相同key 的值
    mapValue = map.put(key, createdValue);

    // 如果之前存在相同keyvalue,即有冲突。
    if (mapValue != null) {
    /*
    * 有冲突
    * 所以 撤销 刚才的 操作
    * 将 之前相同key 的值 重新放回去
    */
    map.put(key, mapValue);
    } else {
    // 拿到键值对,计算出在容量中的相对长度,然后加上
    size += safeSizeOf(key, createdValue);
    }
    }

    // 如果上面 判断出了 将要放入的值发生冲突
    if (mapValue != null) {
    /*
    * 刚才create的值被删除了,原来的 之前相同key 的值被重新添加回去了
    * 告诉 自定义 的 entryRemoved 方法
    */
    entryRemoved(false, key, createdValue, mapValue);
    return mapValue;
    } else {
    // 上面 进行了 size += 操作 所以这里要重整长度
    trimToSize(maxSize);
    return createdValue;
    }
    }

       get()方法的思路就是:

       1)先尝试从map缓存中获取value,即mapVaule = map.get(key);如果mapVaule != null,说明缓存中存在该对象,直接返回即可;
       2)如果mapVaule == null,说明缓存中不存在该对象,大多数情况下会直接返回null;但是如果我们重写了create()方法,在缓存没有该数据的时候自己去创建一个,则会继续往下走,中间可能会出现冲突,看注释;
       3)注意:在我们通过LinkedHashMap进行get(key)或put(key,value)时都会对链表进行调整,即将刚刚访问get或加入put的结点放入到链表尾部。

        (7)entryRemoved()
        entryRemoved的源码如下:     

    /**
    * 1.当被回收或者删掉时调用。该方法当value被回收释放存储空间时被remove调用
    * 或者替换条目值时put调用,默认实现什么都没做。
    * 2.该方法没用同步调用,如果其他线程访问缓存时,该方法也会执行。
    * 3.evicted=true:如果该条目被删除空间 (表示 进行了trimToSize or removeevicted=falseput冲突后 或 get里成功create
    * 导致
    * 4.newValue!=null,那么则被put()get()调用。
    */
    protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {
    }
        可以发现entryRemoved方法是一个空方法,说明这个也是让开发者自己根据需求去重写的。entryRemoved()主要作用就是在结点数据value需要被删除或回收的时候,给开发者的回调。开发者就可以在这个方法里面实现一些自己的逻辑:
    (1)可以进行资源的回收;
    (2)可以实现二级内存缓存,可以进一步提高性能,思路如下: 重写LruCache的entryRemoved()函数,把删除掉的item,再次存入另外一个LinkedHashMap<String,
    SoftWeakReference>中,这个数据结构当做二级缓存,每次获得图片的时候,先判断LruCache中是否缓存,没有的话,再判断这个二级缓存中是否有,如果都没有再从sdcard上获取。sdcard上也没有的话,就从网络服务器上拉取。
        entryRemoved()在LruCache中有四个地方进行了调用:put()、get()、trimToSize()、remove()中进行了调用。

         (8)LruCache的线程安全性    
        LruCache是线程安全的,因为在put、get、trimToSize、remove的方法中都加入synchronized进行同步控制。

     1.1.4 LruCache的使用

    上面就是整个LruCache中比较核心的的原理和方法,对于LruCache的使用者来说,我们其实主要注意下面几个点:
    (1)在构造LruCache时提供一个总的缓存大小;
    (2)重写sizeOf方法,对存入map的数据大小进行自定义测量;
    (3)根据需要,决定是否要重写entryRemoved()方法;
    (4)使用LruCache提供的put和get方法进行数据的缓存
    小结:
    • LruCache 自身并没有释放内存,只是 LinkedHashMap中将数据移除了,如果数据还在别的地方被引用了,还是有泄漏问题,还需要手动释放内存;
    • 覆写 entryRemoved 方法能知道
      LruCache 数据移除是是否发生了冲突(冲突是指在map.put()的时候,对应的key中是否存在原来的值),也可以去手动释放资源;

    2.1 磁盘缓存文件缓存)——DiskLruCache分析

        LruCache是一种内存缓存策略,但是当存在大量图片的时候,我们指定的缓存内存空间可能很快就会用完,这个时候,LruCache就会频繁的进行trimToSize()操作,不断的将最近最少使用的数据移除,当再次需要该数据时,又得从网络上重新加载。为此,Google提供了一种磁盘缓存的解决方案——DiskLruCache( DiskLruCache并没有集成到Android源码中,在Android
    Doc的例子中有讲解
    )。

        2.1.1 DiskLruCache实现原理

        我们可以先来直观看一下,使用了DiskLruCache缓存策略的APP,缓存目录中是什么样子,如下图:
        
        可以看到,缓存目录中有一堆文件名很长的文件,这些文件就是我们缓存的一张张图片数据,在最后有一个文件名journal的文件,这个 journal文件是DiskLruCache的一个日志文件,即保存着每张缓存图片的操作记录,journal文件正是实现DiskLruCache的核心。看到出现了journal文件,基本可以说明这个APP使用了DiskLruCache缓存策略。
        根据对LruCache的分析,要实现LRU,最重要的是要有 一种数据结构能够基于 访问顺序 来保存缓存中的对象,LinkedHashMap是一种非常合适的数据结构,为此,DiskLruCache也选择了LinkedHashMap作为维护访问顺序的数据结构,但是,对于DiskLruCache来说,单单LinkedHashMap是不够的,因为我们不能像LruCache一样,直接将数据放置到LinkedHashMap的value中,也就是处于内存当中,在DiskLruCache中,数据是缓存到了本地文件,这里的LinkedHashMap中的value只是保存的是value的一些简要信息Entry,如唯一的文件名称、大小、是否可读等信息,如:

    private final class Entry {
    private final String key;
    /** Lengths of this entry's files. */
    private final long[] lengths;
    /** True if this entry has ever been published */
    private boolean readable;
    /** The ongoing edit or null if this entry is not being edited. */
    private Editor currentEditor;
    /** The sequence number of the most recently committed edit to this entry. */
    private long sequenceNumber;
    private Entry(String key) {
    this.key = key;
    this.lengths = new long[valueCount];
    }
    public String getLengths() throws IOException {
    StringBuilder result = new StringBuilder();
    for (long size : lengths) {
    result.append(' ').append(size);
    }
    return result.toString();
    }

    /**
    * Set lengths using decimal numbers like "10123".
    */
    private void setLengths(String[] strings) throws IOException {
    if (strings.length != valueCount) {
    throw invalidLengths(strings);
    }

    try {
    for (int i = 0; i < strings.length; i++) {
    lengths[i] = Long.parseLong(strings[i]);
    }
    } catch (NumberFormatException e) {
    throw invalidLengths(strings);
    }
    }

    private IOException invalidLengths(String[] strings) throws IOException {
    throw new IOException("unexpected journal line: " + Arrays.toString(strings));
    }

    public File getCleanFile(int i) {
    return new File(directory, key + "." + i);
    }

    public File getDirtyFile(int i) {
    return new File(directory, key + "." + i + ".tmp");
    }
    }
    DiskLruCache中对于LinkedHashMap定义如下:
    private final LinkedHashMap<String, Entry> lruEntries
    = new LinkedHashMap<String, Entry>(0, 0.75f, true);

    在LruCache中,由于数据是直接缓存中内存中,map中数据的建立是在使用LruCache缓存的过程中逐步建立的,而对于DiskLruCache,由于数据是缓存在本地文件,相当于是持久保存下来的一个文件,即使程序退出文件还在,因此,map中数据的建立,除了在使用DiskLruCache过程中建立外,map还应该包括之前已经存在的缓存文件,因此,在获取DiskLruCache的实例时,DiskLruCache会去读取journal这个日志文件,根据这个日志文件中的信息,建立map的初始数据,同时,会根据journal这个日志文件,维护本地的缓存文件。构造DiskLruCache的方法如下:

    public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
    throws IOException {
    if (maxSize <= 0) {
    throw new IllegalArgumentException("maxSize <= 0");
    }
    if (valueCount <= 0) {
    throw new IllegalArgumentException("valueCount <= 0");
    }

    // prefer to pick up where we left off
    DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
    if (cache.journalFile.exists()) {
    try {
    cache.readJournal();
    cache.processJournal();
    cache.journalWriter = new BufferedWriter(new FileWriter(cache.journalFile, true),IO_BUFFER_SIZE);
    return cache;
    } catch (IOException journalIsCorrupt) {
    // System.logW("DiskLruCache " + directory + " is corrupt: "
    // + journalIsCorrupt.getMessage() + ", removing");
    cache.delete();
    }
    }

    // create a new empty cache
    directory.mkdirs();
    cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
    cache.rebuildJournal();
    return cache;
    }

    其中,

    cache.readJournal();    
    cache.processJournal();
    正是去读取journal日志文件,建立起map中的初始数据,同时维护缓存文件。
    那journal日志文件到底保存了什么信息呢,一个标准的journal日志文件信息如下:
    libcore.io.DiskLruCache    //第一行,固定内容,声明
    1                                        //第二行,cache的版本号,恒为1
    1                                        //第三行,APP的版本号
    2                                        //第四行,一个key,可以存放多少条数据valueCount    
                                               //第五行,空行分割行
    DIRTY 335c4c6028171cfddfbaae1a9c313c52
    CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934
    REMOVE 335c4c6028171cfddfbaae1a9c313c52
    DIRTY 1ab96a171faeeee38496d8b330771a7a
    CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
    READ 335c4c6028171cfddfbaae1a9c313c52
    READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
    前五行称为journal日志文件的头,下面部分的每一行会以四种前缀之一开始: DIRTY、CLEAN、REMOVE、READ。

    以一个DIRTY前缀开始的,后面紧跟着缓存图片的key。以DIRTY这个这个前缀开头,意味着这是一条脏数据。每当我们调用一次DiskLruCache的edit()方法时,都会向journal文件中写入一条DIRTY记录,表示我们正准备写入一条缓存数据,但不知结果如何。然后调用commit()方法表示写入缓存成功,这时会向journal中写入一条CLEAN记录,意味着这条“脏”数据被“洗干净了”,调用abort()方法表示写入缓存失败,这时会向journal中写入一条REMOVE记录。也就是说,每一行DIRTY的key,后面都应该有一行对应的CLEAN或者REMOVE的记录,否则这条数据就是“脏”的,会被自动删除掉。

    在CLEAN前缀和key后面还有一个数值,代表的是该条缓存数据的大小。

    因此,我们可以总结DiskLruCache中的工作流程:

    1)初始化:通过open()方法,获取DiskLruCache的实例,在open方法中通过readJournal(); 方法读取journal日志文件,根据journal日志文件信息建立map中的初始数据;然后再调用processJournal();方法对刚刚建立起的map数据进行分析,分析的工作,一个是计算当前有效缓存文件(即被CLEAN的)的大小,一个是清理无用缓存文件;

    2)数据缓存与获取缓存:上面的初始化工作完成后,我们就可以在程序中进行数据的缓存功能和获取缓存的功能了;

    缓存数据的操作是借助DiskLruCache.Editor这个类完成的,这个类也是不能new的,需要调用DiskLruCache的edit()方法来获取实例,如下所示: 

      public Editor edit(String key) throws IOException

    在写入完成后,需要进行commit()。如下一个简单示例:

    new Thread(new Runnable() {  
        @Override  
        public void run() {  
            try {  
                String imageUrl = "https://img-my.csdn.net/uploads/201309/01/1378037235_7476.jpg";  
                String key = hashKeyForDisk(imageUrl);  //MD5对url进行加密,这个主要是为了获得统一的16位字符
                DiskLruCache.Editor editor = mDiskLruCache.edit(key);  //拿到Editor,往journal日志中写入DIRTY记录
                if (editor != null) {  
                    OutputStream outputStream = editor.newOutputStream(0);  
                    if (downloadUrlToStream(imageUrl, outputStream)) {  //downloadUrlToStream方法为下载图片的方法,并且将输出流放到outputStream
                        editor.commit();  //完成后记得commit(),成功后,再往journal日志中写入CLEAN记录
                    } else {  
                        editor.abort();  //失败后,要remove缓存文件,往journal文件中写入REMOVE记录
                    }  
                }  
                mDiskLruCache.flush();  //将缓存操作同步到journal日志文件,不一定要在这里就调用
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
        }  
    }).start(); 

    注意每次调用edit()时,会向journal日志文件写入DIRTY为前缀的一条记录;文件保存成功后,调用commit()时,也会向journal日志中写入一条CLEAN为前缀的一条记录,如果失败,需要调用abort(),abort()里面会向journal文件写入一条REMOVE为前缀的记录。
    获取缓存数据是通过get()方法实现的,如下一个简单示例:
    try {  
        String imageUrl = "https://img-my.csdn.net/uploads/201309/01/1378037235_7476.jpg";  
        String key = hashKeyForDisk(imageUrl);  //MD5对url进行加密,这个主要是为了获得统一的16位字符
         //通过get拿到value的Snapshot,里面封装了输入流、key等信息,调用get会向journal文件写入READ为前缀的记录
        DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key); 
        if (snapShot != null) {  
            InputStream is = snapShot.getInputStream(0);  
            Bitmap bitmap = BitmapFactory.decodeStream(is);  
            mImage.setImageBitmap(bitmap);  
        }  
    } catch (IOException e) {  
        e.printStackTrace();  
    } 
    3)合适的地方进行flush()
    在上面进行数据缓存或获取缓存的时候,调用不同的方法会往journal中写入不同前缀的一行记录,记录写入是通过IO下的Writer写入的,要真正生效,还需要调用writer的flush()方法,而DiskLruCache中的flush()方法中封装了writer.flush()的操作,因此,我们只需要在合适地方调用DiskLruCache中的flush()方法即可。其作用也就是将操作记录同步到journal文件中,这是一个消耗效率的IO操作,我们不用每次一往journal中写数据后就调用flush,这样对效率影响较大,可以在Activity的onPause()中调用一下即可。
    小结&注意:
    (1)我们可以在在UI线程中检测内存缓存,即主线程中可以直接使用LruCache;
    (2)使用DiskLruCache时,由于缓存或获取都需要对本地文件进行操作,因此需要另开一个线程,在子线程中检测磁盘缓存、保存缓存数据,磁盘操作从来不应该在UI线程中实现;
    (3)LruCache内存缓存的核心是LinkedHashMap,而DiskLruCache的核心是LinkedHashMap和journal日志文件,相当于把journal看作是一块“内存”,LinkedHashMap的value只保存文件的简要信息,对缓存文件的所有操作都会记录在journal日志文件中。
    DiskLruCache可能的优化方案:
        DiskLruCache是基于日志文件journal的,这就决定了每次对缓存文件的操作都需要进行日志文件的记录,我们可以不用journal文件,在第一次构造DiskLruCache的时候, 直接从程序访问缓存目录下的缓存文件,并将每个缓存文件的访问时间作为初始值记录在map的value中,每次访问或保存缓存都更新相应key对应的缓存文件的访问时间,这样就避免了频繁的IO操作,这种情况下就需要使用单例模式对DiskLruCache进行构造了,上面的Acache轻量级的数据缓存类就是这种实现方式。

    3 二级缓存

        LruCache内存缓存在解决数据量不是很大的情况下效果不错,当数据很大时,比图需要加载大量图片,LruCache指定的缓存容量可能很快被耗尽,此时LruCache频繁的替换移除淘汰文件,又频繁要进行网络请求,很有可能出现OOM,为此,在大量数据的情况下,我们可以将磁盘缓存DiskLruCache作为一个二级缓存的模式,优化缓存方案。
         流程就是,
        (1)当我们需要缓存数据的时候,既在内存中缓存,也将文件缓存到磁盘;
        (2)当获取缓存文件时,先尝试从内存缓存中获取,如果存在,则直接返回该文件;如果不存在,则从磁盘缓存中获取,如果磁盘缓存中还没有,那就只能从网络获取,获取到数据后,同时在内存和磁盘中进行缓存。
    下一篇准备根据上面的内容写一个轻量级的数据缓存框架,框架将以LruCache和DiskLruCache结合的策略进行设计,尽请期待。
    参考文章:


    1、没有缓存的弊端:
    • 流量开销:对于客户端——服务器端应用,从远程获取图片算是经常要用的一个功能,而图片资源往往会消耗比较大的流量。
    • 加载速度:如果应用中图片加载速度很慢的话,那么用户体验会非常糟糕。
    • 那么如何处理好图片资源的获取和管理呢?异步下载+本地缓存
    2、缓存带来的好处:
    • 1. 服务器的压力大大减小;
    • 2. 客户端的响应速度大大变快(用户体验好);
    • 3. 客户端的数据加载出错情况大大较少,大大提高了应有的稳定性(用户体验好);
    • 4. 一定程度上可以支持离线浏览(或者说为离线浏览提供了技术支持)。
    3、缓存管理的应用场景:
    • 1. 提供网络服务的应用;
    • 2. 数据更新不需要实时更新,即便是允许3-5分钟的延迟也建议采用缓存机制;
    • 3. 缓存的过期时间是可以接受的(不会因为缓存带来的好处,导致某些数据因为更新不及时而影响产品的形象等)
    4、大位图导致内存开销大的原因是什么?
    • 1.下载或加载的过程中容易导致阻塞;
    • 大位图Bitmap对象是png格式的图片的30至100倍;
    • 2.大位图在加载到ImageView控件前的解码过程;BitmapFactory.decodeFile()会有内存消耗。(decodeByteArray())

    5、缓存设计的要点:
    • 1.命中率;
    • 2.合理分配占用的空间;
    • 3.合理的缓存层级。
    展开全文
  • 磁盘缓存(Disk Buffer)或磁盘快取(Disk Cache)实际上是将下载到的数据先保存于系统为软件分配的内存空间中(这个内存空间被称之为“内存池”),当保存到内存池中的数据达到一个程度时,便将数据保存到硬盘中。...
  • 内存缓存、虚拟内存的区别

    千次阅读 2018-12-05 20:27:29
    其中对内存的管理是系统的最主要的职责,怎么样使有限的内存用在刀刃上,怎么要保证系统本身所需的内存(以防止死机,在win2000和winxp里这一点已经做的非常好了  缓存是CPU的一部分,它存在于CPU中  CPU存取数据...
  • 磁盘缓存内存缓存的区别

    千次阅读 2017-07-13 12:45:17
    内存缓存 高速缓存(英语:cache,英语发音:/kæʃ/ kash [1][2][3],简称缓存),其原始意义是指访问速度比一般随机存取存储器(RAM)快的一种RAM,通常它不像系统主存那样使用DRAM技术,而使用昂贵但较快速的...
  • iOS开发之缓存(一):内存缓存 iOS内存缓存和磁盘缓存的区别 iOS开发之内存缓存 磁盘缓存 沙盒
  • CPU、内存缓存的关系

    千次阅读 2019-01-10 17:06:30
    Cache也是我们经常遇到的概念,也就是平常看到的一级缓存(L1 Cache)、二级缓存(L2 Cache)、三级缓存(L3 Cache)这些数据,它位于CPU与内存之间,是一个读写速度比内存更快的存储器。当CPU向内存中写入或读出数据时...
  • 本文参考郭霖大神的DiskLruCache解析,感兴趣的朋友可以先到...当你取到图片的元数据,会将数据存入硬盘缓存以及内存缓存中。 数据的获取 取数据的时候,先从内存缓存中取; 如果没有取到,则从硬
  • 这两天在为一个应用做solr搜索方案定制的过程中,需要用到solr的fieldcache,在估算fieldcache需要的内存容量,缓存中key是int,value是两个64bit大小的long类型数组,数据量大约是8100w,64×8100w/1024/1024,大致...
  • 缓存内存的区别

    万次阅读 多人点赞 2017-04-18 07:50:17
    但是还是有很多人不知道缓存什么地方,缓存是做什么用的  其实,缓存是CPU的一部分,它存在于CPU中  CPU存取数据的速度非常的快,一秒钟能够存取、处理十亿指令和数据(术语:CPU主频1G),而内存就慢很多,...
  • 大家都知道CPU缓存很重要,但对于缓存的具体细分却知之甚少,本文只要是关于CPU缓存的介绍,并着重描述了一级缓存、二级缓存、三级缓存区别方法。 CPU缓存 CPU缓存(Cache Memory)是位于CPU与内存之间的临时存储器...
  • 什么缓存缓存策略有哪些?

    千次阅读 2019-06-30 15:46:22
    1、什么缓存? ☞ 缓存就是数据交换的缓冲区(称作:Cache),当某一硬件要读取数据时,会首先从缓存汇总查询数据,有则直接执行,不存在时从内存中获取。...电脑中最大缓存就是内存条,硬盘上也有16M或者32...
  • java 内存模型缓存和重排序-03

    万次阅读 2018-12-12 20:57:56
    缓存 为了提升性能,JVM 做了 2 件事情。 缓存+重排序 为什么会出现线程可见性问题 要想解释为什么会出现线程可见性问题,需要从计算机处理器结构谈起。 我们都知道计算机运算任务需要CPU和内存相互配合共同完成,...
  • CPU缓存内存屏障

    千次阅读 2019-01-26 21:03:32
    尽可能地避免多处理器访问主内存的时间开销,处理器大多会利用缓存(cache)以提高性能。 多级缓存 L1 Cache(一级缓存)是CPU第一层高速缓存,分为数据缓存和指令缓存。一般服务器的CPU的L1缓存的容量通常在32...
  •  一个计算机包含多种存储器比如:寄存器、高速缓存内存、硬盘、光盘等,为啥有这么多种存储方式,对于不太了解的人,总是觉得云里雾里的,搞不明白原因,直接弄一个存储器不就得了? 计算机主要的功能就是运算...
  • 缓存内存、虚拟内存分析

    千次阅读 2015-12-14 11:13:05
    许多人认为,“缓存”是内存的一部分,许多技术文章都是这样教授的,事实上这么说是不...CPU存取数据的速度非常的快,一秒钟能够存取、处理十亿指令和数据(术语:CPU主频1G),而内存就慢很多,快的内存能够达到几
  • CPU缓存是CPU与内存之间的临时数据交换器,为了解决CPU运行处理速度与内存读写速度不匹配的矛盾——缓存的速度比内存的速度快多了。 上图左侧为简易的高速缓存结构,数据的读取和存储都经过高速缓存Cache,CPU核心与...
  • centos6.5 缓存消耗内存过高

    千次阅读 2017-12-09 15:41:35
    1.清理前内存使用情况  free -m 2.开始清理  ...echo 1 > /proc/sys/vm/drop_caches ...查看内存条数命令: dmidecode | grep -A16 "Memory Device$"     +++++++++++++
  • 缓存穿透、缓存击穿、缓存雪崩区别和解决方案

    万次阅读 多人点赞 2018-09-19 14:35:57
    一、缓存处理流程  前台请求,后台先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存,并返回结果,数据库也没取到,那直接返回空结果。     二、缓存穿透  描述:  缓存...
  • 什么要用缓存缓存使用不当会造成什么后果? 分析 这个问题,互联网公司必问,要是一个人连缓存都不太清楚,那确实比较尴尬。 只要问到缓存,上来第一个问题,肯定是先问问你项目哪里用了缓存?为啥要用?不用...
  • 缓存在应用中是必不可少的,经常用的如redis、memcache以及内存缓存等。Guava是Google出的一个工具包,它里面的cache即是对本地内存缓存的一种实现,支持多种缓存过期策略。 Guava cache的缓存加载方式有两种: ...
  • MySQL查询缓存内存使用和碎片管理

    千次阅读 2018-01-03 10:04:03
    MySQL的查询缓存是完全存储在内存中,那么在配置和使用之前,我们看看MySQL是如何使用内存的。 当服务器启动的时候,需要初始化查询缓存需要的内存。这时候内存池是一个完整的空闲块,而这个空闲块的大小就是...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 341,126
精华内容 136,450
关键字:

内存条缓存是什么意思