基于LRU算法的本地文件緩存

需求:

  • 管理下載文件贪庙、按照LRU算法刪除不用的文件
  • 如果磁盤空間低于預(yù)期止邮,預(yù)警提示
  • 如果下載文件過大导披,預(yù)警提示

緩存淘汰算法--LRU算法

LRU(Least recently used撩匕,最近最少使用)算法根據(jù)數(shù)據(jù)的歷史訪問記錄來進(jìn)行淘汰數(shù)據(jù)筑凫,其核心思想是“如果數(shù)據(jù)最近被訪問過巍实,那么將來被訪問的幾率也更高”。

實(shí)現(xiàn)

說明
  • FileCacheOptions類 用于配置緩存的信息
    • cacheRootPath 緩存路徑
    • maxFileCount 最大文件數(shù)
    • maxCacheSize 最大緩存空間
    • isUseFileCache 是否使用緩存等
  • LRUFileCache 采用Lru算法思路來管理下載文件
核心代碼

_ 全部代碼參考:https://github.com/CJstar/Android-ImageFileCache _

  • FileCacheOptions
public final class FileCacheOptions {
    /**
     * the file cache root path
     */
    private String cacheRootPath;
    /**
     * file cache count
     */
    private int maxFileCount;
    /**
     * file cache max size: byte
     */
    private long maxCacheSize;
    /**
     * if it is false, will not cache files
     */
    private boolean isUseFileCache = true;

    /**
     *  free sd space needed cache
     */
    private long minFreeSDCardSpace;

    public String getCacheRootPath() {
        return cacheRootPath;
    }

    public void setCacheRootPath(String cacheRootPath) {
        this.cacheRootPath = cacheRootPath;
    }

    public int getMaxFileCount() {
        return maxFileCount;
    }

    public void setMaxFileCount(int maxFileCount) {
        this.maxFileCount = maxFileCount;
    }

    /**
     * cache size in bytes
     *
     * @return
     */
    public long getMaxCacheSize() {
        return maxCacheSize;
    }

    public void setMaxCacheSize(long maxCacheSize) {
        this.maxCacheSize = maxCacheSize;
    }

    public boolean isUseFileCache() {
        return isUseFileCache;
    }

    public void setIsUseFileCache(boolean isUseFileCache) {
        this.isUseFileCache = isUseFileCache;
    }

    private FileCacheOptions(Builder builder) {
        setCacheRootPath(builder.getCacheRootPath());
        setIsUseFileCache(builder.isUseFileCache());
        setMaxCacheSize(builder.getMaxCacheSize());
        setMaxFileCount(builder.getMaxFileCount());
    }

    /**
     * This is the options set builder, we can create the options by this method
     */
    public static class Builder {
        private String cacheRootPath;
        private int maxFileCount;
        private long maxCacheSize;
        private boolean isUseFileCache;

        public Builder() {
        }

        public String getCacheRootPath() {
            return cacheRootPath;
        }

        public Builder setCacheRootPath(String cacheRootPath) {
            this.cacheRootPath = cacheRootPath;
            return this;
        }

        public int getMaxFileCount() {
            return maxFileCount;
        }

        public Builder setMaxFileCount(int maxFileCount) {
            this.maxFileCount = maxFileCount;
            return this;
        }

        public long getMaxCacheSize() {
            return maxCacheSize;
        }

        public Builder setMaxCacheSize(long maxCacheSize) {
            this.maxCacheSize = maxCacheSize;
            return this;
        }

        public boolean isUseFileCache() {
            return isUseFileCache;
        }

        public Builder setIsUseFileCache(boolean isUseFileCache) {
            this.isUseFileCache = isUseFileCache;
            return this;
        }

        public FileCacheOptions builder() {
            return new FileCacheOptions(this);
        }
    }
}

  • LRUFileCache
public class LRUFileCache implements FileCache {

    /**
     * file cache  config
     */
    private FileCacheOptions options;
    /**
     * cache file suffix
     */
    private static final String WHOLESALE_CONV = "";
    /**
     * mini free space on SDCard  100M
     */
    private static final long FREE_SD_SPACE_NEEDED_TO_CACHE = 100 * 1024 * 1024L;

    private static LRUFileCache mLRUFileCache;

    public static LRUFileCache getInstance() {
        if (mLRUFileCache == null) {
            synchronized (LRUFileCache.class) {
                if (mLRUFileCache == null) {
                    mLRUFileCache = new LRUFileCache();
                }
            }
        }

        return mLRUFileCache;
    }

    /**
     * 緩存文件的配置
     */
    public void setFileLoadOptions(FileCacheOptions options) {
        this.options = options;
    }

