Android缓存机制学习笔记

一、内存缓存:———>LruCache:

1、原因:Api Level 9 垃圾回收机制更倾向于回收持有软引用和弱引用的对象,让软引用和弱引用变得不可靠。 而Bitmap放在内存的堆区域中。
原理:LruCache中的主要原理是将最近使用的对象用强引用存储在LinkedHashMap中,并且将最近最少使用的对象在缓存值达到预定值之前从内存中移除。
数据结构: LinkedHashMap LinkedHashMap是一个双向循环链表。可以基于访问顺序来保存缓存中的对象。在链表的尾部是最近刚刚使用的结点,在链表的头部是是最近最少使用的结点。
使用LinkedHashMap的原因:需要频繁的插入、删除等操作,可使用LinkedList,还需要基于key-value保存数据,还需要保存插入数据的顺序,则使用LinkedHashMap

2、成员变量、构造方法:
public class LruCache<KV> {
    private final LinkedHashMap<K, V> map;// 声明一个LinkedHashMap
    private int size;// 已经存储的数量大小
    private int maxSize;// 规定的最大存储空间
    private int putCount;// put的次数
    private int createCount;// create的次数
    private int evictionCount;// 回首的次数
    private int hitCount;// 命中的次数
    private int missCount;// 丢失的次数
    /**
     * 指定最大内存的LruCache构造方法
     /
    public LruCache(int maxSize) {// 官方推荐maxSize一般声明为手机内存的1/8
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        this.map = new LinkedHashMap<K, V>(00.75f, true);
    }
  
    public void resize(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        synchronized (this) {
            this.maxSize = maxSize;
        }
        trimToSize(maxSize);
    }

3、存放数据的方法:

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覆盖原来的数据
        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;
}


* 根据key查询缓存,如果该key对应的value存在于缓存,直接返回value;
* 访问到这个结点时,LinkHashMap会将它移动到双向循环链表的的尾部。
* 如果如果没有缓存的值,则返回null。
*/
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(K key) 逻辑
     * 
     */
    synchronized (this) {
        // 记录 create 的次数
        createCount++;
        // 将自定义create创建的值,放入LinkedHashMap中,如果key已经存在,会返回 之前相同key 的值
        mapValue = map.put(key, createdValue);

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

5、清除数据:
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);
    }
}

  /**
     * 移除已存在的元素实体
     * Removes the entry for {@code key} if it exists.
     * @return the previous value mapped by {@code key}.
     */
    public final V remove(K key) {
        if (key == null) {
            throw new 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);
        }

        return previous;
    }
    ……
    }

6、e ntryRemoved()
主要作用就是在结点数据value需要被删除或回收的时候,给开发者的回调。开发者就可以在这个方法里面实现一些自己的逻辑:
1)可以进行资源的回收;
2)可以实现二级内存缓存,可以进一步提高性能,思路如下: 重写LruCache的entryRemoved()函数,把删除掉的item,再次存入另外一个LinkedHashMap<String, SoftWeakReference<Bitmap>>中,这个数据结构当做二级缓存,每次获得图片的时候,先判断LruCache中是否缓存,没有的话,再判断这个二级缓存中是否有,如果都没有再从sdcard上获取。sdcard上也没有的话,就从网络服务器上拉取。
entryRemoved()在LruCache中有四个地方进行了调用:put()、get()、trimToSize()、remove()中进行了调用。

二、磁盘缓存:--->DiskLruCache:

在DiskLruCache中,数据是缓存到了本地文件,这里的LinkedHashMap中的value只是保存的是value的一些简要信息Entry,如唯一的文件名称、大小、是否可读等信息
工作流程:

