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 
     21 /**
     22  * SparseArrays map integers to Objects.  Unlike a normal array of Objects,
     23  * there can be gaps in the indices.  It is intended to be more efficient
     24  * than using a HashMap to map Integers to Objects.
     25  */
     26 public class SparseArray<E> implements Cloneable {
     27     private static final Object DELETED = new Object();
     28     private boolean mGarbage = false;
     29 
     30     private int[] mKeys;
     31     private Object[] mValues;
     32     private int mSize;
     33 
     34     /**
     35      * Creates a new SparseArray containing no mappings.
     36      */
     37     public SparseArray() {
     38         this(10);
     39     }
     40 
     41     /**
     42      * Creates a new SparseArray containing no mappings that will not
     43      * require any additional memory allocation to store the specified
     44      * number of mappings.
     45      */
     46     public SparseArray(int initialCapacity) {
     47         initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity);
     48 
     49         mKeys = new int[initialCapacity];
     50         mValues = new Object[initialCapacity];
     51         mSize = 0;
     52     }
     53 
     54     @Override
     55     @SuppressWarnings("unchecked")
     56     public SparseArray<E> clone() {
     57         SparseArray<E> clone = null;
     58         try {
     59             clone = (SparseArray<E>) super.clone();
     60             clone.mKeys = mKeys.clone();
     61             clone.mValues = mValues.clone();
     62         } catch (CloneNotSupportedException cnse) {
     63             /* ignore */
     64         }
     65         return clone;
     66     }
     67 
     68     /**
     69      * Gets the Object mapped from the specified key, or <code>null</code>
     70      * if no such mapping has been made.
     71      */
     72     public E get(int key) {
     73         return get(key, null);
     74     }
     75 
     76     /**
     77      * Gets the Object mapped from the specified key, or the specified Object
     78      * if no such mapping has been made.
     79      */
     80     @SuppressWarnings("unchecked")
     81     public E get(int key, E valueIfKeyNotFound) {
     82         int i = binarySearch(mKeys, 0, mSize, key);
     83 
     84         if (i < 0 || mValues[i] == DELETED) {
     85             return valueIfKeyNotFound;
     86         } else {
     87             return (E) mValues[i];
     88         }
     89     }
     90 
     91     /**
     92      * Removes the mapping from the specified key, if there was any.
     93      */
     94     public void delete(int key) {
     95         int i = binarySearch(mKeys, 0, mSize, key);
     96 
     97         if (i >= 0) {
     98             if (mValues[i] != DELETED) {
     99                 mValues[i] = DELETED;
    100                 mGarbage = true;
    101             }
    102         }
    103     }
    104 
    105     /**
    106      * Alias for {@link #delete(int)}.
    107      */
    108     public void remove(int key) {
    109         delete(key);
    110     }
    111 
    112     /**
    113      * Removes the mapping at the specified index.
    114      */
    115     public void removeAt(int index) {
    116         if (mValues[index] != DELETED) {
    117             mValues[index] = DELETED;
    118             mGarbage = true;
    119         }
    120     }
    121 
    122     private void gc() {
    123         // Log.e("SparseArray", "gc start with " + mSize);
    124 
    125         int n = mSize;
    126         int o = 0;
    127         int[] keys = mKeys;
    128         Object[] values = mValues;
    129 
    130         for (int i = 0; i < n; i++) {
    131             Object val = values[i];
    132 
    133             if (val != DELETED) {
    134                 if (i != o) {
    135                     keys[o] = keys[i];
    136                     values[o] = val;
    137                 }
    138 
    139                 o++;
    140             }
    141         }
    142 
    143         mGarbage = false;
    144         mSize = o;
    145 
    146         // Log.e("SparseArray", "gc end with " + mSize);
    147     }
    148 
    149     /**
    150      * Adds a mapping from the specified key to the specified value,
    151      * replacing the previous mapping from the specified key if there
    152      * was one.
    153      */
    154     public void put(int key, E value) {
    155         int i = binarySearch(mKeys, 0, mSize, key);
    156 
    157         if (i >= 0) {
    158             mValues[i] = value;
    159         } else {
    160             i = ~i;
    161 
    162             if (i < mSize && mValues[i] == DELETED) {
    163                 mKeys[i] = key;
    164                 mValues[i] = value;
    165                 return;
    166             }
    167 
    168             if (mGarbage && mSize >= mKeys.length) {
    169                 gc();
    170 
    171                 // Search again because indices may have changed.
    172                 i = ~binarySearch(mKeys, 0, mSize, key);
    173             }
    174 
    175             if (mSize >= mKeys.length) {
    176                 int n = ArrayUtils.idealIntArraySize(mSize + 1);
    177 
    178                 int[] nkeys = new int[n];
    179                 Object[] nvalues = new Object[n];
    180 
    181                 // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
    182                 System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
    183                 System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
    184 
    185                 mKeys = nkeys;
    186                 mValues = nvalues;
    187             }
    188 
    189             if (mSize - i != 0) {
    190                 // Log.e("SparseArray", "move " + (mSize - i));
    191                 System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
    192                 System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
    193             }
    194 
    195             mKeys[i] = key;
    196             mValues[i] = value;
    197             mSize++;
    198         }
    199     }
    200 
    201     /**
    202      * Returns the number of key-value mappings that this SparseArray
    203      * currently stores.
    204      */
    205     public int size() {
    206         if (mGarbage) {
    207             gc();
    208         }
    209 
    210         return mSize;
    211     }
    212 
    213     /**
    214      * Given an index in the range <code>0...size()-1</code>, returns
    215      * the key from the <code>index</code>th key-value mapping that this
    216      * SparseArray stores.
    217      */
    218     public int keyAt(int index) {
    219         if (mGarbage) {
    220             gc();
    221         }
    222 
    223         return mKeys[index];
    224     }
    225 
    226     /**
    227      * Given an index in the range <code>0...size()-1</code>, returns
    228      * the value from the <code>index</code>th key-value mapping that this
    229      * SparseArray stores.
    230      */
    231     @SuppressWarnings("unchecked")
    232     public E valueAt(int index) {
    233         if (mGarbage) {
    234             gc();
    235         }
    236 
    237         return (E) mValues[index];
    238     }
    239 
    240     /**
    241      * Given an index in the range <code>0...size()-1</code>, sets a new
    242      * value for the <code>index</code>th key-value mapping that this
    243      * SparseArray stores.
    244      */
    245     public void setValueAt(int index, E value) {
    246         if (mGarbage) {
    247             gc();
    248         }
    249 
    250         mValues[index] = value;
    251     }
    252 
    253     /**
    254      * Returns the index for which {@link #keyAt} would return the
    255      * specified key, or a negative number if the specified
    256      * key is not mapped.
    257      */
    258     public int indexOfKey(int key) {
    259         if (mGarbage) {
    260             gc();
    261         }
    262 
    263         return binarySearch(mKeys, 0, mSize, key);
    264     }
    265 
    266     /**
    267      * Returns an index for which {@link #valueAt} would return the
    268      * specified key, or a negative number if no keys map to the
    269      * specified value.
    270      * Beware that this is a linear search, unlike lookups by key,
    271      * and that multiple keys can map to the same value and this will
    272      * find only one of them.
    273      */
    274     public int indexOfValue(E value) {
    275         if (mGarbage) {
    276             gc();
    277         }
    278 
    279         for (int i = 0; i < mSize; i++)
    280             if (mValues[i] == value)
    281                 return i;
    282 
    283         return -1;
    284     }
    285 
    286     /**
    287      * Removes all key-value mappings from this SparseArray.
    288      */
    289     public void clear() {
    290         int n = mSize;
    291         Object[] values = mValues;
    292 
    293         for (int i = 0; i < n; i++) {
    294             values[i] = null;
    295         }
    296 
    297         mSize = 0;
    298         mGarbage = false;
    299     }
    300 
    301     /**
    302      * Puts a key/value pair into the array, optimizing for the case where
    303      * the key is greater than all existing keys in the array.
    304      */
    305     public void append(int key, E value) {
    306         if (mSize != 0 && key <= mKeys[mSize - 1]) {
    307             put(key, value);
    308             return;
    309         }
    310 
    311         if (mGarbage && mSize >= mKeys.length) {
    312             gc();
    313         }
    314 
    315         int pos = mSize;
    316         if (pos >= mKeys.length) {
    317             int n = ArrayUtils.idealIntArraySize(pos + 1);
    318 
    319             int[] nkeys = new int[n];
    320             Object[] nvalues = new Object[n];
    321 
    322             // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
    323             System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
    324             System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
    325 
    326             mKeys = nkeys;
    327             mValues = nvalues;
    328         }
    329 
    330         mKeys[pos] = key;
    331         mValues[pos] = value;
    332         mSize = pos + 1;
    333     }
    334 
    335     private static int binarySearch(int[] a, int start, int len, int key) {
    336         int high = start + len, low = start - 1, guess;
    337 
    338         while (high - low > 1) {
    339             guess = (high + low) / 2;
    340 
    341             if (a[guess] < key)
    342                 low = guess;
    343             else
    344                 high = guess;
    345         }
    346 
    347         if (high == start + len)
    348             return ~(start + len);
    349         else if (a[high] == key)
    350             return high;
    351         else
    352             return ~high;
    353     }
    354 }
    355