Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (C) 2006 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  * SparseArrays map integers 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 Integers 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 SparseArray<E> implements Cloneable {
     53     private static final Object DELETED = new Object();
     54     private boolean mGarbage = false;
     55 
     56     private int[] mKeys;
     57     private Object[] mValues;
     58     private int mSize;
     59 
     60     /**
     61      * Creates a new SparseArray containing no mappings.
     62      */
     63     public SparseArray() {
     64         this(10);
     65     }
     66 
     67     /**
     68      * Creates a new SparseArray 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 SparseArray(int initialCapacity) {
     75         if (initialCapacity == 0) {
     76             mKeys = EmptyArray.INT;
     77             mValues = EmptyArray.OBJECT;
     78         } else {
     79             mValues = ArrayUtils.newUnpaddedObjectArray(initialCapacity);
     80             mKeys = new int[mValues.length];
     81         }
     82         mSize = 0;
     83     }
     84 
     85     @Override
     86     @SuppressWarnings("unchecked")
     87     public SparseArray<E> clone() {
     88         SparseArray<E> clone = null;
     89         try {
     90             clone = (SparseArray<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(int 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(int 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(int 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      * @hide
    138      * Removes the mapping from the specified key, if there was any, returning the old value.
    139      */
    140     public E removeReturnOld(int key) {
    141         int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
    142 
    143         if (i >= 0) {
    144             if (mValues[i] != DELETED) {
    145                 final E old = (E) mValues[i];
    146                 mValues[i] = DELETED;
    147                 mGarbage = true;
    148                 return old;
    149             }
    150         }
    151         return null;
    152     }
    153 
    154     /**
    155      * Alias for {@link #delete(int)}.
    156      */
    157     public void remove(int key) {
    158         delete(key);
    159     }
    160 
    161     /**
    162      * Removes the mapping at the specified index.
    163      *
    164      * <p>For indices outside of the range <code>0...size()-1</code>,
    165      * the behavior is undefined.</p>
    166      */
    167     public void removeAt(int index) {
    168         if (mValues[index] != DELETED) {
    169             mValues[index] = DELETED;
    170             mGarbage = true;
    171         }
    172     }
    173 
    174     /**
    175      * Remove a range of mappings as a batch.
    176      *
    177      * @param index Index to begin at
    178      * @param size Number of mappings to remove
    179      *
    180      * <p>For indices outside of the range <code>0...size()-1</code>,
    181      * the behavior is undefined.</p>
    182      */
    183     public void removeAtRange(int index, int size) {
    184         final int end = Math.min(mSize, index + size);
    185         for (int i = index; i < end; i++) {
    186             removeAt(i);
    187         }
    188     }
    189 
    190     private void gc() {
    191         // Log.e("SparseArray", "gc start with " + mSize);
    192 
    193         int n = mSize;
    194         int o = 0;
    195         int[] keys = mKeys;
    196         Object[] values = mValues;
    197 
    198         for (int i = 0; i < n; i++) {
    199             Object val = values[i];
    200 
    201             if (val != DELETED) {
    202                 if (i != o) {
    203                     keys[o] = keys[i];
    204                     values[o] = val;
    205                     values[i] = null;
    206                 }
    207 
    208                 o++;
    209             }
    210         }
    211 
    212         mGarbage = false;
    213         mSize = o;
    214 
    215         // Log.e("SparseArray", "gc end with " + mSize);
    216     }
    217 
    218     /**
    219      * Adds a mapping from the specified key to the specified value,
    220      * replacing the previous mapping from the specified key if there
    221      * was one.
    222      */
    223     public void put(int key, E value) {
    224         int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
    225 
    226         if (i >= 0) {
    227             mValues[i] = value;
    228         } else {
    229             i = ~i;
    230 
    231             if (i < mSize && mValues[i] == DELETED) {
    232                 mKeys[i] = key;
    233                 mValues[i] = value;
    234                 return;
    235             }
    236 
    237             if (mGarbage && mSize >= mKeys.length) {
    238                 gc();
    239 
    240                 // Search again because indices may have changed.
    241                 i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
    242             }
    243 
    244             mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
    245             mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
    246             mSize++;
    247         }
    248     }
    249 
    250     /**
    251      * Returns the number of key-value mappings that this SparseArray
    252      * currently stores.
    253      */
    254     public int size() {
    255         if (mGarbage) {
    256             gc();
    257         }
    258 
    259         return mSize;
    260     }
    261 
    262     /**
    263      * Given an index in the range <code>0...size()-1</code>, returns
    264      * the key from the <code>index</code>th key-value mapping that this
    265      * SparseArray stores.
    266      *
    267      * <p>The keys corresponding to indices in ascending order are guaranteed to
    268      * be in ascending order, e.g., <code>keyAt(0)</code> will return the
    269      * smallest key and <code>keyAt(size()-1)</code> will return the largest
    270      * key.</p>
    271      *
    272      * <p>For indices outside of the range <code>0...size()-1</code>,
    273      * the behavior is undefined.</p>
    274      */
    275     public int keyAt(int index) {
    276         if (mGarbage) {
    277             gc();
    278         }
    279 
    280         return mKeys[index];
    281     }
    282 
    283     /**
    284      * Given an index in the range <code>0...size()-1</code>, returns
    285      * the value from the <code>index</code>th key-value mapping that this
    286      * SparseArray stores.
    287      *
    288      * <p>The values corresponding to indices in ascending order are guaranteed
    289      * to be associated with keys in ascending order, e.g.,
    290      * <code>valueAt(0)</code> will return the value associated with the
    291      * smallest key and <code>valueAt(size()-1)</code> will return the value
    292      * associated with the largest key.</p>
    293      *
    294      * <p>For indices outside of the range <code>0...size()-1</code>,
    295      * the behavior is undefined.</p>
    296      */
    297     @SuppressWarnings("unchecked")
    298     public E valueAt(int index) {
    299         if (mGarbage) {
    300             gc();
    301         }
    302 
    303         return (E) mValues[index];
    304     }
    305 
    306     /**
    307      * Given an index in the range <code>0...size()-1</code>, sets a new
    308      * value for the <code>index</code>th key-value mapping that this
    309      * SparseArray stores.
    310      *
    311      * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined.</p>
    312      */
    313     public void setValueAt(int index, E value) {
    314         if (mGarbage) {
    315             gc();
    316         }
    317 
    318         mValues[index] = value;
    319     }
    320 
    321     /**
    322      * Returns the index for which {@link #keyAt} would return the
    323      * specified key, or a negative number if the specified
    324      * key is not mapped.
    325      */
    326     public int indexOfKey(int key) {
    327         if (mGarbage) {
    328             gc();
    329         }
    330 
    331         return ContainerHelpers.binarySearch(mKeys, mSize, key);
    332     }
    333 
    334     /**
    335      * Returns an index for which {@link #valueAt} would return the
    336      * specified key, or a negative number if no keys map to the
    337      * specified value.
    338      * <p>Beware that this is a linear search, unlike lookups by key,
    339      * and that multiple keys can map to the same value and this will
    340      * find only one of them.
    341      * <p>Note also that unlike most collections' {@code indexOf} methods,
    342      * this method compares values using {@code ==} rather than {@code equals}.
    343      */
    344     public int indexOfValue(E value) {
    345         if (mGarbage) {
    346             gc();
    347         }
    348 
    349         for (int i = 0; i < mSize; i++) {
    350             if (mValues[i] == value) {
    351                 return i;
    352             }
    353         }
    354 
    355         return -1;
    356     }
    357 
    358     /**
    359      * Returns an index for which {@link #valueAt} would return the
    360      * specified key, or a negative number if no keys map to the
    361      * specified value.
    362      * <p>Beware that this is a linear search, unlike lookups by key,
    363      * and that multiple keys can map to the same value and this will
    364      * find only one of them.
    365      * <p>Note also that this method uses {@code equals} unlike {@code indexOfValue}.
    366      * @hide
    367      */
    368     public int indexOfValueByValue(E value) {
    369         if (mGarbage) {
    370             gc();
    371         }
    372 
    373         for (int i = 0; i < mSize; i++) {
    374             if (value == null) {
    375                 if (mValues[i] == null) {
    376                     return i;
    377                 }
    378             } else {
    379                 if (value.equals(mValues[i])) {
    380                     return i;
    381                 }
    382             }
    383         }
    384         return -1;
    385     }
    386 
    387     /**
    388      * Removes all key-value mappings from this SparseArray.
    389      */
    390     public void clear() {
    391         int n = mSize;
    392         Object[] values = mValues;
    393 
    394         for (int i = 0; i < n; i++) {
    395             values[i] = null;
    396         }
    397 
    398         mSize = 0;
    399         mGarbage = false;
    400     }
    401 
    402     /**
    403      * Puts a key/value pair into the array, optimizing for the case where
    404      * the key is greater than all existing keys in the array.
    405      */
    406     public void append(int key, E value) {
    407         if (mSize != 0 && key <= mKeys[mSize - 1]) {
    408             put(key, value);
    409             return;
    410         }
    411 
    412         if (mGarbage && mSize >= mKeys.length) {
    413             gc();
    414         }
    415 
    416         mKeys = GrowingArrayUtils.append(mKeys, mSize, key);
    417         mValues = GrowingArrayUtils.append(mValues, mSize, value);
    418         mSize++;
    419     }
    420 
    421     /**
    422      * {@inheritDoc}
    423      *
    424      * <p>This implementation composes a string by iterating over its mappings. If
    425      * this map contains itself as a value, the string "(this Map)"
    426      * will appear in its place.
    427      */
    428     @Override
    429     public String toString() {
    430         if (size() <= 0) {
    431             return "{}";
    432         }
    433 
    434         StringBuilder buffer = new StringBuilder(mSize * 28);
    435         buffer.append('{');
    436         for (int i=0; i<mSize; i++) {
    437             if (i > 0) {
    438                 buffer.append(", ");
    439             }
    440             int key = keyAt(i);
    441             buffer.append(key);
    442             buffer.append('=');
    443             Object value = valueAt(i);
    444             if (value != this) {
    445                 buffer.append(value);
    446             } else {
    447                 buffer.append("(this Map)");
    448             }
    449         }
    450         buffer.append('}');
    451         return buffer.toString();
    452     }
    453 }
    454