    /**
     * use default options
     */
    private LRUFileCache() {
        this.options = new FileCacheOptions.Builder()
                .setCacheRootPath("FileCache")
                .setIsUseFileCache(true)
                .setMaxCacheSize(100 * 1024 * 1024L)//100MB
                .setMaxFileCount(100)
                .builder();
    }


    @Override
    public void addDiskFile(String key, InputStream inputStream) {
        if (TextUtils.isEmpty(key) || inputStream == null) {
            return;
        }

        String filename = convertUrlToFileName(key);
        String dir = options.getCacheRootPath();
        File dirFile = new File(dir);
        if (!dirFile.exists())
            dirFile.mkdirs();
        File file = new File(dir + "/" + filename);
        OutputStream outStream;
        try {
            if (file.exists()) {
                file.delete();
            }

            file.createNewFile();
            outStream = new FileOutputStream(file);
            while (inputStream.available() != 0) {
                outStream.write(inputStream.read());
            }
            outStream.flush();
            outStream.close();
            inputStream.close();
        } catch (Throwable e) {
            Log.w("LRUFileCache", e.getMessage());
        }

        // free the space at every time to add a new file
        freeSpaceIfNeeded();
    }

    @Override
    public File getDiskFile(String key) {
        File file = new File(getFilePathByKey(key));

        if (file != null && file.exists()) {
            updateFileTime(file);

        } else {
            file = null;
        }
        return file;
    }

    @Override
    public boolean isExist(String key) {
        if (URLUtil.isNetworkUrl(key)) {
            return new File(options.getCacheRootPath() + "/" + convertUrlToFileName(key)).exists();

        } else if (URLUtil.isFileUrl(key)) {
            return new File(key).exists();

        } else {
            return false;
        }
    }

    @Override
    public void removeDiskFile(String key) {
        File file = getDiskFile(key);
        if (file != null && file.exists()) {
            file.delete();
        }
    }

    @Override
    public void removeAllDiskFiles() {
        new File(options.getCacheRootPath()).delete();
    }


    /**
     * This method will free the files which had not been used at a long time
     */
    public void freeSpaceIfNeeded() {
        File dir = new File(options.getCacheRootPath());
        File[] files = dir.listFiles();
        if (files == null) {
            return;
        }

        long dirSize = 0;
        for (int i = 0; i < files.length; i++) {
            if (files[i].getName().contains(WHOLESALE_CONV)) {
                dirSize += files[i].length();
            }
        }
        // if the dir size larger than max size or the free space on SDCard is less than 100MB
        //free 40% space for system
        if (dirSize > options.getMaxCacheSize()
                || FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
            // delete 40% files by LRU
            int removeFactor = (int) ((0.4 * files.length) + 1);
            // sort the files by modify time
            Arrays.sort(files, new FileLastModifSort());
            // delete files
            for (int i = 0; i < removeFactor; i++) {
                if (files[i].getName().contains(WHOLESALE_CONV)) {
                    files[i].delete();
                }
            }
        }

        //if file count is larger than max count, delete the last
        if (files.length > options.getMaxFileCount()) {
            Arrays.sort(files, new FileLastModifSort());
            // delete files
            for (int i = 0; i < files.length - options.getMaxFileCount(); i++) {
                if (files[i].getName().contains(WHOLESALE_CONV)) {
                    files[i].delete();
                }
            }
        }

    }

    /**
     * Modify the file time
     *
     * @param file the file which need to update time
     */
    public void updateFileTime(File file) {
        if (file != null && file.exists()) {
            long newModifiedTime = System.currentTimeMillis();
            file.setLastModified(newModifiedTime);
        }
    }

    /**
     * get the free space on SDCard
     *
     * @return free size in B
     */

    private long freeSpaceOnSd() {
        return SDCardUtil.getfreeSpace();
    }

    /**
     * Get the file name by file url
     *
     * @param url
     * @return file name
     */
    private String convertUrlToFileName(String url) {
        String[] strs = url.split("/");
        return strs[strs.length - 1] + WHOLESALE_CONV;
    }

    /**
     * Get the file name by key
     *
     * @param key
     * @return file name
     */
    public String getFilePathByKey(String key) {
        if (URLUtil.isFileUrl(key)) {
            return key;

        } else if (URLUtil.isNetworkUrl(key)) {
            return options.getCacheRootPath() + "/" + convertUrlToFileName(key);

        } else {
            return null;
        }
    }

    /**
     * The comparator for the file modify, sort the files by modify time.
     */
    private class FileLastModifSort implements Comparator<File> {
        public int compare(File arg0, File arg1) {
            if (arg0.lastModified() > arg1.lastModified()) {
                return 1;
            } else if (arg0.lastModified() == arg1.lastModified()) {
                return 0;
            } else {
                return -1;
            }
        }
    }
}

  • SDCardUtil 工具類