1)初始化:通过open()方法,获取DiskLruCache的实例,在open方法中通过readJournal(); 方法读取journal日志文件,根据journal日志文件信息建立map中的初始数据;然后再调用processJournal();方法对刚刚建立起的map数据进行分析,分析的工作,一个是计算当前有效缓存文件(即被CLEAN的)的大小,一个是清理无用缓存文件;
<span style="color:#555555;">public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize) throws IOException {
        if(maxSize <= 0L) {
            throw new IllegalArgumentException("maxSize <= 0");
        } else if(valueCount <= 0) {
            throw new IllegalArgumentException("valueCount <= 0");
        } else {
            File backupFile = new File(directory, "journal.bkp");
            if(backupFile.exists()) {
                File cache = new File(directory, "journal");
                if(cache.exists()) {
                    backupFile.delete();
                } else {
                    renameTo(backupFile, cache, false);
                }
            }

            DiskLruCache cache1 = new DiskLruCache(directory, appVersion, valueCount, maxSize);
            if(cache1.journalFile.exists()) {
                try {
                    cache1.</span><span style="color:#ff0000;">readJournal</span><span style="color:#555555;">();
                    cache1.</span><span style="color:#ff0000;">processJournal</span><span style="color:#555555;">();
                    return cache1;
                } catch (IOException var8) {
                    System.out.println("DiskLruCache " + directory + " is corrupt: " + var8.getMessage() + ", removing");
                    cache1.delete();
                }
            }

            directory.mkdirs();
            cache1 = new DiskLruCache(directory, appVersion, valueCount, maxSize);
            cache1.rebuildJournal();
            return cache1;
        }
    }</span>

2)数据缓存与获取缓存:上面的初始化工作完成后,我们就可以在程序中进行数据的缓存功能和获取缓存的功能了;
缓存数据的操作是借助DiskLruCache.Editor这个类完成的,这个类也是不能new的,需要调用DiskLruCache的edit()方法来获取实例,在写入完成后,需要进行commit();
注意每次调用edit()时,会向journal日志文件写入DIRTY为前缀的一条记录;文件保存成功后,调用commit()时,也会向journal日志中写入一条CLEAN为前缀的一条记录,如果失败,需要调用abort(),abort()里面会向journal文件写入一条REMOVE为前缀的记录。
3)合适的地方进行flush()
在上面进行数据缓存或获取缓存的时候,调用不同的方法会往journal中写入不同前缀的一行记录,记录写入是通过IO下的Writer写入的,要真正生效,还需要调用writer的flush()方法,而DiskLruCache中的flush()方法中封装了writer.flush()的操作,因此,我们只需要在合适地方调用DiskLruCache中的flush()方法即可。其作用也就是将操作记录同步到journal文件中,这是一个消耗效率的IO操作,我们不用每次一往journal中写数据后就调用flush,这样对效率影响较大,可以在Activity的onPause()中调用一下即可。
 private synchronized void completeEdit(DiskLruCache.Editor editor, boolean success) throws IOException {
        DiskLruCache.Entry entry = editor.entry;
        if(entry.currentEditor != editor) {
            throw new IllegalStateException();
        } else {
            int i;
            if(success && !entry.readable) {
                for(i = 0; i < this.valueCount; ++i) {
                    if(!editor.written[i]) {
                        editor.abort();
                        throw new IllegalStateException("Newly created entry didn\'t create value for index " + i);
                    }

                    if(!entry.getDirtyFile(i).exists()) {
                        editor.abort();
                        return;
                    }
                }
            }

            for(i = 0; i < this.valueCount; ++i) {
                File dirty = entry.getDirtyFile(i);
                if(success) {
                    if(dirty.exists()) {
                        File clean = entry.getCleanFile(i);
                        dirty.renameTo(clean);
                        long oldLength = entry.lengths[i];
                        long newLength = clean.length();
                        entry.lengths[i] = newLength;
                        this.size = this.size - oldLength + newLength;
                    }
                } else {
                    deleteIfExists(dirty);
                }
            }

            ++this.redundantOpCount;
            entry.currentEditor = null;
            if(entry.readable | success) {
                entry.readable = true;
                this.journalWriter.append("CLEAN");
                this.journalWriter.append(' ');
                this.journalWriter.append(entry.key);
                this.journalWriter.append(entry.getLengths());
                this.journalWriter.append('\n');
                if(success) {
                    entry.sequenceNumber = (long)(this.nextSequenceNumber++);
                }
            } else {
                this.lruEntries.remove(entry.key);
                this.journalWriter.append("REMOVE");
                this.journalWriter.append(' ');
                this.journalWriter.append(entry.key);
                this.journalWriter.append('\n');
            }

            this.journalWriter.flush();
            if(this.size > this.maxSize || this.journalRebuildRequired()) {
                this.executorService.submit(this.cleanupCallable);
            }

        }
    }





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值