- /*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package libcore.io;
- import java.io.BufferedInputStream;
- import java.io.BufferedWriter;
- import java.io.Closeable;
- import java.io.EOFException;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
- import java.io.FileOutputStream;
- import java.io.FileWriter;
- import java.io.FilterOutputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.InputStreamReader;
- import java.io.OutputStream;
- import java.io.OutputStreamWriter;
- import java.io.Writer;
- import java.nio.charset.Charsets;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Iterator;
- import java.util.LinkedHashMap;
- import java.util.Map;
- import java.util.concurrent.Callable;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.LinkedBlockingQueue;
- import java.util.concurrent.ThreadPoolExecutor;
- import java.util.concurrent.TimeUnit;
- /**
- * A cache that uses a bounded amount of space on a filesystem. Each cache
- * entry has a string key and a fixed number of values. Values are byte
- * sequences, accessible as streams or files. Each value must be between {@code
- * 0} and {@code Integer.MAX_VALUE} bytes in length.
- *
- * <p>The cache stores its data in a directory on the filesystem. This
- * directory must be exclusive to the cache; the cache may delete or overwrite
- * files from its directory. It is an error for multiple processes to use the
- * same cache directory at the same time.
- *
- * <p>This cache limits the number of bytes that it will store on the
- * filesystem. When the number of stored bytes exceeds the limit, the cache will
- * remove entries in the background until the limit is satisfied. The limit is
- * not strict: the cache may temporarily exceed it while waiting for files to be
- * deleted. The limit does not include filesystem overhead or the cache
- * journal so space-sensitive applications should set a conservative limit.
- *
- * <p>Clients call {@link #edit} to create or update the values of an entry. An
- * entry may have only one editor at one time; if a value is not available to be
- * edited then {@link #edit} will return null.
- * <ul>
- * <li>When an entry is being <strong>created</strong> it is necessary to
- * supply a full set of values; the empty value should be used as a
- * placeholder if necessary.
- * <li>When an entry is being <strong>edited</strong>, it is not necessary
- * to supply data for every value; values default to their previous
- * value.
- * </ul>
- * Every {@link #edit} call must be matched by a call to {@link Editor#commit}
- * or {@link Editor#abort}. Committing is atomic: a read observes the full set
- * of values as they were before or after the commit, but never a mix of values.
- *
- * <p>Clients call {@link #get} to read a snapshot of an entry. The read will
- * observe the value at the time that {@link #get} was called. Updates and
- * removals after the call do not impact ongoing reads.
- *
- * <p>This class is tolerant of some I/O errors. If files are missing from the
- * filesystem, the corresponding entries will be dropped from the cache. If
- * an error occurs while writing a cache value, the edit will fail silently.
- * Callers should handle other problems by catching {@code IOException} and
- * responding appropriately.
- */
- public
final
class
DiskLruCache
implements
Closeable
{
-
static
final
String JOURNAL_FILE
=
"journal";
-
static
final
String JOURNAL_FILE_TMP
=
"journal.tmp";
-
static
final
String MAGIC
=
"libcore.io.DiskLruCache";
-
static
final
String VERSION_1
=
"1";
-
static
final
long ANY_SEQUENCE_NUMBER
=
-1;
-
private
static
final
String CLEAN
=
"CLEAN";
-
private
static
final
String DIRTY
=
"DIRTY";
-
private
static
final
String REMOVE
=
"REMOVE";
-
private
static
final
String READ
=
"READ";
-
/*
- * 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.
- * 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.
- */
-
private
final
File directory;
-
private
final
File journalFile;
-
private
final
File journalFileTmp;
-
private
final
int appVersion;
-
private
final
long maxSize;
-
private
final
int valueCount;
-
private
long size
=
0;
-
private
Writer journalWriter;
-
private
final
LinkedHashMap<String,
Entry> lruEntries
-
=
new
LinkedHashMap<String,
Entry>(0,
0.75f,
true);
-
private
int redundantOpCount;
-
/**
- * To differentiate between old and current snapshots, each entry is given
- * a sequence number each time an edit is committed. A snapshot is stale if
- * its sequence number is not equal to its entry's sequence number.
- */
-
private
long nextSequenceNumber
=
0;
-
/** This cache uses a single background thread to evict entries. */
-
private
final
ExecutorService executorService
=
new
ThreadPoolExecutor(0,
1,
-
60L,
TimeUnit.SECONDS,
new
LinkedBlockingQueue<Runnable>());
-
private
final
Callable<Void>
cleanupCallable =
new
Callable<Void>()
{
-
@Override
public
Void call()
throws
Exception
{
-
synchronized
(DiskLruCache.this)
{
-
if
(journalWriter
==
null)
{
-
return
null;
// closed
-
}
- trimToSize();
-
if
(journalRebuildRequired())
{
- rebuildJournal();
- redundantOpCount
=
0;
-
}
-
}
-
return
null;
-
}
-
};
-
private
DiskLruCache(File directory,
int appVersion,
int valueCount,
long maxSize)
{
-
this.directory
= directory;
-
this.appVersion
= appVersion;
-
this.journalFile
=
new
File(directory,
JOURNAL_FILE);
-
this.journalFileTmp
=
new
File(directory,
JOURNAL_FILE_TMP);
-
this.valueCount
= valueCount;
-
this.maxSize
= maxSize;
-
}
-
/**
- * Opens the cache in {@code directory}, creating a cache if none exists
- * there.
- *
- * @param directory a writable directory
- * @param appVersion
- * @param valueCount the number of values per cache entry. Must be positive.
- * @param maxSize the maximum number of bytes this cache should use to store
- * @throws IOException if reading or writing the cache directory fails
- */
-
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");
-
}
-
// prefer to pick up where we left off
-
DiskLruCache cache
=
new
DiskLruCache(directory,
appVersion, valueCount, maxSize);
-
if
(cache.journalFile.exists())
{
-
try
{
- cache.readJournal();
- cache.processJournal();
- cache.journalWriter
=
new
BufferedWriter(new
FileWriter(cache.journalFile,
true));
-
return cache;
-
}
catch
(IOException journalIsCorrupt)
{
-
System.logW("DiskLruCache
" + directory
+
" is corrupt: "
-
+ journalIsCorrupt.getMessage()
+
", removing");
- cache.delete();
-
}
-
}
-
// create a new empty cache
- directory.mkdirs();
- cache
=
new
DiskLruCache(directory,
appVersion, valueCount, maxSize);
- cache.rebuildJournal();
-
return cache;
-
}
-
private
void readJournal()
throws
IOException
{
-
InputStream in
=
new
BufferedInputStream(new
FileInputStream(journalFile));
-
try
{
-
String magic
=
Streams.readAsciiLine(in);
-
String version
=
Streams.readAsciiLine(in);
-
String appVersionString
=
Streams.readAsciiLine(in);
-
String valueCountString
=
Streams.readAsciiLine(in);
-
String blank
=
Streams.readAsciiLine(in);
-
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
+
"]");
-
}
-
while
(true)
{
-
try
{
- readJournalLine(Streams.readAsciiLine(in));
-
}
catch
(EOFException endOfJournal)
{
-
break;
-
}
-
}
-
}
finally
{
-
IoUtils.closeQuietly(in);
-
}
-
}
-
private
void readJournalLine(String
line)
throws
IOException
{
-
String[] parts
= line.split("
");
-
if
(parts.length
<
2)
{
-
throw
new
IOException("unexpected journal line: "
+ line);
-
}
-
String key
= parts[1];
-
if
(parts[0].equals(REMOVE)
&& parts.length
==
2)
{
- lruEntries.remove(key);
-
return;
-
}
-
Entry entry
= lruEntries.get(key);
-
if
(entry
==
null)
{
- entry
=
new
Entry(key);
- lruEntries.put(key,
entry);
-
}
-
if
(parts[0].equals(CLEAN)
&& parts.length
==
2
+ valueCount)
{
- entry.readable
=
true;
- entry.currentEditor
=
null;
- entry.setLengths(Arrays.copyOfRange(parts,
2, parts.length));
-
}
else
if
(parts[0].equals(DIRTY)
&& parts.length
==
2)
{
- entry.currentEditor
=
new
Editor(entry);
-
}
else
if
(parts[0].equals(READ)
&& parts.length
==
2)
{
-
// this work was already done by calling lruEntries.get()
-
}
else
{
-
throw
new
IOException("unexpected journal line: "
+ line);
-
}
-
}
-
/**
- * Computes the initial size and collects garbage as a part of opening the
- * cache. Dirty entries are assumed to be inconsistent and will be deleted.
- */
-
private
void processJournal()
throws
IOException
{
- deleteIfExists(journalFileTmp);
-
for
(Iterator<Entry>
i = lruEntries.values().iterator();
i.hasNext();
)
{
-
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();
-
}
-
}
-
}
-
/**
- * Creates a new journal that omits redundant information. This replaces the
- * current journal if it exists.
- */
-
private
synchronized
void rebuildJournal()
throws
IOException
{
-
if
(journalWriter
!=
null)
{
- journalWriter.close();
-
}
-
Writer writer
=
new
BufferedWriter(new
FileWriter(journalFileTmp));
- 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
(Entry entry
: lruEntries.values())
{
-
if
(entry.currentEditor
!=
null)
{
- writer.write(DIRTY
+
' '
+ entry.key
+
'\n');
-
}
else
{
- writer.write(CLEAN
+
' '
+ entry.key
+ entry.getLengths()
+
'\n');
-
}
-
}
- writer.close();
- journalFileTmp.renameTo(journalFile);
- journalWriter
=
new
BufferedWriter(new
FileWriter(journalFile,
true));
-
}
-
private
static
void deleteIfExists(File
file)
throws
IOException
{
-
try
{
-
Libcore.os.remove(file.getPath());
-
}
catch
(ErrnoException errnoException)
{
-
if
(errnoException.errno
!=
OsConstants.ENOENT)
{
-
throw errnoException.rethrowAsIOException();
-
}
-
}
-
}
-
/**
- * Returns a snapshot of the entry named {@code key}, or null if it doesn't
- * exist is not currently readable. If a value is returned, it is moved to
- * the head of the LRU queue.
- */
-
public
synchronized
Snapshot get(String
key)
throws
IOException
{
- checkNotClosed();
- validateKey(key);
-
Entry entry
= lruEntries.get(key);
-
if
(entry
==
null)
{
-
return
null;
-
}
-
if
(!entry.readable)
{
-
return
null;
-
}
-
/*
- * Open all streams eagerly to guarantee that we see a single published
- * snapshot. If we opened streams lazily then the streams could come
- * from different edits.
- */
-
InputStream[] ins
=
new
InputStream[valueCount];
-
try
{
-
for
(int i
=
0; i
< valueCount; i++)
{
- ins[i]
=
new
FileInputStream(entry.getCleanFile(i));
-
}
-
}
catch
(FileNotFoundException e)
{
-
// a file must have been deleted manually!
-
return
null;
-
}
- redundantOpCount++;
- journalWriter.append(READ
+
' '
+ key
+
'\n');
-
if
(journalRebuildRequired())
{
- executorService.submit(cleanupCallable);
-
}
-
return
new
Snapshot(key,
entry.sequenceNumber, ins);
-
}
-
/**
- * Returns an editor for the entry named {@code key}, or null if another
- * edit is in progress.
- */
-
public
Editor edit(String
key)
throws
IOException
{
-
return edit(key,
ANY_SEQUENCE_NUMBER);
-
}
-
private
synchronized
Editor edit(String
key,
long expectedSequenceNumber)
throws
IOException
{
- checkNotClosed();
- validateKey(key);
-
Entry entry
= lruEntries.get(key);
-
if
(expectedSequenceNumber
!= ANY_SEQUENCE_NUMBER
-
&&
(entry
==
null
|| entry.sequenceNumber
!= expectedSequenceNumber))
{
-
return
null;
// snapshot is stale
-
}
-
if
(entry
==
null)
{
- entry
=
new
Entry(key);
- lruEntries.put(key,
entry);
-
}
else
if
(entry.currentEditor
!=
null)
{
-
return
null;
// another edit is in progress
-
}
-
Editor editor
=
new
Editor(entry);
- entry.currentEditor
= editor;
-
// flush the journal before creating files to prevent file leaks
- journalWriter.write(DIRTY
+
' '
+ key
+
'\n');
- journalWriter.flush();
-
return editor;
-
}
-
/**
- * Returns the directory where this cache stores its data.
- */
-
public
File getDirectory()
{
-
return directory;
-
}
-
/**
- * Returns the maximum number of bytes that this cache should use to store
- * its data.
- */
-
public
long maxSize()
{
-
return maxSize;
-
}
-
/**
- * Returns the number of bytes currently being used to store the values in
- * this cache. This may be greater than the max size if a background
- * deletion is pending.
- */
-
public
synchronized
long size()
{
-
return size;
-
}
-
private
synchronized
void completeEdit(Editor
editor,
boolean success)
throws
IOException
{
-
Entry entry
= editor.entry;
-
if
(entry.currentEditor
!= editor)
{
-
throw
new
IllegalStateException();
-
}
-
// if this edit is creating the entry for the first time, every index must have a value
-
if
(success
&&
!entry.readable)
{
-
for
(int i
=
0; i
< valueCount; i++)
{
-
if
(!entry.getDirtyFile(i).exists())
{
- editor.abort();
-
throw
new
IllegalStateException("edit didn't create file "
+ i);
-
}
-
}
-
}
-
for
(int i
=
0; i
< valueCount; i++)
{
-
File dirty
= entry.getDirtyFile(i);
-
if
(success)
{
-
if
(dirty.exists())
{
-
File clean
= entry.getCleanFile(i);
- dirty.renameTo(clean);
-
long oldLength
= entry.lengths[i];
-
long newLength
= clean.length();
- entry.lengths[i]
= newLength;
- size
= size
- oldLength
+ newLength;
-
}
-
}
else
{
- deleteIfExists(dirty);
-
}
-
}
- redundantOpCount++;
- entry.currentEditor
=
null;
-
if
(entry.readable
| success)
{
- entry.readable
=
true;
- journalWriter.write(CLEAN
+
' '
+ entry.key
+ entry.getLengths()
+
'\n');
-
if
(success)
{
- entry.sequenceNumber
= nextSequenceNumber++;
-
}
-
}
else
{
- lruEntries.remove(entry.key);
- journalWriter.write(REMOVE
+
' '
+ entry.key
+
'\n');
-
}
-
if
(size
> maxSize
|| journalRebuildRequired())
{
- executorService.submit(cleanupCallable);
-
}
-
}
-
/**
- * We only rebuild the journal when it will halve the size of the journal
- * and eliminate at least 2000 ops.
- */
-
private
boolean journalRebuildRequired()
{
-
final
int REDUNDANT_OP_COMPACT_THRESHOLD
=
2000;
-
return redundantOpCount
>= REDUNDANT_OP_COMPACT_THRESHOLD
-
&& redundantOpCount
>= lruEntries.size();
-
}
-
/**
- * Drops the entry for {@code key} if it exists and can be removed. Entries
- * actively being edited cannot be removed.
- *
- * @return true if an entry was removed.
- */
-
public
synchronized
boolean remove(String
key)
throws
IOException
{
- checkNotClosed();
- validateKey(key);
-
Entry entry
= lruEntries.get(key);
-
if
(entry
==
null
|| entry.currentEditor
!=
null)
{
-
return
false;
-
}
-
for
(int i
=
0; i
< valueCount; i++)
{
-
File file
= entry.getCleanFile(i);
-
if
(!file.delete())
{
-
throw
new
IOException("failed to delete "
+ file);
-
}
- size
-= entry.lengths[i];
- entry.lengths[i]
=
0;
-
}
- redundantOpCount++;
- journalWriter.append(REMOVE
+
' '
+ key
+
'\n');
- lruEntries.remove(key);
-
if
(journalRebuildRequired())
{
- executorService.submit(cleanupCallable);
-
}
-
return
true;
-
}
-
/**
- * Returns true if this cache has been closed.
- */
-
public
boolean isClosed()
{
-
return journalWriter
==
null;
-
}
-
private
void checkNotClosed()
{
-
if
(journalWriter
==
null)
{
-
throw
new
IllegalStateException("cache is closed");
-
}
-
}
-
/**
- * Force buffered operations to the filesystem.
- */
-
public
synchronized
void flush()
throws
IOException
{
- checkNotClosed();
- trimToSize();
- journalWriter.flush();
-
}
-
/**
- * Closes this cache. Stored values will remain on the filesystem.
- */
-
public
synchronized
void close()
throws
IOException
{
-
if
(journalWriter
==
null)
{
-
return;
// already closed
-
}
-
for
(Entry entry
:
new
ArrayList<Entry>(lruEntries.values()))
{
-
if
(entry.currentEditor
!=
null)
{
- entry.currentEditor.abort();
-
}
-
}
- trimToSize();
- journalWriter.close();
- journalWriter
=
null;
-
}
-
private
void trimToSize()
throws
IOException
{
-
while
(size
> maxSize)
{
-
Map.Entry<String,
Entry> toEvict
= lruEntries.eldest();
- remove(toEvict.getKey());
-
}
-
}
-
/**
- * Closes the cache and deletes all of its stored values. This will delete
- * all files in the cache directory including files that weren't created by
- * the cache.
- */
-
public
void
delete()
throws
IOException
{
- close();
-
IoUtils.deleteContents(directory);
-
}
-
private
void validateKey(String
key)
{
-
if
(key.contains("
")
|| key.contains("\n")
|| key.contains("\r"))
{
-
throw
new
IllegalArgumentException(
-
"keys must not contain spaces or newlines: \""
+ key
+
"\"");
-
}
-
}
-
private
static
String inputStreamToString(InputStream
in)
throws
IOException
{
-
return
Streams.readFully(new
InputStreamReader(in,
Charsets.UTF_8));
-
}
-
/**
- * A snapshot of the values for an entry.
- */
-
public
final
class
Snapshot
implements
Closeable
{
-
private
final
String key;
-
private
final
long sequenceNumber;
-
private
final
InputStream[] ins;
-
private
Snapshot(String key,
long sequenceNumber,
InputStream[] ins)
{
-
this.key
= key;
-
this.sequenceNumber
= sequenceNumber;
-
this.ins
= ins;
-
}
-
/**
- * Returns an editor for this snapshot's entry, or null if either the
- * entry has changed since this snapshot was created or if another edit
- * is in progress.
- */
-
public
Editor edit()
throws
IOException
{
-
return
DiskLruCache.this.edit(key,
sequenceNumber);
-
}
-
/**
- * Returns the unbuffered stream with the value for {@code index}.
- */
-
public
InputStream getInputStream(int
index)
{
-
return ins[index];
-
}
-
/**
- * Returns the string value for {@code index}.
- */
-
public
String getString(int
index)
throws
IOException
{
-
return inputStreamToString(getInputStream(index));
-
}
-
@Override
public
void close()
{
-
for
(InputStream in
: ins)
{
-
IoUtils.closeQuietly(in);
-
}
-
}
-
}
-
/**
- * Edits the values for an entry.
- */
-
public
final
class
Editor
{
-
private
final
Entry entry;
-
private
boolean hasErrors;
-
private
Editor(Entry entry)
{
-
this.entry
= entry;
-
}
-
/**
- * Returns an unbuffered input stream to read the last committed value,
- * or null if no value has been committed.
- */
-
public
InputStream newInputStream(int
index)
throws
IOException
{
-
synchronized
(DiskLruCache.this)
{
-
if
(entry.currentEditor
!=
this)
{
-
throw
new
IllegalStateException();
-
}
-
if
(!entry.readable)
{
-
return
null;
-
}
-
return
new
FileInputStream(entry.getCleanFile(index));
-
}
-
}
-
/**
- * Returns the last committed value as a string, or null if no value
- * has been committed.
- */
-
public
String getString(int
index)
throws
IOException
{
-
InputStream in
= newInputStream(index);
-
return in
!=
null
? inputStreamToString(in)
:
null;
-
}
-
/**
- * Returns a new unbuffered output stream to write the value at
- * {@code index}. If the underlying output stream encounters errors
- * when writing to the filesystem, this edit will be aborted when
- * {@link #commit} is called. The returned output stream does not throw
- * IOExceptions.
- */
-
public
OutputStream newOutputStream(int
index)
throws
IOException
{
-
synchronized
(DiskLruCache.this)
{
-
if
(entry.currentEditor
!=
this)
{
-
throw
new
IllegalStateException();
-
}
-
return
new
FaultHidingOutputStream(new
FileOutputStream(entry.getDirtyFile(index)));
-
}
-
}
-
/**
- * Sets the value at {@code index} to {@code value}.
- */
-
public
void set(int
index,
String value)
throws
IOException
{
-
Writer writer
=
null;
-
try
{
- writer
=
new
OutputStreamWriter(newOutputStream(index),
Charsets.UTF_8);
- writer.write(value);
-
}
finally
{
-
IoUtils.closeQuietly(writer);
-
}
-
}
-
/**
- * Commits this edit so it is visible to readers. This releases the
- * edit lock so another edit may be started on the same key.
- */
-
public
void commit()
throws
IOException
{
-
if
(hasErrors)
{
- completeEdit(this,
false);
- remove(entry.key);
// the previous entry is stale
-
}
else
{
- completeEdit(this,
true);
-
}
-
}
-
/**
- * Aborts this edit. This releases the edit lock so another edit may be
- * started on the same key.
- */
-
public
void abort()
throws
IOException
{
- completeEdit(this,
false);
-
}
-
private
class
FaultHidingOutputStream
extends
FilterOutputStream
{
-
private
FaultHidingOutputStream(OutputStream out)
{
-
super(out);
-
}
-
@Override
public
void write(int
oneByte)
{
-
try
{
- out.write(oneByte);
-
}
catch
(IOException e)
{
- hasErrors
=
true;
-
}
-
}
-
@Override
public
void write(byte[]
buffer,
int offset,
int length)
{
-
try
{
- out.write(buffer,
offset, length);
-
}
catch
(IOException e)
{
- hasErrors
=
true;
-
}
-
}
-
@Override
public
void close()
{
-
try
{
- out.close();
-
}
catch
(IOException e)
{
- hasErrors
=
true;
-
}
-
}
-
@Override
public
void flush()
{
-
try
{
- out.flush();
-
}
catch
(IOException e)
{
- hasErrors
=
true;
-
}
-
}
-
}
-
}
-
private
final
class
Entry
{
-
private
final
String key;
-
/** Lengths of this entry's files. */
-
private
final
long[] lengths;
-
/** True if this entry has ever been published */
-
private
boolean readable;
-
/** The ongoing edit or null if this entry is not being edited. */
-
private
Editor currentEditor;
-
/** The sequence number of the most recently committed edit to this entry. */
-
private
long sequenceNumber;
-
private
Entry(String key)
{
-
this.key
= key;
-
this.lengths
=
new
long[valueCount];
-
}
-
public
String getLengths()
throws
IOException
{
-
StringBuilder result
=
new
StringBuilder();
-
for
(long size
: lengths)
{
- result.append('
').append(size);
-
}
-
return result.toString();
-
}
-
/**
- * Set lengths using decimal numbers like "10123".
- */
-
private
void setLengths(String[]
strings)
throws
IOException
{
-
if
(strings.length
!= valueCount)
{
-
throw invalidLengths(strings);
-
}
-
try
{
-
for
(int i
=
0; i
< strings.length;
i++)
{
- lengths[i]
=
Long.parseLong(strings[i]);
-
}
-
}
catch
(NumberFormatException e)
{
-
throw invalidLengths(strings);
-
}
-
}
-
private
IOException invalidLengths(String[]
strings)
throws
IOException
{
-
throw
new
IOException("unexpected journal line: "
+
Arrays.toString(strings));
-
}
-
public
File getCleanFile(int
i)
{
-
return
new
File(directory,
key +
"."
+ i);
-
}
-
public
File getDirtyFile(int
i)
{
-
return
new
File(directory,
key +
"."
+ i
+
".tmp");
-
}
-
}
- }
相关推荐
两行代码实现android中分享
分享小demo,其实市面上这种代码很多, 按menu键出现分享菜单,通过intent 传递数据,实现分享。
Android 系统自带分享功能【图片、文字、CSV和PDF文件】
前言: 调用系统自带的分享功能将文本,当前界面截图、CSV和PDF文件分享出去; 效果图【测试机型:小米10】: 1、代码部分 ①:工具类:FileShareUtils public class FileShareUtils { private static final String TAG = "FileShareUtils"; //文件输出流 private static OutputStream outputStream; /** * 维度。A4尺寸为21
关于Android实现简单的微信朋友圈分享功能
主要介绍了关于Android实现简单的微信朋友圈分享功能,非常不错,具有参考借鉴价值,需要的的朋友参考下
Android开发-在应用之间共享文件
Android开发-在应用之间共享文件
Android系统自带分享图片功能
简介 记录一个利用系统分享功能进行图片分享的工具类(代码是用Kotlin写的,都是比较简单的语法,部分可能需要自定义的地方都已经标出)。调用方式比较简单: Util.startShareImage(this) //this为当前的Activity实例 权限 记得添加文件操作权限, 另外需要注意6.0版本以上的权限管理 <uses android:name=android.permission.WRITE_EXTERNAL_STORAGE/> <uses-permission android:name=android.permission.READ_EXTERNA
改进差分进化算法及其求解柔性作业车间调度问题(Python代码实现)
内容概要:本文系统探讨了改进差分进化算法在求解柔性作业车间调度问题中的应用,并结合Python代码实现,展示了该算法相较于传统方法在全局搜索能力和收敛速度方面的优势。研究不仅聚焦于算法本身的改进策略,如变异、交叉和选择操作的优化,还深入分析了其在复杂制造环境中应对多目标、多约束调度问题的有效性。文中通过标准测试实例验证了算法性能,并与其他智能优化算法进行了对比分析,体现了其在实际工程应用中的潜力。此外,文档还关联了多无人机路径规划、电力系统状态估计、微电网优化、图像与信号处理等多个前沿技术领域,展现了智能优化算法在跨学科工程仿真与决策支持中的广泛适用性。; 适合人群:具备一定编程基础和工程背景,从事智能制造、自动化、工业工程、电气工程、控制科学与工程、计算机科学及相关领域的科研人员、高校研究生及企业研发技术人员。; 使用场景及目标:①为科研工作者提供可复现的智能优化算法代码框架,支持柔性作业车间调度、多无人机协同路径规划、电力系统优化等复杂工程问题的建模仿真;②辅助完成学术论文复现、课程设计、毕业设计或科研项目开发;③推动智能算法在生产调度、能源管理、交通规划等实际场景中的落地应用。; 阅读建议:建议读者结合文中提供的算法原理与代码实现,动手调试并验证不同参数设置对优化结果的影响,同时可参考荔枝科研社提供的完整资源包进行扩展学习与实践,以加深对算法机制与工程应用的理解。
Android中的缓存策略:LruCache和DiskLruCache
本篇文章主要是介绍Android中内置的两个缓存类的原理。所谓缓存,就是将获取的数据保存下来以便下次继续使用,这种技术尤其在网络请求和图片加载中有用,可以显著地提升App的性能表现。Android中也内置了两个缓存类,分别是和。
复现基于改进秃鹰算法的微电网群经济优化调度研究(Matlab代码实现)
内容概要:本文聚焦于“基于改进秃鹰算法的微电网群经济优化调度”研究,提出一种改进的秃鹰优化算法(IBOA)以解决传统智能优化算法在求解过程中易陷入局部最优、收敛速度慢等问题。通过引入自适应权重、动态搜索机制及种群多样性保持策略,显著增强了算法的全局搜索能力和收敛精度。研究构建了包含光伏发电、风力发电、储能系统、可控负荷及电网交互的微电网群多主体协同调度模型,以系统综合运行成本最小化为目标函数,综合考虑功率平衡、设备出力能力、储能荷电状态、爬坡速率等多种实际约束条件。利用Matlab平台进行仿真验证,并与标准BOA、PSO、GA等经典智能算法进行对比,结果表明所提算法在降低系统运行成本、提高可再生能源消纳率及优化调度效率方面具有明显优势。; 适合人群:适用于具备电力系统基础、优化理论知识及Matlab编程能力的高校研究生、科研人员及从事微电网、综合能源系统优化调度相关工作的工程技术人员。; 使用场景及目标:①用于微电网群或多微网系统的经济调度建模与仿真;②开展新型智能优化算法的设计、改进与性能评估;③支撑含高比例可再生能源的配电系统优化运行决策; 阅读建议:建议结合提供的Matlab代码深入理解算法实现细节与调度模型构建过程,重点分析改进策略对优化性能的影响,并可通过替换场景参数或引入其他算法进行横向对比实验,进一步掌握智能优化在能源系统中的应用方法。【复现】基于改进秃鹰算法的微电网群经济优化调度研究(Matlab代码实现)
UniversalImageLoader源码解析之 DiskCache
转载请注明出处:http://blog.csdn.net/crazy1235/article/details/70472306 FileNameGenerator DiskCache tryLoadBitmap UnlimitedDiskCache BaseDiskCache LimitedAgeDiskCache LruDiskCache get save remove clear上上一篇介绍了
DANN实战:用Python手把手教你实现对抗迁移学习(附完整代码)
本文通过Python实战教程,手把手教你实现DANN对抗迁移学习模型,以解决领域偏移问题。文章详细解析了领域对抗神经网络的核心机制,包括梯度反转层和领域判别器的构建,并提供了从数据准备、模型搭建到训练评估的完整代码,帮助开发者快速掌握这一强大的无监督领域自适应技术。
告别Root!用Termux+ADB在Android上玩转自动化(保姆级教程)
本文详细介绍了如何在非Root环境下使用Termux和ADB实现Android自动化操作,无需Root权限即可运行自动化脚本。通过Termux搭建Linux终端环境,结合ADB无线调试和Python脚本,实现点击、滑动、截图等自动化功能,适用于定时任务、自动签到等场景。
Universal-Image-Loader源码收获
不熟悉Universal-Image-Loader使用的同学,可参考: “Universal-Image-Loader使用” http://xiaxveliang.blog.163.com/blog/static/29708034201582853043103/ Universal-Image-Loader 如今使用广泛,最近也将公司项目中的图片下载引擎替换为了Universal-Imag...
SAP S/4HANA 可扩展性 – 面向初学者的简化指南
可扩展性(Extensibility)是 SAP ECC 和 SAP S/4HANA 的一项关键功能。它使客户能够通过定制其业务流程来创造竞争优势,并使合作伙伴能够通过量身定制的解决方案来丰富核心 ECC 或 S/4HANA。在过去的几十年中,SAP 的 on-premise 版本客户和合作伙伴主要使用经典的 ABAP 扩展性来扩展他们的 ERP 解决方案。经典扩展性允许 ABAP 开发人员使用甚至修改所有 SAP 对象。尽管经典扩展性非常强大和灵活,但在当今的云世界中,它已不再是一个好的选择。
xUtils源码阅读(8)-ImageDecoder
图片解析工具类,就是从文件读取图片内容,并处理成我们需要的样式(大小,旋转,修剪等)。 该工具类别看这么的长,实际上就一个函数,那就是decodeFileWithLock 所有其它的函数都是在为这个函数做服务呢,或者给这个函数做帮手呢。 这样再来看该类,就简单多了。 源码: /** * Created by wyouflf on 15/10/9. * ImageDecoder
Windows下用MSYS2一键搞定Verilator安装(附国内镜像加速)
本文详细介绍了在Windows系统下,如何利用MSYS2工具和国内镜像源,快速、高效地完成Verilator的安装与配置。通过替换清华镜像源加速下载,并配合Pacman包管理器,实现近乎一键式的安装流程,有效解决了传统方法步骤繁琐、网络依赖下载慢的问题,为数字电路仿真提供了便捷的Windows环境解决方案。
从SD更新字库到FLASH,再用LCD显示
用Flash当字库存储载体。思路是先将要用到的字库文件存在SD卡,然后存储到Flash,LCD显示时候从Flash中读取 还挺顺利的,移植的原子的程序,自己稍加修改,主要是我的液晶屏函数,Flash读写函数,还有存储文件的flash地址不一样。一晚上搞定,大小字体都OK。挺高兴哈哈。这几天忙工作事情有点耽搁了学习进展嘿嘿 在text.c的showstr函数中char的字体颜色随意设置的,可以显...
Android的DiskLruCache硬盘缓存技术
之前记着LruCache我们从网络下载好的图片现存入到内存,下次直接从内存读取整体读取速度都变快了,但是我们退出应用下次再进来相同的图片是否可以不用从网络读取,所以DiskLruCache就是做这方面的工作。
SIMULINK开发项目实例 1000 例专栏之第960例--基于电力系统稳定性的建模与仿真场景:-含分布式电源的配电网电压稳定性研究
间歇性与随机性:例如,太阳能依赖于日照强度,风能依赖于风速,这使得它们的输出功率具有不确定性。逆变器接口:大多数分布式电源通过逆变器接入电网,这对电网的动态响应特性产生了影响。位置分散:通常分布在用户侧或靠近负荷中心的位置。本文详细介绍了如何使用Simulink结合Simscape Electrical 构建一个用于研究含分布式电源配电网电压稳定性的模型,并提供了详细的仿真流程说明。
android分享各种文件
1.分享PDF文件: Intent intent = new Intent("android.intent.action.VIEW"); intent.addCategory("android.intent.category.DEFAULT"); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); Uri uri = Uri.
eNSP中玩转Python自动化——解锁网工新姿势(一)
Python自动化一、安装Paramiko模块二、搭建实验环境1、桥接电脑网卡2、配置交换机IP地址交换机配置测试ensp的交换机与电脑的连通性:交换机配置SSHXshell验证三、Python脚本1、执行python脚本2、同步成功 一、安装Paramiko模块 首先安装Python3 然后安装pip解释器 最后安装Paramiko模块 Windows+R打开Cmd,输入命令: pip3 install paramiko 进入python,导入 paramiko模块: import paramiko
Python爬虫实战:使用最新技术爬取新浪新闻首页数据
本文详细介绍了三种爬取新浪新闻首页的技术方案,从简单的requests-html到强大的Playwright,再到分布式Scrapy架构。小规模爬取:requests-html或aiohttp方案需要JavaScript渲染:Playwright方案大规模分布式爬取:Scrapy-Redis方案。
Android 7.0适配 打开文件和分享文件
嗯,我就闲来无事想说一句,我是可爱的分割线(●’◡’●)。7.0的禁止不安全路径被外部访问。其中典型例子就是拍照。所以我以为分享图片也是需要适配,实验得知并不是。分享走的是系统的,所以不会有错误,如果你是打开文件的话,那么久需要适配content URL了。可是6.xx之后要动态申请权限,所以如果是对文件会发生读写操作的话需要把,申请读写权限意图说明。也就是 addFlags。1.Intent.FL
pscad IEEE标准系统搭建说明
pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。
android 展示pdf文件
注:此方式展示pdf文件会增加apk大小3-4m左右 建议使用x5的webview进行加载pdf文件(可扩展)X5内核初始化下载失败的解决方案有道云笔记 2. 简单介绍 此篇文章主要还是将pdf文件进行下载到本sd目录下,之后转为file文件,交给pdfview进行展示,具体的展示pdf文件可进入pdfview源码中进行查看https://github.com/barteksc/AndroidPdfViewer 3.1 okhttp依赖:
锂电池充电器电路设计 里面有电路
锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。
OV9734 DATASHEET,完整规格书
OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等
android APP内显示PDF文件
android APP内显示PDF文件最近公司要做一个小功能,显示在APP内部显示pdf文件,刚接到任务感觉非常简单的一小功能,做的时候才发现在真的很不好实现。 因为android的webView与iOS的UIWebView不一样,不能够直接支持在线观看pdf文件,虽然谷歌官方给了个“http//docs.google.com/gviewembedded=true&url=”这样的解决方案,但是需要
android 在线预览pdf文件(目前最全)
android原生webView不支持预览pdf文件,ios却可以,所以android想要实现在线预览webView要通过其他方法,有以下几种方法: 一.andorid原生自带的pdf管理库,主要提供两个类pdfRender和pdfDocument,有兴趣的可以自己去了解一下,缺点就是必须是API>=21(也就是5.0以上)的版本才能支持,局限性太大。 二.一个github上面的开源库P...
AndroidPdf框架一览(一)
Android端PDF框架一览(一)1. 简介(1)android-pdfview第一个当然是github上面star 最多android-pdfview,它是基于谷歌的一款开源PDF浏览框架VuDroid的Android端开发框架, 支持缩略图,缩放,页面枚举,默认起始页。还有pdf加载监听,pdf页面滑动监听。 功能很强大,不过很可惜,功能有限,作者也在15年停止了维护。(2)pdfiump
3190



被折叠的 条评论
为什么被折叠?



