ASimpleCache缓存源码分析

作者: 接地气的二呆 | 来源:发表于2016-06-19 15:29 被阅读188次

    ASimpleCache 是一个为android制定的 轻量级的 开源缓存框架。轻量到只有一个java文件(由十几个类精简而来)
    仓库地址:https://github.com/yangfuhai/ASimpleCache

    官方介绍

    1、它可以缓存什么东西?

    普通的字符串、JsonObject、JsonArray、Bitmap、Drawable、序列化的java对象,和 byte数据。

    2、它有什么特色?

    • 特色主要是:
    • 1:轻,轻到只有一个JAVA文件。
    • 2:可配置,可以配置缓存路径,缓存大小,缓存数量等。
    • 3:可以设置缓存超时时间,缓存超时自动失效,并被删除。
    • 4:支持多进程。

    3、它在android中可以用在哪些场景?

    • 1、替换SharePreference当做配置文件
    • 2、可以缓存网络请求数据,比如oschina的android客户端可以缓存http请求的新闻内容,缓存时间假设为1个小时,超时后自动失效,让客户端重新请求新的数据,减少客户端流量,同时减少服务器并发量。
    • 3、您来说...

    4、如何使用 ASimpleCache?

    以下有个小的demo,希望您能喜欢:

    ACache mCache = ACache.get(this);
    mCache.put("test_key1", "test value");
    mCache.put("test_key2", "test value", 10);//保存10秒,如果超过10秒去获取这个key,将为null
    mCache.put("test_key3", "test value", 2 * ACache.TIME_DAY);//保存两天,如果超过两天去获取这个key,将为null
    

    获取数据

    ACache mCache = ACache.get(this);
    String value = mCache.getAsString("test_key1");
    

    分析

    ACache 构造

    可以由上个简单的例子可以看出 ACache是核心类,但是起构造方法是 private 的,我们只能通过ACache.get()获得其实例,由此做切入分析

        public static ACache get(Context ctx) {
            return get(ctx, "ACache");
        }
    
        public static ACache get(Context ctx, String cacheName) {
            File f = new File(ctx.getCacheDir(), cacheName);
            return get(f, MAX_SIZE, MAX_COUNT);
        }
    
        public static ACache get(File cacheDir) {
            return get(cacheDir, MAX_SIZE, MAX_COUNT);
        }
    
        public static ACache get(Context ctx, long max_zise, int max_count) {
            File f = new File(ctx.getCacheDir(), "ACache");
            return get(f, max_zise, max_count);
        }
    
        public static ACache get(File cacheDir, long max_zise, int max_count) {
            ACache manager = mInstanceMap.get(cacheDir.getAbsoluteFile() + myPid());
            if (manager == null) {
                manager = new ACache(cacheDir, max_zise, max_count);
                mInstanceMap.put(cacheDir.getAbsolutePath() + myPid(), manager);
            }
            return manager;
        }
    
    1. mCache = ACache.get(this);
    2. ACache get(Context ctx, String cacheName) ,在 /data/data/app-package-name/cache/ACache创建了缓存目录
    3. ACache get(File cacheDir, long max_zise, int max_count),后两个参数在未传入默认值,在初次运行时manager ==null,最终会调用到 ACache的 private 的构造方法,并且将结果放入mInstanceMap中,key 值为路径+Pid,value 为实例化的ACache对象
        private static final int MAX_SIZE = 1000 * 1000 * 50; // 50 mb
        private static final int MAX_COUNT = Integer.MAX_VALUE; // 不限制存放数据的数量
    

    继续看ACache 的构造方法

    private ACache(File cacheDir, long max_size, int max_count) {
        if (!cacheDir.exists() && !cacheDir.mkdirs()) {
            throw new RuntimeException("can't make dirs in " + cacheDir.getAbsolutePath());
        }
        mCache = new ACacheManager(cacheDir, max_size, max_count);
    }
    

    如果目录文件不存在会抛出异常,同时最终将全局变量 ACacheManager初始化。

    ACacheManager

    ACacheManager 是一个缓存管理器,ACache的内部类,最终的缓存的 put 和 get都由这个类管理

    private ACacheManager(File cacheDir, long sizeLimit, int countLimit) {
        this.cacheDir = cacheDir;
        this.sizeLimit = sizeLimit;
        this.countLimit = countLimit;
        cacheSize = new AtomicLong();
        cacheCount = new AtomicInteger();
        calculateCacheSizeAndCacheCount();
    }
    

    cacheSize,cacheCount 都是原子量,不用加锁保证了线程安全,

    /**
     * 计算 cacheSize和cacheCount
     */
    private void calculateCacheSizeAndCacheCount() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                int size = 0;
                int count = 0;
                File[] cachedFiles = cacheDir.listFiles();
                if (cachedFiles != null) {
                    for (File cachedFile : cachedFiles) {
                    //遍历 cacheDir 中的文件,并计算大小和数量
                        size += calculateSize(cachedFile);
                        count += 1;
                        //lastUsageDates Map 中保存了<File,最后修改时间>
                        lastUsageDates.put(cachedFile, cachedFile.lastModified());
                    }
                    cacheSize.set(size);
                    cacheCount.set(count);
                }
            }
        }).start();
    }
    

    calculateCacheSizeAndCacheCount,开了一个线程计算cacheSizecacheCount

    到这里获取缓存实例工作完成,主要完成了如下工作:

    1. 新建了缓存目录
    2. 通过ACache构造方法构造新实例,并且将该实例引用插入mInstanceMap
    3. 实例化ACacheManager,计算cacheSize和cacheCount

    缓存写数据

    /**
     * 保存 String数据 到 缓存中
     * 
     * @param key
     *            保存的key
     * @param value
     *            保存的String数据
     * @param saveTime
     *            保存的时间,单位:秒
     */
    public void put(String key, String value, int saveTime) {
        put(key, Utils.newStringWithDateInfo(saveTime, value));
    }
    
    /**
     * 保存 String数据 到 缓存中
     * 
     * @param key
     *            保存的key
     * @param value
     *            保存的String数据
     */
    public void put(String key, String value) {
        //新建文件,每一个 key 对应一个文件
        File file = mCache.newFile(key);
        BufferedWriter out = null;
        try {
            //将数据保存至文件
            out = new BufferedWriter(new FileWriter(file), 1024);
            out.write(value);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (out != null) {
                try {
                    out.flush();
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            //将文件加人mCache
            mCache.put(file);
        }
    }
    
    ------
       private File newFile(String key) {
           return new File(cacheDir, key.hashCode() + "");     //新建文件,文件名为key的整型哈希码
       }
    

    put(String key, String value, int saveTime)第三个是缓存的有效时间

    Utils.newStringWithDateInfo(saveTime, value)会将time 和 value 整合成一个 String

    在看看mCache.put(file);

    private void put(File file) {
        int curCacheCount = cacheCount.get();
        //检查文件个数是不是超过显示
        while (curCacheCount + 1 > countLimit) {
            long freedSize = removeNext();//移除旧的文件,返回文件大小
            cacheSize.addAndGet(-freedSize);//更新cacheSize
            curCacheCount = cacheCount.addAndGet(-1);//更新curCacheCount
        }
        cacheCount.addAndGet(1);
    
        long valueSize = calculateSize(file);
        long curCacheSize = cacheSize.get();
        while (curCacheSize + valueSize > sizeLimit) {
            //检查缓存大小是不是超过显示
            long freedSize = removeNext();
            curCacheSize = cacheSize.addAndGet(-freedSize);
        }
        cacheSize.addAndGet(valueSize);
    
        Long currentTime = System.currentTimeMillis();
        file.setLastModified(currentTime);//设置文件最后修改时间
        //lastUsageDates Map 中保存了<File,最后修改时间>
        lastUsageDates.put(file, currentTime); //更新 map
    }
    

    移除最不常用的文件

    **
     * 移除旧的文件
     * @return
     */
    private long removeNext() {
        if (lastUsageDates.isEmpty()) {
            return 0;
        }
    
        Long oldestUsage = null;
        File mostLongUsedFile = null;
        Set<Entry<File, Long>> entries = lastUsageDates.entrySet();//获得 Entry<K,V> 的集合
        synchronized (lastUsageDates) {
            for (Entry<File, Long> entry : entries) {
                //找出最久没有使用的
                if (mostLongUsedFile == null) {
                    mostLongUsedFile = entry.getKey();
                    oldestUsage = entry.getValue();
                } else {
                    Long lastValueUsage = entry.getValue();
                    if (lastValueUsage < oldestUsage) {
                          //数值越大,时间值越大,表示越近使用的
                        oldestUsage = lastValueUsage;
                        mostLongUsedFile = entry.getKey();
                    }
                }
            }
        }
    
        long fileSize = calculateSize(mostLongUsedFile);
        if (mostLongUsedFile.delete()) {
            lastUsageDates.remove(mostLongUsedFile);
        }
        return fileSize;
    }
    
    private long calculateSize(File file) {
        return file.length();
    }
    }
    

    缓存读数据

    public String getAsString(String key) {
    File file = mCache.get(key);    //获取文件
    if (!file.exists())
        return null;
    boolean removeFile = false;
    BufferedReader in = null;
    try {
        in = new BufferedReader(new FileReader(file));
        String readString = "";
        String currentLine;
        while ((currentLine = in.readLine()) != null) {
            readString += currentLine;
        }
        if (!Utils.isDue(readString)) { //String数据未到期
            return Utils.clearDateInfo(readString);//去除时间信息的字符串内容
        } else {
            removeFile = true;  //数据到期了则删除缓存
            return null;
        }
    } catch (IOException e) {
        e.printStackTrace();
        return null;
    } finally {
        if (in != null) {
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (removeFile)
            remove(key);
    }
    }
    
    /**
     * 判断缓存的String数据是否到期
     * 
     * @param str
     * @return true:到期了 false:还没有到期
     */
    private static boolean isDue(String str) {
        return isDue(str.getBytes());
    }
    
    /**
     * 判断缓存的byte数据是否到期
     * 
     * @param data
     * @return true:到期了 false:还没有到期
     */
    private static boolean isDue(byte[] data) {
        String[] strs = getDateInfoFromDate(data);//分别拿出前面的时间信息,和实际的数据
        if (strs != null && strs.length == 2) {
            String saveTimeStr = strs[0];
            while (saveTimeStr.startsWith("0")) {
                //去零
                saveTimeStr = saveTimeStr.substring(1, saveTimeStr.length());
            }
            long saveTime = Long.valueOf(saveTimeStr);
            long deleteAfter = Long.valueOf(strs[1]);
            if (System.currentTimeMillis() > saveTime + deleteAfter * 1000) {
                //到期了
                return true;
            }
        }
        return false;
    }
    

    JsonObject、JsonArray、Bitmap、Drawable、序列化的存入缓存都是转化为字符串/byte格式,再调用函数put(String key, String value)即可。

    我的更多 Android 博文请关注我的博客: http://xuyushi.github.io/archives/

    参考

    http://blog.csdn.net/zhoubin1992/article/details/46379055

    相关文章

      网友评论

      • ZebraWei:你能不能跟原创博客有点区别啊!
      • nothingwxq:试了下多进程删除不成功。哎。
        nothingwxq: @接地气的二呆 嗯,刚调试了下,我的问题,路径错了,我用的默认路径。
        nothingwxq: @接地气的二呆 直接简单的一个只读,另外一个写。
        接地气的二呆:@nothingwxq 没用过多进程,能详细的描述下么
      • fc681483162b:不错 不错
        接地气的二呆:@许你壹世静好岁月 :smile: 一般般啦

      本文标题:ASimpleCache缓存源码分析

      本文链接:https://www.haomeiwen.com/subject/uqmbdttx.html