android 輕量級存儲框架ACache 源碼分析及評估

引言

這篇文章主要介紹 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邏輯可以優化完善;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章