Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright 2013, Google Inc.
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  *     * Redistributions of source code must retain the above copyright
     10  * notice, this list of conditions and the following disclaimer.
     11  *     * Redistributions in binary form must reproduce the above
     12  * copyright notice, this list of conditions and the following disclaimer
     13  * in the documentation and/or other materials provided with the
     14  * distribution.
     15  *     * Neither the name of Google Inc. nor the names of its
     16  * contributors may be used to endorse or promote products derived from
     17  * this software without specific prior written permission.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     30  */
     31 
     32 package org.jf.util;
     33 
     34 import java.util.Arrays;
     35 import java.util.Collections;
     36 import java.util.List;
     37 
     38 /**
     39  * SparseArrays map integers to Objects.  Unlike a normal array of Objects,
     40  * there can be gaps in the indices.  It is intended to be more efficient
     41  * than using a HashMap to map Integers to Objects.
     42  */
     43 public class SparseArray<E> {
     44     private static final Object DELETED = new Object();
     45     private boolean mGarbage = false;
     46 
     47     /**
     48      * Creates a new SparseArray containing no mappings.
     49      */
     50     public SparseArray() {
     51         this(10);
     52     }
     53 
     54     /**
     55      * Creates a new SparseArray containing no mappings that will not
     56      * require any additional memory allocation to store the specified
     57      * number of mappings.
     58      */
     59     public SparseArray(int initialCapacity) {
     60         mKeys = new int[initialCapacity];
     61         mValues = new Object[initialCapacity];
     62         mSize = 0;
     63     }
     64 
     65     /**
     66      * Gets the Object mapped from the specified key, or <code>null</code>
     67      * if no such mapping has been made.
     68      */
     69     public E get(int key) {
     70         return get(key, null);
     71     }
     72 
     73     /**
     74      * Gets the Object mapped from the specified key, or the specified Object
     75      * if no such mapping has been made.
     76      */
     77     public E get(int key, E valueIfKeyNotFound) {
     78         int i = binarySearch(mKeys, 0, mSize, key);
     79 
     80         if (i < 0 || mValues[i] == DELETED) {
     81             return valueIfKeyNotFound;
     82         } else {
     83             return (E) mValues[i];
     84         }
     85     }
     86 
     87     /**
     88      * Removes the mapping from the specified key, if there was any.
     89      */
     90     public void delete(int key) {
     91         int i = binarySearch(mKeys, 0, mSize, key);
     92 
     93         if (i >= 0) {
     94             if (mValues[i] != DELETED) {
     95                 mValues[i] = DELETED;
     96                 mGarbage = true;
     97             }
     98         }
     99     }
    100 
    101     /**
    102      * Alias for {@link #delete(int)}.
    103      */
    104     public void remove(int key) {
    105         delete(key);
    106     }
    107 
    108     private void gc() {
    109         // Log.e("SparseArray", "gc start with " + mSize);
    110 
    111         int n = mSize;
    112         int o = 0;
    113         int[] keys = mKeys;
    114         Object[] values = mValues;
    115 
    116         for (int i = 0; i < n; i++) {
    117             Object val = values[i];
    118 
    119             if (val != DELETED) {
    120                 if (i != o) {
    121                     keys[o] = keys[i];
    122                     values[o] = val;
    123                 }
    124 
    125                 o++;
    126             }
    127         }
    128 
    129         mGarbage = false;
    130         mSize = o;
    131 
    132         // Log.e("SparseArray", "gc end with " + mSize);
    133     }
    134 
    135     /**
    136      * Adds a mapping from the specified key to the specified value,
    137      * replacing the previous mapping from the specified key if there
    138      * was one.
    139      */
    140     public void put(int key, E value) {
    141         int i = binarySearch(mKeys, 0, mSize, key);
    142 
    143         if (i >= 0) {
    144             mValues[i] = value;
    145         } else {
    146             i = ~i;
    147 
    148             if (i < mSize && mValues[i] == DELETED) {
    149                 mKeys[i] = key;
    150                 mValues[i] = value;
    151                 return;
    152             }
    153 
    154             if (mGarbage && mSize >= mKeys.length) {
    155                 gc();
    156 
    157                 // Search again because indices may have changed.
    158                 i = ~binarySearch(mKeys, 0, mSize, key);
    159             }
    160 
    161             if (mSize >= mKeys.length) {
    162                 int n = Math.max(mSize + 1, mKeys.length * 2);
    163 
    164                 int[] nkeys = new int[n];
    165                 Object[] nvalues = new Object[n];
    166 
    167                 // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
    168                 System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
    169                 System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
    170 
    171                 mKeys = nkeys;
    172                 mValues = nvalues;
    173             }
    174 
    175             if (mSize - i != 0) {
    176                 // Log.e("SparseArray", "move " + (mSize - i));
    177                 System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
    178                 System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
    179             }
    180 
    181             mKeys[i] = key;
    182             mValues[i] = value;
    183             mSize++;
    184         }
    185     }
    186 
    187     /**
    188      * Returns the number of key-value mappings that this SparseArray
    189      * currently stores.
    190      */
    191     public int size() {
    192         if (mGarbage) {
    193             gc();
    194         }
    195 
    196         return mSize;
    197     }
    198 
    199     /**
    200      * Given an index in the range <code>0...size()-1</code>, returns
    201      * the key from the <code>index</code>th key-value mapping that this
    202      * SparseArray stores.
    203      */
    204     public int keyAt(int index) {
    205         if (mGarbage) {
    206             gc();
    207         }
    208 
    209         return mKeys[index];
    210     }
    211 
    212     /**
    213      * Given an index in the range <code>0...size()-1</code>, returns
    214      * the value from the <code>index</code>th key-value mapping that this
    215      * SparseArray stores.
    216      */
    217     public E valueAt(int index) {
    218         if (mGarbage) {
    219             gc();
    220         }
    221 
    222         return (E) mValues[index];
    223     }
    224 
    225     /**
    226      * Given an index in the range <code>0...size()-1</code>, sets a new
    227      * value for the <code>index</code>th key-value mapping that this
    228      * SparseArray stores.
    229      */
    230     public void setValueAt(int index, E value) {
    231         if (mGarbage) {
    232             gc();
    233         }
    234 
    235         mValues[index] = value;
    236     }
    237 
    238     /**
    239      * Returns the index for which {@link #keyAt} would return the
    240      * specified key, or a negative number if the specified
    241      * key is not mapped.
    242      */
    243     public int indexOfKey(int key) {
    244         if (mGarbage) {
    245             gc();
    246         }
    247 
    248         return binarySearch(mKeys, 0, mSize, key);
    249     }
    250 
    251     /**
    252      * Returns an index for which {@link #valueAt} would return the
    253      * specified key, or a negative number if no keys map to the
    254      * specified value.
    255      * Beware that this is a linear search, unlike lookups by key,
    256      * and that multiple keys can map to the same value and this will
    257      * find only one of them.
    258      */
    259     public int indexOfValue(E value) {
    260         if (mGarbage) {
    261             gc();
    262         }
    263 
    264         for (int i = 0; i < mSize; i++)
    265             if (mValues[i] == value)
    266                 return i;
    267 
    268         return -1;
    269     }
    270 
    271     /**
    272      * Removes all key-value mappings from this SparseArray.
    273      */
    274     public void clear() {
    275         int n = mSize;
    276         Object[] values = mValues;
    277 
    278         for (int i = 0; i < n; i++) {
    279             values[i] = null;
    280         }
    281 
    282         mSize = 0;
    283         mGarbage = false;
    284     }
    285 
    286     /**
    287      * Puts a key/value pair into the array, optimizing for the case where
    288      * the key is greater than all existing keys in the array.
    289      */
    290     public void append(int key, E value) {
    291         if (mSize != 0 && key <= mKeys[mSize - 1]) {
    292             put(key, value);
    293             return;
    294         }
    295 
    296         if (mGarbage && mSize >= mKeys.length) {
    297             gc();
    298         }
    299 
    300         int pos = mSize;
    301         if (pos >= mKeys.length) {
    302             int n = Math.max(pos + 1, mKeys.length * 2);
    303 
    304             int[] nkeys = new int[n];
    305             Object[] nvalues = new Object[n];
    306 
    307             // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
    308             System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
    309             System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
    310 
    311             mKeys = nkeys;
    312             mValues = nvalues;
    313         }
    314 
    315         mKeys[pos] = key;
    316         mValues[pos] = value;
    317         mSize = pos + 1;
    318     }
    319 
    320     /**
    321      * Increases the size of the underlying storage if needed, to ensure that it can
    322      * hold the specified number of items without having to allocate additional memory
    323      * @param capacity the number of items
    324      */
    325     public void ensureCapacity(int capacity) {
    326         if (mGarbage && mSize >= mKeys.length) {
    327             gc();
    328         }
    329 
    330         if (mKeys.length < capacity) {
    331             int[] nkeys = new int[capacity];
    332             Object[] nvalues = new Object[capacity];
    333 
    334             System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
    335             System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
    336 
    337             mKeys = nkeys;
    338             mValues = nvalues;
    339         }
    340     }
    341 
    342     private static int binarySearch(int[] a, int start, int len, int key) {
    343         int high = start + len, low = start - 1, guess;
    344 
    345         while (high - low > 1) {
    346             guess = (high + low) / 2;
    347 
    348             if (a[guess] < key)
    349                 low = guess;
    350             else
    351                 high = guess;
    352         }
    353 
    354         if (high == start + len)
    355             return ~(start + len);
    356         else if (a[high] == key)
    357             return high;
    358         else
    359             return ~high;
    360     }
    361 
    362     /**
    363      * @return a read-only list of the values in this SparseArray which are in ascending order, based on their
    364      * associated key
    365      */
    366     public List<E> getValues() {
    367         return Collections.unmodifiableList(Arrays.asList((E[])mValues));
    368     }
    369 
    370     private int[] mKeys;
    371     private Object[] mValues;
    372     private int mSize;
    373 }
    374