A disk-based implementation of a least-recently used cache for Android compatibility.
npx @tessl/cli install tessl/maven-com-jakewharton--disklrucache@2.0.0A disk-based implementation of a least-recently used (LRU) cache that uses a bounded amount of filesystem space. Each cache entry has a string key and a fixed number of values stored as byte sequences, with automatic eviction of least-recently-used entries when storage limits are exceeded.
pom.xml:
<dependency>
<groupId>com.jakewharton</groupId>
<artifactId>disklrucache</artifactId>
<version>2.0.2</version>
</dependency>import com.jakewharton.disklrucache.DiskLruCache;
import java.io.File;
import java.io.IOException;import com.jakewharton.disklrucache.DiskLruCache;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
// Create or open cache
File cacheDir = new File(context.getCacheDir(), "image_cache");
int appVersion = 1;
int valueCount = 1; // number of values per entry
long maxSize = 10 * 1024 * 1024; // 10MB
DiskLruCache cache = DiskLruCache.open(cacheDir, appVersion, valueCount, maxSize);
// Write to cache
DiskLruCache.Editor editor = cache.edit("key1");
if (editor != null) {
OutputStream out = editor.newOutputStream(0);
// write data to out
out.close();
editor.commit();
}
// Read from cache
DiskLruCache.Snapshot snapshot = cache.get("key1");
if (snapshot != null) {
InputStream in = snapshot.getInputStream(0);
// read data from in
String value = snapshot.getString(0); // or read as string
snapshot.close();
}
// Clean up
cache.close();DiskLruCache uses a journal-based design for persistence and crash recovery:
The cache is designed for single-process use with exclusive directory access and provides strong consistency guarantees through its journaling system.
Create, configure and manage the cache instance lifecycle.
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize) throws IOException;
public synchronized long getMaxSize();
public synchronized void setMaxSize(long maxSize);
public synchronized long size();
public File getDirectory();
public synchronized boolean isClosed();
public synchronized void flush() throws IOException;
public synchronized void close() throws IOException;
public void delete() throws IOException;Retrieve cached entries as snapshots providing access to stored values.
public synchronized Snapshot get(String key) throws IOException;Create and edit cache entries through atomic editor operations.
public Editor edit(String key) throws IOException;
public synchronized boolean remove(String key) throws IOException;Access cached entry values through immutable snapshots.
public final class Snapshot implements Closeable {
public Editor edit() throws IOException;
public InputStream getInputStream(int index);
public String getString(int index) throws IOException;
public long getLength(int index);
public void close();
}Create and modify cache entries through atomic edit sessions.
public final class Editor {
public InputStream newInputStream(int index) throws IOException;
public OutputStream newOutputStream(int index) throws IOException;
public String getString(int index) throws IOException;
public void set(int index, String value) throws IOException;
public void commit() throws IOException;
public void abort() throws IOException;
public void abortUnlessCommitted();
}Cache keys must conform to strict validation rules:
[a-z0-9_-]{1,64}// Valid keys
cache.edit("user_123");
cache.edit("profile-data");
cache.edit("temp_file_001");
// Invalid keys (throw IllegalArgumentException)
cache.edit("User_123"); // uppercase letters
cache.edit("user/profile"); // forward slash
cache.edit("user@domain.com"); // @ symbol
cache.edit(""); // empty stringEach cache entry has a fixed number of values determined at cache creation:
open() call)Integer.MAX_VALUE bytes// Methods that throw IOException
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize) throws IOException;
public synchronized Snapshot get(String key) throws IOException;
public Editor edit(String key) throws IOException;
public synchronized boolean remove(String key) throws IOException;
public synchronized void flush() throws IOException;
public synchronized void close() throws IOException;
public void delete() throws IOException;
// Methods that throw IllegalArgumentException
// Thrown for invalid keys or invalid constructor parametersCommon error scenarios:
IllegalArgumentException for keys not matching [a-z0-9_-]{1,64}IllegalStateException when operating on closed cacheedit() returns null when entry already being editedIOException for filesystem operationsget() returns null for non-existent entries// Setup image cache
File imageDir = new File(context.getCacheDir(), "images");
DiskLruCache imageCache = DiskLruCache.open(imageDir, 1, 1, 50 * 1024 * 1024);
// Cache image
public void cacheImage(String url, byte[] imageData) throws IOException {
String key = urlToKey(url);
DiskLruCache.Editor editor = imageCache.edit(key);
if (editor != null) {
OutputStream out = editor.newOutputStream(0);
out.write(imageData);
out.close();
editor.commit();
}
}
// Retrieve cached image
public byte[] getCachedImage(String url) throws IOException {
String key = urlToKey(url);
DiskLruCache.Snapshot snapshot = imageCache.get(key);
if (snapshot != null) {
InputStream in = snapshot.getInputStream(0);
byte[] data = readFully(in);
snapshot.close();
return data;
}
return null;
}
private String urlToKey(String url) {
return url.replaceAll("[^a-z0-9_-]", "_").toLowerCase();
}// Cache with multiple values per entry (metadata + content)
DiskLruCache dataCache = DiskLruCache.open(cacheDir, 1, 2, maxSize);
// Store data with metadata
public void cacheWithMetadata(String key, String metadata, byte[] content) throws IOException {
DiskLruCache.Editor editor = dataCache.edit(key);
if (editor != null) {
// Store metadata as string in index 0
editor.set(0, metadata);
// Store content as bytes in index 1
OutputStream out = editor.newOutputStream(1);
out.write(content);
out.close();
editor.commit();
}
}
// Retrieve data with metadata
public class CachedData {
public final String metadata;
public final byte[] content;
public CachedData(String metadata, byte[] content) {
this.metadata = metadata;
this.content = content;
}
}
public CachedData getCachedData(String key) throws IOException {
DiskLruCache.Snapshot snapshot = dataCache.get(key);
if (snapshot != null) {
String metadata = snapshot.getString(0);
InputStream contentStream = snapshot.getInputStream(1);
byte[] content = readFully(contentStream);
snapshot.close();
return new CachedData(metadata, content);
}
return null;
}// Robust cache operations with proper error handling
public boolean safeCacheOperation(String key, byte[] data) {
DiskLruCache.Editor editor = null;
try {
editor = cache.edit(key);
if (editor == null) {
return false; // Entry being edited by another thread
}
OutputStream out = editor.newOutputStream(0);
out.write(data);
out.close();
editor.commit();
return true;
} catch (IOException e) {
if (editor != null) {
try {
editor.abort();
} catch (IOException ignored) {
}
}
return false;
}
}
// Proper cache shutdown
public void shutdownCache() {
try {
if (cache != null && !cache.isClosed()) {
cache.flush(); // Ensure pending writes complete
cache.close();
}
} catch (IOException e) {
// Log error but continue shutdown
}
}public static final String JOURNAL_FILE = "journal";
public static final String JOURNAL_FILE_TEMP = "journal.tmp";
public static final String JOURNAL_FILE_BACKUP = "journal.bkp";
public static final String MAGIC = "libcore.io.DiskLruCache";
public static final String VERSION_1 = "1";
public static final long ANY_SEQUENCE_NUMBER = -1L;
public static final Pattern LEGAL_KEY_PATTERN = Pattern.compile("[a-z0-9_-]{1,64}");LEGAL_KEY_PATTERN regex exactly