Glide原始碼閱讀(四)記憶體快取、磁碟快取、跳過快取
一、記憶體快取實現
com.bumptech.glide.util.LruCache<T, Y>中,通過LinkedHashMap做記憶體快取
Engine中,MemoryCache.put(EngineKey, EngineResource);新增到LinkedHashMap中。
EngineKey組成:
public EngineKey(String id, Key signature, int width, int height, ResourceDecoder cacheDecoder, ResourceDecoder decoder, Transformation transformation, ResourceEncoder encoder, ResourceTranscoder transcoder, Encoder sourceEncoder) { this.id = id; this.signature = signature; this.width = width; this.height = height; this.cacheDecoder = cacheDecoder; this.decoder = decoder; this.transformation = transformation; this.encoder = encoder; this.transcoder = transcoder; this.sourceEncoder = sourceEncoder; }
EngineResource組成:
EngineResource(Resource<Z> toWrap, boolean isCacheable) {
if (toWrap == null) {
throw new NullPointerException("Wrapped resource must not be null");
}
resource = toWrap;
this.isCacheable = isCacheable;
}
載入圖片前,在Engine中先從快取取,取不到才會去下載
二、磁碟快取
有個只有一個執行緒的執行緒池,掃描日誌檔案,根據日誌檔案的日誌對快取做處理
//只有一個執行緒的執行緒池,在後臺掃描日誌檔案,根據日誌資訊操作快取 final ThreadPoolExecutor executorService = new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); private final Callable<Void> cleanupCallable = new Callable<Void>() {//清理快取的任務 public Void call() throws Exception { synchronized (DiskLruCache.this) { if (journalWriter == null) { return null; // Closed. } trimToSize(); if (journalRebuildRequired()) { rebuildJournal(); redundantOpCount = 0; } } return null; } };
DiskLruCache 下載圖片成功後,會走磁碟快取 DecodeJob.java中
public Resource<Z> decodeFromSource() throws Exception {
Resource<T> decoded = decodeSource();//解碼後的資源
return transformEncodeAndTranscode(decoded);
}
private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) {
long startTime = LogTime.getLogTime();
Resource<T> transformed = transform(decoded);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Transformed resource from source", startTime);
}
writeTransformedToCache(transformed);//把解碼後的資源寫入快取
startTime = LogTime.getLogTime();
Resource<Z> result = transcode(transformed);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Transcoded transformed from source", startTime);
}
return result;
}
private void writeTransformedToCache(Resource<T> transformed) {
if (transformed == null || !diskCacheStrategy.cacheResult()) {
return;
}
long startTime = LogTime.getLogTime();
//這個writer會把解碼的資源,重新編碼寫入檔案
SourceWriter<Resource<T>> writer = new SourceWriter<Resource<T>>(loadProvider.getEncoder(), transformed);
//這裡先根據resultKey生成一串加密字串,生成一個檔名與key相關的檔案,後呼叫writer.write(),把資源重新編碼寫入檔案
diskCacheProvider.getDiskCache().put(resultKey, writer);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Wrote transformed from source to cache", startTime);
}
}
class SourceWriter<DataType> implements DiskCache.Writer {
...
@Override
public boolean write(File file) {
boolean success = false;
OutputStream os = null;
try {
os = fileOpener.open(file);//獲取輸出流
success = encoder.encode(data, os);//把資料encode成指定格式資源,寫入file中
} catch (FileNotFoundException e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Failed to find file to write to disk cache", e);
}
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
}
}
}
return success;
}
}
三、磁碟快取策略
1)journalFile 日誌檔案,(journalFileTemp只是臨時的,用完就刪了,backupFile是journalFile的備份)
每次快取檔案操作都會寫入日誌,會寫入key和DIRTY/CLEAN/REMOVE等標記
該日誌檔案頭部有一定規則,用於校驗。
讀到DIRTY/REMOVE等標記時,再根據key,從lruEntries中找到對應檔案做處理
讀取日誌檔案過程中,會把key、以及key對應檔案包裝物件entry存入lruEntries中
所以每次快取操作都會先讀該日誌檔案,然後根據標記對磁碟快取檔案做處理
另外,有一個只有一條執行緒的執行緒池,會掃描該日誌檔案,根據日誌操作快取
2)LinkedHashMap<String, Entry> lruEntries 記憶體中快取有磁碟快取檔案資訊,讀快取時,根據key找到對應檔案
3)官方描述
/*
* This cache uses a journal file named "journal". A typical journal file
* looks like this:
* libcore.io.DiskLruCache
* 1
* 100
* 2
*
* CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
* DIRTY 335c4c6028171cfddfbaae1a9c313c52
* CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
* REMOVE 335c4c6028171cfddfbaae1a9c313c52
* DIRTY 1ab96a171faeeee38496d8b330771a7a
* CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
* READ 335c4c6028171cfddfbaae1a9c313c52
* READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
*
* The first five lines of the journal form its header. They are the
* constant string "libcore.io.DiskLruCache", the disk cache's version,
* the application's version, the value count, and a blank line.
*
* Each of the subsequent lines in the file is a record of the state of a
* cache entry. Each line contains space-separated values: a state, a key,
* and optional state-specific values.
* o DIRTY lines track that an entry is actively being created or updated.
* Every successful DIRTY action should be followed by a CLEAN or REMOVE
* action. DIRTY lines without a matching CLEAN or REMOVE indicate that
* temporary files may need to be deleted.
每一行DIRTY的key,後面都應該有一行對應的CLEAN或者REMOVE的記錄,
否則這條資料就是“髒”的,會被自動刪除掉。
* o CLEAN lines track a cache entry that has been successfully published
* and may be read. A publish line is followed by the lengths of each of
* its values.
* o READ lines track accesses for LRU.
* o REMOVE lines track entries that have been deleted.
*
* The journal file is appended to as cache operations occur. The journal may
* occasionally be compacted by dropping redundant lines. A temporary file named
* "journal.tmp" will be used during compaction; that file should be deleted if
* it exists when the cache is opened.
*/
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize) throws IOException {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
if (valueCount <= 0) {
throw new IllegalArgumentException("valueCount <= 0");
}
File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
if (backupFile.exists()) {
File journalFile = new File(directory, JOURNAL_FILE);
if (journalFile.exists()) {//如果存在journalFile則刪除backupFile,否則把backupFile名改為journalFile名
backupFile.delete();
} else {
renameTo(backupFile, journalFile, false);
}
}
DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
if (cache.journalFile.exists()) {
try {
cache.readJournal();//讀取journalFile,校驗檔案,生成快取檔案
cache.processJournal();//刪除快取中儲存的key.0 key.0.temp檔案
return cache;
} catch (IOException journalIsCorrupt) {
System.out.println("DiskLruCache " + directory + " is corrupt: " + journalIsCorrupt.getMessage() + ", removing");
cache.delete();
}
}
// journalFile、backupFile都不存在,則重新建立journalFile
directory.mkdirs();
cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
cache.rebuildJournal();
return cache;
}
//journalFile、backupFile都不存在,則重新建立journalFile
private synchronized void rebuildJournal() throws IOException {
if (journalWriter != null) {
journalWriter.close();
}
Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(journalFileTmp), Util.US_ASCII));
try {//建立的檔案前四行特殊處理
writer.write(MAGIC);
writer.write("\n");
writer.write(VERSION_1);
writer.write("\n");
writer.write(Integer.toString(appVersion));
writer.write("\n");
writer.write(Integer.toString(valueCount));
writer.write("\n");
writer.write("\n");
for (DiskLruCache.Entry entry : lruEntries.values()) {
if (entry.currentEditor != null) {
writer.write(DIRTY + ' ' + entry.key + '\n');//DIRTY:跟蹤正在建立或更新的條目
} else {
writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');//CLEAN:跟蹤已成功釋出的快取條目並可能被讀取,釋出行後面跟著每個的長度值
}
}
} finally {
writer.close();
}
if (journalFile.exists()) {
renameTo(journalFile, journalFileBackup, true);
}
renameTo(journalFileTmp, journalFile, false);//建立好檔案並寫入頭部固定標識後,檔案重新命名為journalFile
journalFileBackup.delete();
journalWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(journalFile, true), Util.US_ASCII));
}
//存在journalFile時,讀取檔案
private void readJournal() throws IOException {
StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII);
try {//先讀取檔案頭部資訊,校驗
String magic = reader.readLine();
String version = reader.readLine();
String appVersionString = reader.readLine();
String valueCountString = reader.readLine();
String blank = reader.readLine();
if (!MAGIC.equals(magic)
|| !VERSION_1.equals(version)
|| !Integer.toString(appVersion).equals(appVersionString)
|| !Integer.toString(valueCount).equals(valueCountString)
|| !"".equals(blank)) {
throw new IOException("unexpected journal header: [" + magic + ", " + version + ", " + valueCountString + ", " + blank + "]");
}
int lineCount = 0;
while (true) {
try {
readJournalLine(reader.readLine());
lineCount++;
} catch (EOFException endOfJournal) {
break;
}
}
redundantOpCount = lineCount - lruEntries.size();
// If we ended on a truncated line, rebuild the journal before appending to it.
if (reader.hasUnterminatedLine()) {
rebuildJournal();
} else {
journalWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(journalFile, true), Util.US_ASCII));
}
} finally {
Util.closeQuietly(reader);
}
}
//校驗通過後,讀取journalFile每一行內容
private void readJournalLine(String line) throws IOException {
int firstSpace = line.indexOf(' ');
if (firstSpace == -1) {
throw new IOException("unexpected journal line: " + line);
}
int keyBegin = firstSpace + 1;
int secondSpace = line.indexOf(' ', keyBegin);
final String key;
if (secondSpace == -1) {
key = line.substring(keyBegin);
if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {//REMOVE:跟蹤已被刪除的條目,刪除該快取
lruEntries.remove(key);
return;
}
} else {
key = line.substring(keyBegin, secondSpace);
}
DiskLruCache.Entry entry = lruEntries.get(key);
if (entry == null) {
/*
這個包含key的entry,在new出來時,會建立一個 key.i 為檔名的快取檔案和一個 key.i.temp 為檔名的臨時檔案(i預設是0)
entry會儲存 這兩個檔案的File物件、key,根據key可以操作這些檔案
*/
entry = new DiskLruCache.Entry(key);
lruEntries.put(key, entry);
}
if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
String[] parts = line.substring(secondSpace + 1).split(" ");
entry.readable = true;
entry.currentEditor = null;
entry.setLengths(parts);
} else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
entry.currentEditor = new DiskLruCache.Editor(entry);
} else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
// This work was already done by calling lruEntries.get().
} else {
throw new IOException("unexpected journal line: " + line);
}
}
//刪除該key對應entry中儲存的兩個檔案 key.i key.i.temp
private void processJournal() throws IOException {
deleteIfExists(journalFileTmp);//刪除臨時檔案
//遍歷快取LruEntries
for (Iterator<DiskLruCache.Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
DiskLruCache.Entry entry = i.next();
if (entry.currentEditor == null) {
for (int t = 0; t < valueCount; t++) {
size += entry.lengths[t];
}
} else {
entry.currentEditor = null;
for (int t = 0; t < valueCount; t++) {
deleteIfExists(entry.getCleanFile(t));
deleteIfExists(entry.getDirtyFile(t));
}
i.remove();
}
}
}
四、跳過快取
有些需求可能要求不使用快取
RequestManager中
public DrawableTypeRequest<byte[]> fromBytes() {
return (DrawableTypeRequest<byte[]>) loadGeneric(byte[].class)
.signature(new StringSignature(UUID.randomUUID().toString()))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true /*skipMemoryCache*/);//這個true最後會傳到Engine中
}
GenericRequestBuilder中
public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> skipMemoryCache(boolean skip) {
this.isCacheable = !skip;//上面的true傳進來後 isCacheable = false;
return this;
}
Engine中這個isMemoryCacheable就是上面的isCacheableprivate EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {//false,則從快取讀取直接返回null,後面會去下載圖片
return null;
}
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
}
return cached;
}
在最外層呼叫的是
Glide.with(context)
.load(imageBytes)//byte[]
....
這種方式不會使用快取
相關推薦
Glide原始碼閱讀(四)記憶體快取、磁碟快取、跳過快取
一、記憶體快取實現com.bumptech.glide.util.LruCache<T, Y>中,通過LinkedHashMap做記憶體快取Engine中,MemoryCache.put(EngineKey, EngineResource);新增到LinkedHa
Glide原始碼分析(一)——DiskLruCache磁碟快取的實現
Glide磁碟的實現主要是通過DiskLruCache來實現的。DiskLruCache並非針對Glide編寫的,而是一個通用的磁碟快取實現,雖然並非Google官方的程式碼,但是已經在很多應用中得到了引入使用。 journal日誌 DiskLruCache
linux調優:按照CPU、記憶體、磁碟IO、網路效能監測
系統優化是一項複雜、繁瑣、長期的工作,優化前需要監測、採集、測試、評估,優化後也需要測試、採集、評估、監測,而且是一個長期和持續的過程,不 是說現在優化了,測試了,以後就可以一勞永逸了,也不是說書本上的優化就適合眼下正在執行的系統,不同的系統、不同的硬體、不同的應用優化的重點也不同、 優化的
Linux下java獲取CPU、記憶體、磁碟IO、網路頻寬使用率
原文地址:https://www.cnblogs.com/gisblogs/p/3985393.html 一、CPU 使用proc檔案系統,"proc檔案系統是一個偽檔案系統,它只存在記憶體當中,而不佔用外存空間。它以檔案系統的方式為訪問系統核心資料的操作提供介面。使用者和應用程式可以通過p
SGISTL原始碼閱讀四 物件的構造與析構
SGISTL原始碼閱讀四 物件的構造與析構 前言 前面我們提到,SGISTL將空間配置和物件的構造分開操作了,前面的文章我們對空間配置已經做了描述,下面我們來看一下如何構造和析構物件。 深入原始碼 construc //接受一個指標和一個初值 template <c
spark原始碼閱讀筆記Dataset(三)structField、structType、schame
StructType(fields: Seq[StructField]) 一個StructType物件,可以有多個StructField,同時也可以用名字(name)來提取,就想當於Map可以用key來提取value,但是他StructType提取的是整條欄位的資訊 在原始碼中structType是一個cas
Lua原始碼閱讀四——lua虛擬機器指令系統
本篇文章,主要探討一下lua中的指令系統(涉及到的檔案 lopcodes.c )。 在lua中,用32位的unsigned int型別來表示一條指令操作碼,32位值包含了6位的操作碼和26位的指令欄位兩部分內容。 All instructions have an opc
kafka原始碼閱讀環境搭建(gradle構建工具、idea)
1.安裝gradle工具,下載地址:https://gradle.org/next-steps/?version=4.7&format=all2.配置環境變數,GRADLE_HOME,path,注意:要在系統變數中配置3.cmd進入dos視窗,gradle -v檢視版
Linux按照CPU、記憶體、磁碟IO、網路效能監測(強烈推薦)
系統優化是一項複雜、繁瑣、長期的工作,優化前需要監測、採集、測試、評估,優化後也需要測試、採集、評估、監測,而且是一個長期和持續的過程,不 是說現在優化了,測試了,以後就可以一勞永逸了,也不是說書本上的優化就適合眼下正在執行的系統,不同的系統、不同的硬體、不同的應用優化的重
Linux下java獲取CPU、記憶體、磁碟IO、網路IO
獲取linux命令執行結果 下面的程式碼用於獲取執行一個Linux命令之後的結果,函式返回一個字串,即命令的執行結果 import java.io.IOException; import java.io.InputStreamReader; i
監控主機記憶體、磁碟使用率、程序、資料庫
最近自己做了一個監控,對公司所有主機、資料庫進行簡單的監控,具體包括主機記憶體剩餘量、磁碟使用率,程序監控等, 分享給大家,希望對大家有用,具體配置如下: 1、相應主機記憶體剩餘多少進行監控,可以定製一個閥值,如果低於這個閥值就報警,如1G,下面紅框內是要監控的值
HashMap原始碼閱讀(原始碼閱讀四)
開發十年,就只剩下這套架構體系了! >>>
磁碟管理(裝置的檢視、掛載與解除安裝、磁碟分割槽、swap分割槽的建立與刪除、磁碟配額)
磁碟管理 概述: 分割槽:磁碟上的分割槽規劃 硬碟:是一種儲存裝置,可劃分分割槽(可見的) 硬碟與系統的關係: 系統管理硬碟 硬碟儲存系統資訊 1.本地儲存裝置的檢視 fdisk 是用於管理磁碟分割槽的實用程式 fdisk -l #檢視
磁碟管理2(磁碟配額、磁碟加密、磁碟的兩種型別:mbr與gpt)
1.磁碟配額 磁碟配額:針對於裝置 dd命令: dd if=/dev/zero of=/mnt/studentfile bs=1M count=21 具體引數的含義: dd #擷取 if
磁軌、柱面、扇區、磁碟簇、尋道時間、旋轉延遲、存取時間
1.磁軌 以碟片中心為圓心,用不同的半徑,劃分出不同的很窄的圓環形區域,稱為磁軌。 2.柱面 上下一串碟片中,相同半徑的磁軌所組成的一個圓柱型的環壁,就稱為柱面。 3.扇區 磁碟上的每個磁軌被等分為若干個弧段,這些弧段便是磁碟的扇區.扇區是磁碟最小的物理儲存單元 4.磁碟簇(windows) windows
Flask第四天-MongoDB簡介、 增刪改)、MongoDB資料型別、MongoDB關鍵字/查詢關鍵字/修改器、PyMongo排序選取跳過、websocket加密
db 檢視當前資料庫 show dbs 查詢所有資料庫(在物理磁碟上的) u
linux 筆記(一)(虛擬機器安裝、磁碟分割槽、linux安裝)
Linux筆記(一) Windows與linux的區別。 1. 嚴格區分大小寫。 2. 所有的內容都是檔案。 3. 不以副檔名區分檔案型別(下面是約定俗成的副檔名)。 a) 壓縮包:
java讀取記憶體中的csv檔案,跳過第一行
package ApacheCommonCSV; import junit.framework.TestCase; import org.apache.commons.csv.CSVFormat; im
Glide 快取策略 記憶體快取和磁碟快取
本文主要介紹瞭如何配置和管理Glide中的快取,其中大部分內容都可以直接在官方Wiki中找到,這裡只是進行了整理和彙總。言歸正傳,Glide支援圖片的二級快取(並不是三級快取,因為從網路載入並不屬於快取),即記憶體快取和磁碟快取。 磁碟快取 一般的圖片快取指的就是磁碟快取
Glide原始碼分析(六),快取架構、存取命中分析
分析Glide快取策略,我們還得從之前分析的Engine#load方法入手,這個方法中,展示了快取讀取的一些策略,我們繼續貼上這塊程式碼。 Engine#load public <R> LoadStatus load( Gli