Home | History | Annotate | Download | only in common
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 // This is an on-disk cache which maps a 64-bits key to a byte array.
     18 //
     19 // It consists of three files: one index file and two data files. One of the
     20 // data files is "active", and the other is "inactive". New entries are
     21 // appended into the active region until it reaches the size limit. At that
     22 // point the active file and the inactive file are swapped, and the new active
     23 // file is truncated to empty (and the index for that file is also cleared).
     24 // The index is a hash table with linear probing. When the load factor reaches
     25 // 0.5, it does the same thing like when the size limit is reached.
     26 //
     27 // The index file format: (all numbers are stored in little-endian)
     28 // [0]  Magic number: 0xB3273030
     29 // [4]  MaxEntries: Max number of hash entries per region.
     30 // [8]  MaxBytes: Max number of data bytes per region (including header).
     31 // [12] ActiveRegion: The active growing region: 0 or 1.
     32 // [16] ActiveEntries: The number of hash entries used in the active region.
     33 // [20] ActiveBytes: The number of data bytes used in the active region.
     34 // [24] Version number.
     35 // [28] Checksum of [0..28).
     36 // [32] Hash entries for region 0. The size is X = (12 * MaxEntries bytes).
     37 // [32 + X] Hash entries for region 1. The size is also X.
     38 //
     39 // Each hash entry is 12 bytes: 8 bytes key and 4 bytes offset into the data
     40 // file. The offset is 0 when the slot is free. Note that 0 is a valid value
     41 // for key. The keys are used directly as index into a hash table, so they
     42 // should be suitably distributed.
     43 //
     44 // Each data file stores data for one region. The data file is concatenated
     45 // blobs followed by the magic number 0xBD248510.
     46 //
     47 // The blob format:
     48 // [0]  Key of this blob
     49 // [8]  Checksum of this blob
     50 // [12] Offset of this blob
     51 // [16] Length of this blob (not including header)
     52 // [20] Blob
     53 //
     54 // Below are the interface for BlobCache. The instance of this class does not
     55 // support concurrent use by multiple threads.
     56 //
     57 // public BlobCache(String path, int maxEntries, int maxBytes, boolean reset) throws IOException;
     58 // public void insert(long key, byte[] data) throws IOException;
     59 // public byte[] lookup(long key) throws IOException;
     60 // public void lookup(LookupRequest req) throws IOException;
     61 // public void close();
     62 // public void syncIndex();
     63 // public void syncAll();
     64 // public static void deleteFiles(String path);
     65 //
     66 package com.android.gallery3d.common;
     67 
     68 import android.util.Log;
     69 
     70 import java.io.Closeable;
     71 import java.io.File;
     72 import java.io.IOException;
     73 import java.io.RandomAccessFile;
     74 import java.nio.ByteOrder;
     75 import java.nio.MappedByteBuffer;
     76 import java.nio.channels.FileChannel;
     77 import java.util.Arrays;
     78 import java.util.zip.Adler32;
     79 
     80 public class BlobCache implements Closeable {
     81     private static final String TAG = "BlobCache";
     82 
     83     private static final int MAGIC_INDEX_FILE = 0xB3273030;
     84     private static final int MAGIC_DATA_FILE = 0xBD248510;
     85 
     86     // index header offset
     87     private static final int IH_MAGIC = 0;
     88     private static final int IH_MAX_ENTRIES = 4;
     89     private static final int IH_MAX_BYTES = 8;
     90     private static final int IH_ACTIVE_REGION = 12;
     91     private static final int IH_ACTIVE_ENTRIES = 16;
     92     private static final int IH_ACTIVE_BYTES = 20;
     93     private static final int IH_VERSION = 24;
     94     private static final int IH_CHECKSUM = 28;
     95     private static final int INDEX_HEADER_SIZE = 32;
     96 
     97     private static final int DATA_HEADER_SIZE = 4;
     98 
     99     // blob header offset
    100     private static final int BH_KEY = 0;
    101     private static final int BH_CHECKSUM = 8;
    102     private static final int BH_OFFSET = 12;
    103     private static final int BH_LENGTH = 16;
    104     private static final int BLOB_HEADER_SIZE = 20;
    105 
    106     private RandomAccessFile mIndexFile;
    107     private RandomAccessFile mDataFile0;
    108     private RandomAccessFile mDataFile1;
    109     private FileChannel mIndexChannel;
    110     private MappedByteBuffer mIndexBuffer;
    111 
    112     private int mMaxEntries;
    113     private int mMaxBytes;
    114     private int mActiveRegion;
    115     private int mActiveEntries;
    116     private int mActiveBytes;
    117     private int mVersion;
    118 
    119     private RandomAccessFile mActiveDataFile;
    120     private RandomAccessFile mInactiveDataFile;
    121     private int mActiveHashStart;
    122     private int mInactiveHashStart;
    123     private byte[] mIndexHeader = new byte[INDEX_HEADER_SIZE];
    124     private byte[] mBlobHeader = new byte[BLOB_HEADER_SIZE];
    125     private Adler32 mAdler32 = new Adler32();
    126 
    127     // Creates the cache. Three files will be created:
    128     // path + ".idx", path + ".0", and path + ".1"
    129     // The ".0" file and the ".1" file each stores data for a region. Each of
    130     // them can grow to the size specified by maxBytes. The maxEntries parameter
    131     // specifies the maximum number of entries each region can have. If the
    132     // "reset" parameter is true, the cache will be cleared before use.
    133     public BlobCache(String path, int maxEntries, int maxBytes, boolean reset)
    134             throws IOException {
    135         this(path, maxEntries, maxBytes, reset, 0);
    136     }
    137 
    138     public BlobCache(String path, int maxEntries, int maxBytes, boolean reset,
    139             int version) throws IOException {
    140         mIndexFile = new RandomAccessFile(path + ".idx", "rw");
    141         mDataFile0 = new RandomAccessFile(path + ".0", "rw");
    142         mDataFile1 = new RandomAccessFile(path + ".1", "rw");
    143         mVersion = version;
    144 
    145         if (!reset && loadIndex()) {
    146             return;
    147         }
    148 
    149         resetCache(maxEntries, maxBytes);
    150 
    151         if (!loadIndex()) {
    152             closeAll();
    153             throw new IOException("unable to load index");
    154         }
    155     }
    156 
    157     // Delete the files associated with the given path previously created
    158     // by the BlobCache constructor.
    159     public static void deleteFiles(String path) {
    160         deleteFileSilently(path + ".idx");
    161         deleteFileSilently(path + ".0");
    162         deleteFileSilently(path + ".1");
    163     }
    164 
    165     private static void deleteFileSilently(String path) {
    166         try {
    167             new File(path).delete();
    168         } catch (Throwable t) {
    169             // ignore;
    170         }
    171     }
    172 
    173     // Close the cache. All resources are released. No other method should be
    174     // called after this is called.
    175     @Override
    176     public void close() {
    177         syncAll();
    178         closeAll();
    179     }
    180 
    181     private void closeAll() {
    182         closeSilently(mIndexChannel);
    183         closeSilently(mIndexFile);
    184         closeSilently(mDataFile0);
    185         closeSilently(mDataFile1);
    186     }
    187 
    188     // Returns true if loading index is successful. After this method is called,
    189     // mIndexHeader and index header in file should be kept sync.
    190     private boolean loadIndex() {
    191         try {
    192             mIndexFile.seek(0);
    193             mDataFile0.seek(0);
    194             mDataFile1.seek(0);
    195 
    196             byte[] buf = mIndexHeader;
    197             if (mIndexFile.read(buf) != INDEX_HEADER_SIZE) {
    198                 Log.w(TAG, "cannot read header");
    199                 return false;
    200             }
    201 
    202             if (readInt(buf, IH_MAGIC) != MAGIC_INDEX_FILE) {
    203                 Log.w(TAG, "cannot read header magic");
    204                 return false;
    205             }
    206 
    207             if (readInt(buf, IH_VERSION) != mVersion) {
    208                 Log.w(TAG, "version mismatch");
    209                 return false;
    210             }
    211 
    212             mMaxEntries = readInt(buf, IH_MAX_ENTRIES);
    213             mMaxBytes = readInt(buf, IH_MAX_BYTES);
    214             mActiveRegion = readInt(buf, IH_ACTIVE_REGION);
    215             mActiveEntries = readInt(buf, IH_ACTIVE_ENTRIES);
    216             mActiveBytes = readInt(buf, IH_ACTIVE_BYTES);
    217 
    218             int sum = readInt(buf, IH_CHECKSUM);
    219             if (checkSum(buf, 0, IH_CHECKSUM) != sum) {
    220                 Log.w(TAG, "header checksum does not match");
    221                 return false;
    222             }
    223 
    224             // Sanity check
    225             if (mMaxEntries <= 0) {
    226                 Log.w(TAG, "invalid max entries");
    227                 return false;
    228             }
    229             if (mMaxBytes <= 0) {
    230                 Log.w(TAG, "invalid max bytes");
    231                 return false;
    232             }
    233             if (mActiveRegion != 0 && mActiveRegion != 1) {
    234                 Log.w(TAG, "invalid active region");
    235                 return false;
    236             }
    237             if (mActiveEntries < 0 || mActiveEntries > mMaxEntries) {
    238                 Log.w(TAG, "invalid active entries");
    239                 return false;
    240             }
    241             if (mActiveBytes < DATA_HEADER_SIZE || mActiveBytes > mMaxBytes) {
    242                 Log.w(TAG, "invalid active bytes");
    243                 return false;
    244             }
    245             if (mIndexFile.length() !=
    246                     INDEX_HEADER_SIZE + mMaxEntries * 12 * 2) {
    247                 Log.w(TAG, "invalid index file length");
    248                 return false;
    249             }
    250 
    251             // Make sure data file has magic
    252             byte[] magic = new byte[4];
    253             if (mDataFile0.read(magic) != 4) {
    254                 Log.w(TAG, "cannot read data file magic");
    255                 return false;
    256             }
    257             if (readInt(magic, 0) != MAGIC_DATA_FILE) {
    258                 Log.w(TAG, "invalid data file magic");
    259                 return false;
    260             }
    261             if (mDataFile1.read(magic) != 4) {
    262                 Log.w(TAG, "cannot read data file magic");
    263                 return false;
    264             }
    265             if (readInt(magic, 0) != MAGIC_DATA_FILE) {
    266                 Log.w(TAG, "invalid data file magic");
    267                 return false;
    268             }
    269 
    270             // Map index file to memory
    271             mIndexChannel = mIndexFile.getChannel();
    272             mIndexBuffer = mIndexChannel.map(FileChannel.MapMode.READ_WRITE,
    273                     0, mIndexFile.length());
    274             mIndexBuffer.order(ByteOrder.LITTLE_ENDIAN);
    275 
    276             setActiveVariables();
    277             return true;
    278         } catch (IOException ex) {
    279             Log.e(TAG, "loadIndex failed.", ex);
    280             return false;
    281         }
    282     }
    283 
    284     private void setActiveVariables() throws IOException {
    285         mActiveDataFile = (mActiveRegion == 0) ? mDataFile0 : mDataFile1;
    286         mInactiveDataFile = (mActiveRegion == 1) ? mDataFile0 : mDataFile1;
    287         mActiveDataFile.setLength(mActiveBytes);
    288         mActiveDataFile.seek(mActiveBytes);
    289 
    290         mActiveHashStart = INDEX_HEADER_SIZE;
    291         mInactiveHashStart = INDEX_HEADER_SIZE;
    292 
    293         if (mActiveRegion == 0) {
    294             mInactiveHashStart += mMaxEntries * 12;
    295         } else {
    296             mActiveHashStart += mMaxEntries * 12;
    297         }
    298     }
    299 
    300     private void resetCache(int maxEntries, int maxBytes) throws IOException {
    301         mIndexFile.setLength(0);  // truncate to zero the index
    302         mIndexFile.setLength(INDEX_HEADER_SIZE + maxEntries * 12 * 2);
    303         mIndexFile.seek(0);
    304         byte[] buf = mIndexHeader;
    305         writeInt(buf, IH_MAGIC, MAGIC_INDEX_FILE);
    306         writeInt(buf, IH_MAX_ENTRIES, maxEntries);
    307         writeInt(buf, IH_MAX_BYTES, maxBytes);
    308         writeInt(buf, IH_ACTIVE_REGION, 0);
    309         writeInt(buf, IH_ACTIVE_ENTRIES, 0);
    310         writeInt(buf, IH_ACTIVE_BYTES, DATA_HEADER_SIZE);
    311         writeInt(buf, IH_VERSION, mVersion);
    312         writeInt(buf, IH_CHECKSUM, checkSum(buf, 0, IH_CHECKSUM));
    313         mIndexFile.write(buf);
    314         // This is only needed if setLength does not zero the extended part.
    315         // writeZero(mIndexFile, maxEntries * 12 * 2);
    316 
    317         mDataFile0.setLength(0);
    318         mDataFile1.setLength(0);
    319         mDataFile0.seek(0);
    320         mDataFile1.seek(0);
    321         writeInt(buf, 0, MAGIC_DATA_FILE);
    322         mDataFile0.write(buf, 0, 4);
    323         mDataFile1.write(buf, 0, 4);
    324     }
    325 
    326     // Flip the active region and the inactive region.
    327     private void flipRegion() throws IOException {
    328         mActiveRegion = 1 - mActiveRegion;
    329         mActiveEntries = 0;
    330         mActiveBytes = DATA_HEADER_SIZE;
    331 
    332         writeInt(mIndexHeader, IH_ACTIVE_REGION, mActiveRegion);
    333         writeInt(mIndexHeader, IH_ACTIVE_ENTRIES, mActiveEntries);
    334         writeInt(mIndexHeader, IH_ACTIVE_BYTES, mActiveBytes);
    335         updateIndexHeader();
    336 
    337         setActiveVariables();
    338         clearHash(mActiveHashStart);
    339         syncIndex();
    340     }
    341 
    342     // Sync mIndexHeader to the index file.
    343     private void updateIndexHeader() {
    344         writeInt(mIndexHeader, IH_CHECKSUM,
    345                 checkSum(mIndexHeader, 0, IH_CHECKSUM));
    346         mIndexBuffer.position(0);
    347         mIndexBuffer.put(mIndexHeader);
    348     }
    349 
    350     // Clear the hash table starting from the specified offset.
    351     private void clearHash(int hashStart) {
    352         byte[] zero = new byte[1024];
    353         mIndexBuffer.position(hashStart);
    354         for (int count = mMaxEntries * 12; count > 0;) {
    355             int todo = Math.min(count, 1024);
    356             mIndexBuffer.put(zero, 0, todo);
    357             count -= todo;
    358         }
    359     }
    360 
    361     // Inserts a (key, data) pair into the cache.
    362     public void insert(long key, byte[] data) throws IOException {
    363         if (DATA_HEADER_SIZE + BLOB_HEADER_SIZE + data.length > mMaxBytes) {
    364             throw new RuntimeException("blob is too large!");
    365         }
    366 
    367         if (mActiveBytes + BLOB_HEADER_SIZE + data.length > mMaxBytes
    368                 || mActiveEntries * 2 >= mMaxEntries) {
    369             flipRegion();
    370         }
    371 
    372         if (!lookupInternal(key, mActiveHashStart)) {
    373             // If we don't have an existing entry with the same key, increase
    374             // the entry count.
    375             mActiveEntries++;
    376             writeInt(mIndexHeader, IH_ACTIVE_ENTRIES, mActiveEntries);
    377         }
    378 
    379         insertInternal(key, data, data.length);
    380         updateIndexHeader();
    381     }
    382 
    383     public void clearEntry(long key) throws IOException {
    384         if (!lookupInternal(key, mActiveHashStart)) {
    385             return; // Nothing to clear
    386         }
    387         byte[] header = mBlobHeader;
    388         Arrays.fill(header, (byte) 0);
    389         mActiveDataFile.seek(mFileOffset);
    390         mActiveDataFile.write(header);
    391     }
    392 
    393     // Appends the data to the active file. It also updates the hash entry.
    394     // The proper hash entry (suitable for insertion or replacement) must be
    395     // pointed by mSlotOffset.
    396     private void insertInternal(long key, byte[] data, int length)
    397             throws IOException {
    398         byte[] header = mBlobHeader;
    399         int sum = checkSum(data);
    400         writeLong(header, BH_KEY, key);
    401         writeInt(header, BH_CHECKSUM, sum);
    402         writeInt(header, BH_OFFSET, mActiveBytes);
    403         writeInt(header, BH_LENGTH, length);
    404         mActiveDataFile.write(header);
    405         mActiveDataFile.write(data, 0, length);
    406 
    407         mIndexBuffer.putLong(mSlotOffset, key);
    408         mIndexBuffer.putInt(mSlotOffset + 8, mActiveBytes);
    409         mActiveBytes += BLOB_HEADER_SIZE + length;
    410         writeInt(mIndexHeader, IH_ACTIVE_BYTES, mActiveBytes);
    411     }
    412 
    413     public static class LookupRequest {
    414         public long key;        // input: the key to find
    415         public byte[] buffer;   // input/output: the buffer to store the blob
    416         public int length;      // output: the length of the blob
    417     }
    418 
    419     // This method is for one-off lookup. For repeated lookup, use the version
    420     // accepting LookupRequest to avoid repeated memory allocation.
    421     private LookupRequest mLookupRequest = new LookupRequest();
    422     public byte[] lookup(long key) throws IOException {
    423         mLookupRequest.key = key;
    424         mLookupRequest.buffer = null;
    425         if (lookup(mLookupRequest)) {
    426             return mLookupRequest.buffer;
    427         } else {
    428             return null;
    429         }
    430     }
    431 
    432     // Returns true if the associated blob for the given key is available.
    433     // The blob is stored in the buffer pointed by req.buffer, and the length
    434     // is in stored in the req.length variable.
    435     //
    436     // The user can input a non-null value in req.buffer, and this method will
    437     // try to use that buffer. If that buffer is not large enough, this method
    438     // will allocate a new buffer and assign it to req.buffer.
    439     //
    440     // This method tries not to throw IOException even if the data file is
    441     // corrupted, but it can still throw IOException if things get strange.
    442     public boolean lookup(LookupRequest req) throws IOException {
    443         // Look up in the active region first.
    444         if (lookupInternal(req.key, mActiveHashStart)) {
    445             if (getBlob(mActiveDataFile, mFileOffset, req)) {
    446                 return true;
    447             }
    448         }
    449 
    450         // We want to copy the data from the inactive file to the active file
    451         // if it's available. So we keep the offset of the hash entry so we can
    452         // avoid looking it up again.
    453         int insertOffset = mSlotOffset;
    454 
    455         // Look up in the inactive region.
    456         if (lookupInternal(req.key, mInactiveHashStart)) {
    457             if (getBlob(mInactiveDataFile, mFileOffset, req)) {
    458                 // If we don't have enough space to insert this blob into
    459                 // the active file, just return it.
    460                 if (mActiveBytes + BLOB_HEADER_SIZE + req.length > mMaxBytes
    461                     || mActiveEntries * 2 >= mMaxEntries) {
    462                     return true;
    463                 }
    464                 // Otherwise copy it over.
    465                 mSlotOffset = insertOffset;
    466                 try {
    467                     insertInternal(req.key, req.buffer, req.length);
    468                     mActiveEntries++;
    469                     writeInt(mIndexHeader, IH_ACTIVE_ENTRIES, mActiveEntries);
    470                     updateIndexHeader();
    471                 } catch (Throwable t) {
    472                     Log.e(TAG, "cannot copy over");
    473                 }
    474                 return true;
    475             }
    476         }
    477 
    478         return false;
    479     }
    480 
    481 
    482     // Copies the blob for the specified offset in the specified file to
    483     // req.buffer. If req.buffer is null or too small, allocate a buffer and
    484     // assign it to req.buffer.
    485     // Returns false if the blob is not available (either the index file is
    486     // not sync with the data file, or one of them is corrupted). The length
    487     // of the blob is stored in the req.length variable.
    488     private boolean getBlob(RandomAccessFile file, int offset,
    489             LookupRequest req) throws IOException {
    490         byte[] header = mBlobHeader;
    491         long oldPosition = file.getFilePointer();
    492         try {
    493             file.seek(offset);
    494             if (file.read(header) != BLOB_HEADER_SIZE) {
    495                 Log.w(TAG, "cannot read blob header");
    496                 return false;
    497             }
    498             long blobKey = readLong(header, BH_KEY);
    499             if (blobKey == 0) {
    500                 return false; // This entry has been cleared.
    501             }
    502             if (blobKey != req.key) {
    503                 Log.w(TAG, "blob key does not match: " + blobKey);
    504                 return false;
    505             }
    506             int sum = readInt(header, BH_CHECKSUM);
    507             int blobOffset = readInt(header, BH_OFFSET);
    508             if (blobOffset != offset) {
    509                 Log.w(TAG, "blob offset does not match: " + blobOffset);
    510                 return false;
    511             }
    512             int length = readInt(header, BH_LENGTH);
    513             if (length < 0 || length > mMaxBytes - offset - BLOB_HEADER_SIZE) {
    514                 Log.w(TAG, "invalid blob length: " + length);
    515                 return false;
    516             }
    517             if (req.buffer == null || req.buffer.length < length) {
    518                 req.buffer = new byte[length];
    519             }
    520 
    521             byte[] blob = req.buffer;
    522             req.length = length;
    523 
    524             if (file.read(blob, 0, length) != length) {
    525                 Log.w(TAG, "cannot read blob data");
    526                 return false;
    527             }
    528             if (checkSum(blob, 0, length) != sum) {
    529                 Log.w(TAG, "blob checksum does not match: " + sum);
    530                 return false;
    531             }
    532             return true;
    533         } catch (Throwable t)  {
    534             Log.e(TAG, "getBlob failed.", t);
    535             return false;
    536         } finally {
    537             file.seek(oldPosition);
    538         }
    539     }
    540 
    541     // Tries to look up a key in the specified hash region.
    542     // Returns true if the lookup is successful.
    543     // The slot offset in the index file is saved in mSlotOffset. If the lookup
    544     // is successful, it's the slot found. Otherwise it's the slot suitable for
    545     // insertion.
    546     // If the lookup is successful, the file offset is also saved in
    547     // mFileOffset.
    548     private int mSlotOffset;
    549     private int mFileOffset;
    550     private boolean lookupInternal(long key, int hashStart) {
    551         int slot = (int) (key % mMaxEntries);
    552         if (slot < 0) slot += mMaxEntries;
    553         int slotBegin = slot;
    554         while (true) {
    555             int offset = hashStart + slot * 12;
    556             long candidateKey = mIndexBuffer.getLong(offset);
    557             int candidateOffset = mIndexBuffer.getInt(offset + 8);
    558             if (candidateOffset == 0) {
    559                 mSlotOffset = offset;
    560                 return false;
    561             } else if (candidateKey == key) {
    562                 mSlotOffset = offset;
    563                 mFileOffset = candidateOffset;
    564                 return true;
    565             } else {
    566                 if (++slot >= mMaxEntries) {
    567                     slot = 0;
    568                 }
    569                 if (slot == slotBegin) {
    570                     Log.w(TAG, "corrupted index: clear the slot.");
    571                     mIndexBuffer.putInt(hashStart + slot * 12 + 8, 0);
    572                 }
    573             }
    574         }
    575     }
    576 
    577     public void syncIndex() {
    578         try {
    579             mIndexBuffer.force();
    580         } catch (Throwable t) {
    581             Log.w(TAG, "sync index failed", t);
    582         }
    583     }
    584 
    585     public void syncAll() {
    586         syncIndex();
    587         try {
    588             mDataFile0.getFD().sync();
    589         } catch (Throwable t) {
    590             Log.w(TAG, "sync data file 0 failed", t);
    591         }
    592         try {
    593             mDataFile1.getFD().sync();
    594         } catch (Throwable t) {
    595             Log.w(TAG, "sync data file 1 failed", t);
    596         }
    597     }
    598 
    599     // This is for testing only.
    600     //
    601     // Returns the active count (mActiveEntries). This also verifies that
    602     // the active count matches matches what's inside the hash region.
    603     int getActiveCount() {
    604         int count = 0;
    605         for (int i = 0; i < mMaxEntries; i++) {
    606             int offset = mActiveHashStart + i * 12;
    607             long candidateKey = mIndexBuffer.getLong(offset);
    608             int candidateOffset = mIndexBuffer.getInt(offset + 8);
    609             if (candidateOffset != 0) ++count;
    610         }
    611         if (count == mActiveEntries) {
    612             return count;
    613         } else {
    614             Log.e(TAG, "wrong active count: " + mActiveEntries + " vs " + count);
    615             return -1;  // signal failure.
    616         }
    617     }
    618 
    619     int checkSum(byte[] data) {
    620         mAdler32.reset();
    621         mAdler32.update(data);
    622         return (int) mAdler32.getValue();
    623     }
    624 
    625     int checkSum(byte[] data, int offset, int nbytes) {
    626         mAdler32.reset();
    627         mAdler32.update(data, offset, nbytes);
    628         return (int) mAdler32.getValue();
    629     }
    630 
    631     static void closeSilently(Closeable c) {
    632         if (c == null) return;
    633         try {
    634             c.close();
    635         } catch (Throwable t) {
    636             // do nothing
    637         }
    638     }
    639 
    640     static int readInt(byte[] buf, int offset) {
    641         return (buf[offset] & 0xff)
    642                 | ((buf[offset + 1] & 0xff) << 8)
    643                 | ((buf[offset + 2] & 0xff) << 16)
    644                 | ((buf[offset + 3] & 0xff) << 24);
    645     }
    646 
    647     static long readLong(byte[] buf, int offset) {
    648         long result = buf[offset + 7] & 0xff;
    649         for (int i = 6; i >= 0; i--) {
    650             result = (result << 8) | (buf[offset + i] & 0xff);
    651         }
    652         return result;
    653     }
    654 
    655     static void writeInt(byte[] buf, int offset, int value) {
    656         for (int i = 0; i < 4; i++) {
    657             buf[offset + i] = (byte) (value & 0xff);
    658             value >>= 8;
    659         }
    660     }
    661 
    662     static void writeLong(byte[] buf, int offset, long value) {
    663         for (int i = 0; i < 8; i++) {
    664             buf[offset + i] = (byte) (value & 0xff);
    665             value >>= 8;
    666         }
    667     }
    668 }
    669