Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (C) 2009 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 package android.util;
     18 
     19 import com.android.internal.util.ArrayUtils;
     20 import com.android.internal.util.GrowingArrayUtils;
     21 
     22 import libcore.util.EmptyArray;
     23 
     24 /**
     25  * SparseArray mapping longs to Objects.  Unlike a normal array of Objects,
     26  * there can be gaps in the indices.  It is intended to be more memory efficient
     27  * than using a HashMap to map Longs to Objects, both because it avoids
     28  * auto-boxing keys and its data structure doesn't rely on an extra entry object
     29  * for each mapping.
     30  *
     31  * <p>Note that this container keeps its mappings in an array data structure,
     32  * using a binary search to find keys.  The implementation is not intended to be appropriate for
     33  * data structures
     34  * that may contain large numbers of items.  It is generally slower than a traditional
     35  * HashMap, since lookups require a binary search and adds and removes require inserting
     36  * and deleting entries in the array.  For containers holding up to hundreds of items,
     37  * the performance difference is not significant, less than 50%.</p>
     38  *
     39  * <p>To help with performance, the container includes an optimization when removing
     40  * keys: instead of compacting its array immediately, it leaves the removed entry marked
     41  * as deleted.  The entry can then be re-used for the same key, or compacted later in
     42  * a single garbage collection step of all removed entries.  This garbage collection will
     43  * need to be performed at any time the array needs to be grown or the the map size or
     44  * entry values are retrieved.</p>
     45  *
     46  * <p>It is possible to iterate over the items in this container using
     47  * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using
     48  * <code>keyAt(int)</code> with ascending values of the index will return the
     49  * keys in ascending order, or the values corresponding to the keys in ascending
     50  * order in the case of <code>valueAt(int)</code>.</p>
     51  */
     52 public class LongSparseArray<E> implements Cloneable {
     53     private static final Object DELETED = new Object();
     54     private boolean mGarbage = false;
     55 
     56     private long[] mKeys;
     57     private Object[] mValues;
     58     private int mSize;
     59 
     60     /**
     61      * Creates a new LongSparseArray containing no mappings.
     62      */
     63     public LongSparseArray() {
     64         this(10);
     65     }
     66 
     67     /**
     68      * Creates a new LongSparseArray containing no mappings that will not
     69      * require any additional memory allocation to store the specified
     70      * number of mappings.  If you supply an initial capacity of 0, the
     71      * sparse array will be initialized with a light-weight representation
     72      * not requiring any additional array allocations.
     73      */
     74     public LongSparseArray(int initialCapacity) {
     75         if (initialCapacity == 0) {
     76             mKeys = EmptyArray.LONG;
     77             mValues = EmptyArray.OBJECT;
     78         } else {
     79             mKeys = ArrayUtils.newUnpaddedLongArray(initialCapacity);
     80             mValues = ArrayUtils.newUnpaddedObjectArray(initialCapacity);
     81         }
     82         mSize = 0;
     83     }
     84 
     85     @Override
     86     @SuppressWarnings("unchecked")
     87     public LongSparseArray<E> clone() {
     88         LongSparseArray<E> clone = null;
     89         try {
     90             clone = (LongSparseArray<E>) super.clone();
     91             clone.mKeys = mKeys.clone();
     92             clone.mValues = mValues.clone();
     93         } catch (CloneNotSupportedException cnse) {
     94             /* ignore */
     95         }
     96         return clone;
     97     }
     98 
     99     /**
    100      * Gets the Object mapped from the specified key, or <code>null</code>
    101      * if no such mapping has been made.
    102      */
    103     public E get(long key) {
    104         return get(key, null);
    105     }
    106 
    107     /**
    108      * Gets the Object mapped from the specified key, or the specified Object
    109      * if no such mapping has been made.
    110      */
    111     @SuppressWarnings("unchecked")
    112     public E get(long key, E valueIfKeyNotFound) {
    113         int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
    114 
    115         if (i < 0 || mValues[i] == DELETED) {
    116             return valueIfKeyNotFound;
    117         } else {
    118             return (E) mValues[i];
    119         }
    120     }
    121 
    122     /**
    123      * Removes the mapping from the specified key, if there was any.
    124      */
    125     public void delete(long key) {
    126         int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
    127 
    128         if (i >= 0) {
    129             if (mValues[i] != DELETED) {
    130                 mValues[i] = DELETED;
    131                 mGarbage = true;
    132             }
    133         }
    134     }
    135 
    136     /**
    137      * Alias for {@link #delete(long)}.
    138      */
    139     public void remove(long key) {
    140         delete(key);
    141     }
    142 
    143     /**
    144      * Removes the mapping at the specified index.
    145      *
    146      * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
    147      * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
    148      * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
    149      * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
    150      */
    151     public void removeAt(int index) {
    152         if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
    153             // The array might be slightly bigger than mSize, in which case, indexing won't fail.
    154             // Check if exception should be thrown outside of the critical path.
    155             throw new ArrayIndexOutOfBoundsException(index);
    156         }
    157         if (mValues[index] != DELETED) {
    158             mValues[index] = DELETED;
    159             mGarbage = true;
    160         }
    161     }
    162 
    163     private void gc() {
    164         // Log.e("SparseArray", "gc start with " + mSize);
    165 
    166         int n = mSize;
    167         int o = 0;
    168         long[] keys = mKeys;
    169         Object[] values = mValues;
    170 
    171         for (int i = 0; i < n; i++) {
    172             Object val = values[i];
    173 
    174             if (val != DELETED) {
    175                 if (i != o) {
    176                     keys[o] = keys[i];
    177                     values[o] = val;
    178                     values[i] = null;
    179                 }
    180 
    181                 o++;
    182             }
    183         }
    184 
    185         mGarbage = false;
    186         mSize = o;
    187 
    188         // Log.e("SparseArray", "gc end with " + mSize);
    189     }
    190 
    191     /**
    192      * Adds a mapping from the specified key to the specified value,
    193      * replacing the previous mapping from the specified key if there
    194      * was one.
    195      */
    196     public void put(long key, E value) {
    197         int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
    198 
    199         if (i >= 0) {
    200             mValues[i] = value;
    201         } else {
    202             i = ~i;
    203 
    204             if (i < mSize && mValues[i] == DELETED) {
    205                 mKeys[i] = key;
    206                 mValues[i] = value;
    207                 return;
    208             }
    209 
    210             if (mGarbage && mSize >= mKeys.length) {
    211                 gc();
    212 
    213                 // Search again because indices may have changed.
    214                 i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
    215             }
    216 
    217             mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
    218             mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
    219             mSize++;
    220         }
    221     }
    222 
    223     /**
    224      * Returns the number of key-value mappings that this LongSparseArray
    225      * currently stores.
    226      */
    227     public int size() {
    228         if (mGarbage) {
    229             gc();
    230         }
    231 
    232         return mSize;
    233     }
    234 
    235     /**
    236      * Given an index in the range <code>0...size()-1</code>, returns
    237      * the key from the <code>index</code>th key-value mapping that this
    238      * LongSparseArray stores.
    239      *
    240      * <p>The keys corresponding to indices in ascending order are guaranteed to
    241      * be in ascending order, e.g., <code>keyAt(0)</code> will return the
    242      * smallest key and <code>keyAt(size()-1)</code> will return the largest
    243      * key.</p>
    244      *
    245      * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
    246      * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
    247      * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
    248      * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
    249      */
    250     public long keyAt(int index) {
    251         if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
    252             // The array might be slightly bigger than mSize, in which case, indexing won't fail.
    253             // Check if exception should be thrown outside of the critical path.
    254             throw new ArrayIndexOutOfBoundsException(index);
    255         }
    256         if (mGarbage) {
    257             gc();
    258         }
    259 
    260         return mKeys[index];
    261     }
    262 
    263     /**
    264      * Given an index in the range <code>0...size()-1</code>, returns
    265      * the value from the <code>index</code>th key-value mapping that this
    266      * LongSparseArray stores.
    267      *
    268      * <p>The values corresponding to indices in ascending order are guaranteed
    269      * to be associated with keys in ascending order, e.g.,
    270      * <code>valueAt(0)</code> will return the value associated with the
    271      * smallest key and <code>valueAt(size()-1)</code> will return the value
    272      * associated with the largest key.</p>
    273      *
    274      * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
    275      * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
    276      * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
    277      * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
    278      */
    279     @SuppressWarnings("unchecked")
    280     public E valueAt(int index) {
    281         if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
    282             // The array might be slightly bigger than mSize, in which case, indexing won't fail.
    283             // Check if exception should be thrown outside of the critical path.
    284             throw new ArrayIndexOutOfBoundsException(index);
    285         }
    286         if (mGarbage) {
    287             gc();
    288         }
    289 
    290         return (E) mValues[index];
    291     }
    292 
    293     /**
    294      * Given an index in the range <code>0...size()-1</code>, sets a new
    295      * value for the <code>index</code>th key-value mapping that this
    296      * LongSparseArray stores.
    297      *
    298      * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
    299      * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
    300      * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
    301      * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
    302      */
    303     public void setValueAt(int index, E value) {
    304         if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
    305             // The array might be slightly bigger than mSize, in which case, indexing won't fail.
    306             // Check if exception should be thrown outside of the critical path.
    307             throw new ArrayIndexOutOfBoundsException(index);
    308         }
    309         if (mGarbage) {
    310             gc();
    311         }
    312 
    313         mValues[index] = value;
    314     }
    315 
    316     /**
    317      * Returns the index for which {@link #keyAt} would return the
    318      * specified key, or a negative number if the specified
    319      * key is not mapped.
    320      */
    321     public int indexOfKey(long key) {
    322         if (mGarbage) {
    323             gc();
    324         }
    325 
    326         return ContainerHelpers.binarySearch(mKeys, mSize, key);
    327     }
    328 
    329     /**
    330      * Returns an index for which {@link #valueAt} would return the
    331      * specified key, or a negative number if no keys map to the
    332      * specified value.
    333      * Beware that this is a linear search, unlike lookups by key,
    334      * and that multiple keys can map to the same value and this will
    335      * find only one of them.
    336      */
    337     public int indexOfValue(E value) {
    338         if (mGarbage) {
    339             gc();
    340         }
    341 
    342         for (int i = 0; i < mSize; i++) {
    343             if (mValues[i] == value) {
    344                 return i;
    345             }
    346         }
    347         return -1;
    348     }
    349 
    350     /**
    351      * Returns an index for which {@link #valueAt} would return the
    352      * specified key, or a negative number if no keys map to the
    353      * specified value.
    354      * <p>Beware that this is a linear search, unlike lookups by key,
    355      * and that multiple keys can map to the same value and this will
    356      * find only one of them.
    357      * <p>Note also that this method uses {@code equals} unlike {@code indexOfValue}.
    358      * @hide
    359      */
    360     public int indexOfValueByValue(E value) {
    361         if (mGarbage) {
    362             gc();
    363         }
    364 
    365         for (int i = 0; i < mSize; i++) {
    366             if (value == null) {
    367                 if (mValues[i] == null) {
    368                     return i;
    369                 }
    370             } else {
    371                 if (value.equals(mValues[i])) {
    372                     return i;
    373                 }
    374             }
    375         }
    376         return -1;
    377     }
    378 
    379     /**
    380      * Removes all key-value mappings from this LongSparseArray.
    381      */
    382     public void clear() {
    383         int n = mSize;
    384         Object[] values = mValues;
    385 
    386         for (int i = 0; i < n; i++) {
    387             values[i] = null;
    388         }
    389 
    390         mSize = 0;
    391         mGarbage = false;
    392     }
    393 
    394     /**
    395      * Puts a key/value pair into the array, optimizing for the case where
    396      * the key is greater than all existing keys in the array.
    397      */
    398     public void append(long key, E value) {
    399         if (mSize != 0 && key <= mKeys[mSize - 1]) {
    400             put(key, value);
    401             return;
    402         }
    403 
    404         if (mGarbage && mSize >= mKeys.length) {
    405             gc();
    406         }
    407 
    408         mKeys = GrowingArrayUtils.append(mKeys, mSize, key);
    409         mValues = GrowingArrayUtils.append(mValues, mSize, value);
    410         mSize++;
    411     }
    412 
    413     /**
    414      * {@inheritDoc}
    415      *
    416      * <p>This implementation composes a string by iterating over its mappings. If
    417      * this map contains itself as a value, the string "(this Map)"
    418      * will appear in its place.
    419      */
    420     @Override
    421     public String toString() {
    422         if (size() <= 0) {
    423             return "{}";
    424         }
    425 
    426         StringBuilder buffer = new StringBuilder(mSize * 28);
    427         buffer.append('{');
    428         for (int i=0; i<mSize; i++) {
    429             if (i > 0) {
    430                 buffer.append(", ");
    431             }
    432             long key = keyAt(i);
    433             buffer.append(key);
    434             buffer.append('=');
    435             Object value = valueAt(i);
    436             if (value != this) {
    437                 buffer.append(value);
    438             } else {
    439                 buffer.append("(this Map)");
    440             }
    441         }
    442         buffer.append('}');
    443         return buffer.toString();
    444     }
    445 }
    446