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