引言
這篇文章主要介紹 android存儲文件的輕量級緩存框架 ACache;
原鏈接地址
庫很小,小到只有一個文件,看到源碼設計的很精美,決定寫篇博客記錄一下;
ACache
代碼很簡潔,只摘要幾個亮點以饗文章:
支持多進程下的存儲
//設置最大存儲空間和最大存儲數量;
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;
}
private static String myPid() {
return "_" + android.os.Process.myPid();
}
存 - 所有被存儲的對象都通過 流FileOutputStream 存於本地
key: string的hashcode;
value: 所有的存儲對象最後都轉化爲File 存於緩存目錄;
//存於本地路徑,同時也是內存緩存中的key,代表文件系統中的路徑;
private File newFile(String key) {
return new File(cacheDir, key.hashCode() + "");
}
//string 存儲通過hashcode在存儲目錄中生成一個文件存儲;成功後在內存中存儲filekey和當前時間long;
public void put(String key, String value) {
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.put(file);
}
}
//cachemanager 的put方法
private void put(File file) {
//線程安全Integer;
int curCacheCount = cacheCount.get();
while (curCacheCount + 1 > countLimit) {
long freedSize = removeNext();
cacheSize.addAndGet(-freedSize);
curCacheCount = cacheCount.addAndGet(-1);
}
cacheCount.addAndGet(1);
long valueSize = calculateSize(file);
long curCacheSize = cacheSize.get();
//todo 若文件超大,這邊就陷入死循環了,可以優化的地方,不過一般不會;
while (curCacheSize + valueSize > sizeLimit) {
long freedSize = removeNext();
curCacheSize = cacheSize.addAndGet(-freedSize);
//todo 此處可能remove多次,cacheCount應該也需要一起變化;
}
cacheSize.addAndGet(valueSize);
Long currentTime = System.currentTimeMillis();
file.setLastModified(currentTime);
//線程安全的map;
lastUsageDates.put(file, currentTime);
}
存 - 帶有過期時間的存儲
//保存時間單位 s;
public void put(String key, String value, int saveTime) {
put(key, Utils.newStringWithDateInfo(saveTime, value));
}
//使用value 和 過期時間重新生成一個可解析時間的最終存儲字符串;
private static String newStringWithDateInfo(int second, String strInfo) {
return createDateInfo(second) + strInfo;
}
//當前時間(不足13位前補0)+ - + 過期時間 + 分隔位(空格)
private static String createDateInfo(int second) {
String currentTime = System.currentTimeMillis() + "";
while (currentTime.length() < 13) {
currentTime = "0" + currentTime;
}
return currentTime + "-" + second + mSeparator;
}
//如果是存儲的是數組(轉化而來)
//將時間標誌信息和數據信息合併成一個數組,寫入文件;
private static byte[] newByteArrayWithDateInfo(int second, byte[] data2) {
byte[] data1 = createDateInfo(second).getBytes();
byte[] retdata = new byte[data1.length + data2.length];
System.arraycopy(data1, 0, retdata, 0, data1.length);
System.arraycopy(data2, 0, retdata, data1.length, data2.length);
return retdata;
}
取 - 通過key匹配存於本地的File對象,帶有過期時間的取數據判斷;
//先通過hashcode匹配本地文件路徑,從流中讀取數據,如果數據存在時間位; 過期 return null,且調用remove方法; 沒有過期則從數據中取出分隔位後的數據;
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));
StringBuilder readStringBuilder = new StringBuilder();
String currentLine;
while ((currentLine = in.readLine()) != null) {
readStringBuilder.append(currentLine);
}
if (!Utils.isDue(readStringBuilder.toString())) {
return Utils.clearDateInfo(readStringBuilder.toString());
} 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);
}
}
//cachemanager的remove方法,向緩存中加入一個對象,刪除本地文件;
private boolean remove(String key) {
File image = get(key);
return image.delete();
}
//cachemanager的get方法;
private File get(String key) {
File file = newFile(key);
Long currentTime = System.currentTimeMillis();
file.setLastModified(currentTime);
lastUsageDates.put(file, currentTime);
return file;
}
//判斷時間是否過期工具類解析;
//先使用從流中取出的存儲數據,解析出時間數據,判斷是否過期;
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;
}
//hasDateInfo 長度大於15,第13位是-,分隔位大於14位;
//有時間數據,將前13位作爲存儲時間數據,14位到分隔位作爲過期時間數據;
private static String[] getDateInfoFromDate(byte[] data) {
if (hasDateInfo(data)) {
String saveDate = new String(copyOfRange(data, 0, 13));
String deleteAfter = new String(copyOfRange(data, 14, indexOf(data, mSeparator)));
return new String[]{saveDate, deleteAfter};
}
return null;
}
//如果直接取字節數據;
public byte[] getAsBinary(String key) {
RandomAccessFile RAFile = null;
boolean removeFile = false;
try {
File file = mCache.get(key);
if (!file.exists())
return null;
RAFile = new RandomAccessFile(file, "r");
byte[] byteArray = new byte[(int) RAFile.length()];
RAFile.read(byteArray);
if (!Utils.isDue(byteArray)) {
//判斷有時間數據,則copyOfRange數據數組;
return Utils.clearDateInfo(byteArray);
} else {
removeFile = true;
return null;
}
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
if (RAFile != null) {
try {
RAFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (removeFile)
remove(key);
}
}
ACacheManager 設計
ACache的關鍵類;
//可配置屬性
//原子類型 緩存的大小(文件);
private final AtomicLong cacheSize;
//原子類型 緩存的數量
private final AtomicInteger cacheCount;
//大小限制;
private final long sizeLimit;
//數量限制;
private final int countLimit;
//引用map; 初始化時將目錄中原文件添加到內存map中;
private final Map<File, Long> lastUsageDates = Collections.synchronizedMap(new HashMap<File, Long>());
//緩存路徑;
protected File cacheDir;
LRU(lastly recent use)算法實現的存儲機制
//當檢驗到大小或數量大於設定值,則進行移出操作,返回刪除的大小;
private long removeNext() {
if (lastUsageDates.isEmpty()) {
return 0;
}
Long oldestUsage = null;
File mostLongUsedFile = null;
Set<Entry<File, Long>> entries = lastUsageDates.entrySet();
//優先移出時間最早的數據;
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 != null && mostLongUsedFile.delete()) {
lastUsageDates.remove(mostLongUsedFile);
}
return fileSize;
}
優缺點分析
優: 簡單高效,只有一個文件; 設計精美;
缺: 只用於小文件一般性存儲; put邏輯可以優化完善;