public class SDCardUtil {
    public static final long SIZE_KB = 1024L;
    public static final long SIZE_MB = 1024L * 1024L;
    public static final long SIZE_GB = 1024L * 1024L * 1024L;

    public interface AlarmListener {
        void onAlarm();
    }
    
    //設(shè)置超出最大值
    public static void setAlarmSize(long currentSize, long maxSize, SDSizeAlarmUtil.AlarmListener listener) {
        if (currentSize > maxSize) {
            if (listener != null) {
                listener.onAlarm();
            }
        }
    }

    //設(shè)置超出最小磁盤值預(yù)警
    public static void setSDAlarm(long minSize, SDSizeAlarmUtil.AlarmListener listener) {
        long freeSize = getFreeSpace();
        if (freeSize < minSize) {
            if (listener != null) {
                listener.onAlarm();
            }
        }
    }


    public static boolean sDCardIsCanable() {
        String sDStateString = Environment.getExternalStorageState();
        if (sDStateString.equals(Environment.MEDIA_MOUNTED)) {
            Log.d("x", "the SDcard mounted");
            return true;
        }
        return false;

    }


    public static long getFreeSpace() {
        StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath());

        long sdFreeMB = stat.getAvailableBlocks() * stat
                .getBlockSize();
        return sdFreeMB;
    }


    public static String getSizeToSting(long size) {

        if (size < SIZE_KB) {
            return size + "B";
        }

        if (size < SIZE_MB) {
            return Math.round(size * 100.0 / SIZE_KB) / 100.0 + "KB";
        }

        if (size < SIZE_GB) {
            return Math.round(size * 100.0 / SIZE_MB) / 100.0 + "MB";
        }

        return Math.round(size * 100.0 / SIZE_GB) / 100.0 + "G";

    }

}

使用

  • 配置緩存設(shè)置

  private void config() {
      distory = getContext().getExternalCacheDir().getPath() + File.separator + "filecache";
      LRUFileCache.getInstance().setFileLoadOptions(new FileCacheOptions.Builder()
              .setMaxFileCount(5)
              .setMaxCacheSize(200 * 1024L)
              .setIsUseFileCache(true)
              .setCacheRootPath(distory)
              .builder());
  }

使用

  • 配置緩存設(shè)置

  private void config() {
      distory = getContext().getExternalCacheDir().getPath() + File.separator + "filecache";
      LRUFileCache.getInstance().setFileLoadOptions(new FileCacheOptions.Builder()
              .setMaxFileCount(5)
              .setMaxCacheSize(200 * 1024L)
              .setIsUseFileCache(true)
              .setCacheRootPath(distory)
              .builder());
  }
  • 獲取文件
 File file =LRUFileCache.getInstance().getDiskFile(url);
 if(file!=null){
    return  file;
 }else{

 //todo  下載
 }
  • API
 // 釋放空間
 /**
 * This method will free the files which had not been used at a long time
 */
 LRUFileCache.getInstance().freeSpaceIfNeeded();
 
 

參考文獻(xiàn)

Android大圖加載優(yōu)化--基于LRU算法的本地文件緩存

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蜓萄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌辟犀,老刑警劉巖绸硕,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異跃捣,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)酣胀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門闻镶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來铆农,“玉大人狡耻,你說我怎么就攤上這事×朐恚” “怎么了沼头?”我有些...
    開封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵进倍,是天一觀的道長。 經(jīng)常有香客問我陶因,道長垂蜗,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮蝇刀,結(jié)果婚禮上徘溢,老公的妹妹穿的比我還像新娘捆探。我一直安慰自己黍图,他們只是感情好奴烙,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開白布切诀。 她就那樣靜靜地躺著,像睡著了一般丰滑。 火紅的嫁衣襯著肌膚如雪倒庵。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天郁妈,我揣著相機(jī)與錄音圃庭,去河邊找鬼失晴。 笑死,一個(gè)胖子當(dāng)著我的面吹牛书在,可吹牛的內(nèi)容都是我干的拆又。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼栈源,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼甚垦!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起闭翩,我...
    開封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤疗韵,失蹤者是張志新(化名)和其女友劉穎侄非,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體彩库,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡骇钦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年眯搭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片寇蚊。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡仗岸,死狀恐怖借笙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情盗痒,我是刑警寧澤低散,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站稽鞭,受9級(jí)特大地震影響引镊,放射性物質(zhì)發(fā)生泄漏吃嘿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一琴拧、第九天 我趴在偏房一處隱蔽的房頂上張望嘱支。 院中可真熱鬧,春花似錦除师、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽痕貌。三九已至,卻和暖如春舵稠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背室琢。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來泰國打工研乒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留淋硝,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓竿报,卻偏偏與公主長得像继谚,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子芽世,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容