A disk-based implementation of a least-recently used cache for Android compatibility.
npx @tessl/cli install tessl/maven-com-jakewharton--disklrucache@2.0.00
# DiskLruCache
1
2
A 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.
3
4
## Package Information
5
6
- **Package Name**: com.jakewharton:disklrucache
7
- **Package Type**: maven
8
- **Language**: Java
9
- **Target**: Java 1.5+, Android compatible
10
- **Installation**: Add to `pom.xml`:
11
```xml
12
<dependency>
13
<groupId>com.jakewharton</groupId>
14
<artifactId>disklrucache</artifactId>
15
<version>2.0.2</version>
16
</dependency>
17
```
18
19
## Core Imports
20
21
```java
22
import com.jakewharton.disklrucache.DiskLruCache;
23
import java.io.File;
24
import java.io.IOException;
25
```
26
27
## Basic Usage
28
29
```java
30
import com.jakewharton.disklrucache.DiskLruCache;
31
import java.io.File;
32
import java.io.IOException;
33
import java.io.InputStream;
34
import java.io.OutputStream;
35
36
// Create or open cache
37
File cacheDir = new File(context.getCacheDir(), "image_cache");
38
int appVersion = 1;
39
int valueCount = 1; // number of values per entry
40
long maxSize = 10 * 1024 * 1024; // 10MB
41
DiskLruCache cache = DiskLruCache.open(cacheDir, appVersion, valueCount, maxSize);
42
43
// Write to cache
44
DiskLruCache.Editor editor = cache.edit("key1");
45
if (editor != null) {
46
OutputStream out = editor.newOutputStream(0);
47
// write data to out
48
out.close();
49
editor.commit();
50
}
51
52
// Read from cache
53
DiskLruCache.Snapshot snapshot = cache.get("key1");
54
if (snapshot != null) {
55
InputStream in = snapshot.getInputStream(0);
56
// read data from in
57
String value = snapshot.getString(0); // or read as string
58
snapshot.close();
59
}
60
61
// Clean up
62
cache.close();
63
```
64
65
## Architecture
66
67
DiskLruCache uses a journal-based design for persistence and crash recovery:
68
69
- **Journal File**: Records all cache operations (CLEAN, DIRTY, REMOVE, READ) for crash recovery
70
- **Entry Management**: Uses LinkedHashMap for LRU ordering with automatic eviction
71
- **Atomic Operations**: Edit operations are atomic - entries are either fully committed or aborted
72
- **Background Cleanup**: Automatic size management and journal compaction via background thread
73
- **File Organization**: Each entry stored as separate files in the cache directory
74
75
The cache is designed for single-process use with exclusive directory access and provides strong consistency guarantees through its journaling system.
76
77
## Capabilities
78
79
### Cache Creation and Management
80
81
Create, configure and manage the cache instance lifecycle.
82
83
```java { .api }
84
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize) throws IOException;
85
public synchronized long getMaxSize();
86
public synchronized void setMaxSize(long maxSize);
87
public synchronized long size();
88
public File getDirectory();
89
public synchronized boolean isClosed();
90
public synchronized void flush() throws IOException;
91
public synchronized void close() throws IOException;
92
public void delete() throws IOException;
93
```
94
95
### Reading from Cache
96
97
Retrieve cached entries as snapshots providing access to stored values.
98
99
```java { .api }
100
public synchronized Snapshot get(String key) throws IOException;
101
```
102
103
### Writing to Cache
104
105
Create and edit cache entries through atomic editor operations.
106
107
```java { .api }
108
public Editor edit(String key) throws IOException;
109
public synchronized boolean remove(String key) throws IOException;
110
```
111
112
### Snapshot Operations
113
114
Access cached entry values through immutable snapshots.
115
116
```java { .api }
117
public final class Snapshot implements Closeable {
118
public Editor edit() throws IOException;
119
public InputStream getInputStream(int index);
120
public String getString(int index) throws IOException;
121
public long getLength(int index);
122
public void close();
123
}
124
```
125
126
### Editor Operations
127
128
Create and modify cache entries through atomic edit sessions.
129
130
```java { .api }
131
public final class Editor {
132
public InputStream newInputStream(int index) throws IOException;
133
public OutputStream newOutputStream(int index) throws IOException;
134
public String getString(int index) throws IOException;
135
public void set(int index, String value) throws IOException;
136
public void commit() throws IOException;
137
public void abort() throws IOException;
138
public void abortUnlessCommitted();
139
}
140
```
141
142
## Key and Value Constraints
143
144
### Key Requirements
145
146
Cache keys must conform to strict validation rules:
147
148
- **Pattern**: Must match regex `[a-z0-9_-]{1,64}`
149
- **Length**: 1 to 64 characters
150
- **Characters**: Only lowercase letters, digits, underscore, and hyphen
151
- **Case sensitive**: Keys are case-sensitive
152
153
```java
154
// Valid keys
155
cache.edit("user_123");
156
cache.edit("profile-data");
157
cache.edit("temp_file_001");
158
159
// Invalid keys (throw IllegalArgumentException)
160
cache.edit("User_123"); // uppercase letters
161
cache.edit("user/profile"); // forward slash
162
cache.edit("user@domain.com"); // @ symbol
163
cache.edit(""); // empty string
164
```
165
166
### Value Structure
167
168
Each cache entry has a fixed number of values determined at cache creation:
169
170
- **Value Count**: Fixed per cache (set in `open()` call)
171
- **Size Limits**: Each value 0 to `Integer.MAX_VALUE` bytes
172
- **Storage**: Values stored as byte sequences on disk
173
- **Access**: Values accessible as streams or strings
174
- **Indexing**: Values accessed by zero-based index (0 to valueCount-1)
175
176
## Error Handling and Thread Safety
177
178
### Exception Handling
179
180
```java { .api }
181
// Methods that throw IOException
182
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize) throws IOException;
183
public synchronized Snapshot get(String key) throws IOException;
184
public Editor edit(String key) throws IOException;
185
public synchronized boolean remove(String key) throws IOException;
186
public synchronized void flush() throws IOException;
187
public synchronized void close() throws IOException;
188
public void delete() throws IOException;
189
190
// Methods that throw IllegalArgumentException
191
// Thrown for invalid keys or invalid constructor parameters
192
```
193
194
Common error scenarios:
195
- **Invalid Keys**: `IllegalArgumentException` for keys not matching `[a-z0-9_-]{1,64}`
196
- **Closed Cache**: `IllegalStateException` when operating on closed cache
197
- **Concurrent Edits**: `edit()` returns `null` when entry already being edited
198
- **I/O Errors**: `IOException` for filesystem operations
199
- **Missing Entries**: `get()` returns `null` for non-existent entries
200
201
### Thread Safety
202
203
- **Synchronized Operations**: All cache operations are thread-safe
204
- **Concurrent Reads**: Multiple threads can read simultaneously
205
- **Exclusive Editing**: Only one editor per key at a time
206
- **Background Operations**: Automatic cleanup runs on background thread
207
- **Process Exclusivity**: Cache directory must be exclusive to single process
208
209
## Usage Examples
210
211
### Image Caching
212
213
```java
214
// Setup image cache
215
File imageDir = new File(context.getCacheDir(), "images");
216
DiskLruCache imageCache = DiskLruCache.open(imageDir, 1, 1, 50 * 1024 * 1024);
217
218
// Cache image
219
public void cacheImage(String url, byte[] imageData) throws IOException {
220
String key = urlToKey(url);
221
DiskLruCache.Editor editor = imageCache.edit(key);
222
if (editor != null) {
223
OutputStream out = editor.newOutputStream(0);
224
out.write(imageData);
225
out.close();
226
editor.commit();
227
}
228
}
229
230
// Retrieve cached image
231
public byte[] getCachedImage(String url) throws IOException {
232
String key = urlToKey(url);
233
DiskLruCache.Snapshot snapshot = imageCache.get(key);
234
if (snapshot != null) {
235
InputStream in = snapshot.getInputStream(0);
236
byte[] data = readFully(in);
237
snapshot.close();
238
return data;
239
}
240
return null;
241
}
242
243
private String urlToKey(String url) {
244
return url.replaceAll("[^a-z0-9_-]", "_").toLowerCase();
245
}
246
```
247
248
### Multi-Value Entries
249
250
```java
251
// Cache with multiple values per entry (metadata + content)
252
DiskLruCache dataCache = DiskLruCache.open(cacheDir, 1, 2, maxSize);
253
254
// Store data with metadata
255
public void cacheWithMetadata(String key, String metadata, byte[] content) throws IOException {
256
DiskLruCache.Editor editor = dataCache.edit(key);
257
if (editor != null) {
258
// Store metadata as string in index 0
259
editor.set(0, metadata);
260
261
// Store content as bytes in index 1
262
OutputStream out = editor.newOutputStream(1);
263
out.write(content);
264
out.close();
265
266
editor.commit();
267
}
268
}
269
270
// Retrieve data with metadata
271
public class CachedData {
272
public final String metadata;
273
public final byte[] content;
274
275
public CachedData(String metadata, byte[] content) {
276
this.metadata = metadata;
277
this.content = content;
278
}
279
}
280
281
public CachedData getCachedData(String key) throws IOException {
282
DiskLruCache.Snapshot snapshot = dataCache.get(key);
283
if (snapshot != null) {
284
String metadata = snapshot.getString(0);
285
InputStream contentStream = snapshot.getInputStream(1);
286
byte[] content = readFully(contentStream);
287
snapshot.close();
288
return new CachedData(metadata, content);
289
}
290
return null;
291
}
292
```
293
294
### Error Recovery and Cleanup
295
296
```java
297
// Robust cache operations with proper error handling
298
public boolean safeCacheOperation(String key, byte[] data) {
299
DiskLruCache.Editor editor = null;
300
try {
301
editor = cache.edit(key);
302
if (editor == null) {
303
return false; // Entry being edited by another thread
304
}
305
306
OutputStream out = editor.newOutputStream(0);
307
out.write(data);
308
out.close();
309
editor.commit();
310
return true;
311
312
} catch (IOException e) {
313
if (editor != null) {
314
try {
315
editor.abort();
316
} catch (IOException ignored) {
317
}
318
}
319
return false;
320
}
321
}
322
323
// Proper cache shutdown
324
public void shutdownCache() {
325
try {
326
if (cache != null && !cache.isClosed()) {
327
cache.flush(); // Ensure pending writes complete
328
cache.close();
329
}
330
} catch (IOException e) {
331
// Log error but continue shutdown
332
}
333
}
334
```
335
336
## Constants and Validation
337
338
### Public Constants
339
340
```java { .api }
341
public static final String JOURNAL_FILE = "journal";
342
public static final String JOURNAL_FILE_TEMP = "journal.tmp";
343
public static final String JOURNAL_FILE_BACKUP = "journal.bkp";
344
public static final String MAGIC = "libcore.io.DiskLruCache";
345
public static final String VERSION_1 = "1";
346
public static final long ANY_SEQUENCE_NUMBER = -1L;
347
public static final Pattern LEGAL_KEY_PATTERN = Pattern.compile("[a-z0-9_-]{1,64}");
348
```
349
350
### Validation Rules
351
352
- **Directory**: Must be writable and exclusive to the cache
353
- **App Version**: Used for cache invalidation when app updates
354
- **Value Count**: Must be positive, fixed for cache lifetime
355
- **Max Size**: Must be positive, can be changed after creation
356
- **Keys**: Must match `LEGAL_KEY_PATTERN` regex exactly