需求:
- 管理下载文件、按照LRU算法删除不用的文件
- 如果磁盘空间低于预期,预警提示
- 如果下载文件过大,预警提示
缓存淘汰算法--LRU算法
LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
实现
说明
- FileCacheOptions类 用于配置缓存的信息
- cacheRootPath 缓存路径
- maxFileCount 最大文件数
- 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();
}
//设置超出最大值
public static void setAlarmSize(long currentSize, long maxSize, SDSizeAlarmUtil.AlarmListener listener) {
if (currentSize > maxSize) {
if (listener != null) {
listener.onAlarm();
}
}
}
//设置超出最小磁盘值预警
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";
}
}
使用
- 配置缓存设置
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());
}
使用
- 配置缓存设置
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();