1. 程式人生 > >Glide原始碼閱讀(四)記憶體快取、磁碟快取、跳過快取

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就是上面的isCacheable
private 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(三)structFieldstructTypeschame

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