• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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 /**
23  * SparseLongArrays map integers to longs.  Unlike a normal array of longs,
24  * there can be gaps in the indices.  It is intended to be more memory efficient
25  * than using a HashMap to map Integers to Longs, both because it avoids
26  * auto-boxing keys and values and its data structure doesn't rely on an extra entry object
27  * for each mapping.
28  *
29  * <p>Note that this container keeps its mappings in an array data structure,
30  * using a binary search to find keys.  The implementation is not intended to be appropriate for
31  * data structures
32  * that may contain large numbers of items.  It is generally slower than a traditional
33  * HashMap, since lookups require a binary search and adds and removes require inserting
34  * and deleting entries in the array.  For containers holding up to hundreds of items,
35  * the performance difference is not significant, less than 50%.</p>
36  *
37  * <p>It is possible to iterate over the items in this container using
38  * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using
39  * <code>keyAt(int)</code> with ascending values of the index will return the
40  * keys in ascending order, or the values corresponding to the keys in ascending
41  * order in the case of <code>valueAt(int)</code>.</p>
42  */
43 @android.ravenwood.annotation.RavenwoodKeepWholeClass
44 public class SparseLongArray implements Cloneable {
45     private int[] mKeys;
46     private long[] mValues;
47     private int mSize;
48 
49     /**
50      * Creates a new SparseLongArray containing no mappings.
51      */
SparseLongArray()52     public SparseLongArray() {
53         this(0);
54     }
55 
56     /**
57      * Creates a new SparseLongArray containing no mappings that will not
58      * require any additional memory allocation to store the specified
59      * number of mappings.  If you supply an initial capacity of 0, the
60      * sparse array will be initialized with a light-weight representation
61      * not requiring any additional array allocations.
62      */
SparseLongArray(int initialCapacity)63     public SparseLongArray(int initialCapacity) {
64         if (initialCapacity == 0) {
65             mKeys = EmptyArray.INT;
66             mValues = EmptyArray.LONG;
67         } else {
68             mValues = ArrayUtils.newUnpaddedLongArray(initialCapacity);
69             mKeys = new int[mValues.length];
70         }
71         mSize = 0;
72     }
73 
74     @Override
clone()75     public SparseLongArray clone() {
76         SparseLongArray clone = null;
77         try {
78             clone = (SparseLongArray) super.clone();
79             clone.mKeys = mKeys.clone();
80             clone.mValues = mValues.clone();
81         } catch (CloneNotSupportedException cnse) {
82             /* ignore */
83         }
84         return clone;
85     }
86 
87     /**
88      * Gets the long mapped from the specified key, or <code>0</code>
89      * if no such mapping has been made.
90      */
get(int key)91     public long get(int key) {
92         return get(key, 0);
93     }
94 
95     /**
96      * Gets the long mapped from the specified key, or the specified value
97      * if no such mapping has been made.
98      */
get(int key, long valueIfKeyNotFound)99     public long get(int key, long valueIfKeyNotFound) {
100         int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
101 
102         if (i < 0) {
103             return valueIfKeyNotFound;
104         } else {
105             return mValues[i];
106         }
107     }
108 
109     /**
110      * Removes the mapping from the specified key, if there was any.
111      */
delete(int key)112     public void delete(int key) {
113         int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
114 
115         if (i >= 0) {
116             removeAt(i);
117         }
118     }
119 
120     /**
121      * @hide
122      * Remove a range of mappings as a batch.
123      *
124      * @param index Index to begin at
125      * @param size Number of mappings to remove
126      *
127      * <p>For indices outside of the range <code>0...size()-1</code>,
128      * the behavior is undefined.</p>
129      */
removeAtRange(int index, int size)130     public void removeAtRange(int index, int size) {
131         size = Math.min(size, mSize - index);
132         System.arraycopy(mKeys, index + size, mKeys, index, mSize - (index + size));
133         System.arraycopy(mValues, index + size, mValues, index, mSize - (index + size));
134         mSize -= size;
135     }
136 
137     /**
138      * Removes the mapping at the given index.
139      */
removeAt(int index)140     public void removeAt(int index) {
141         System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1));
142         System.arraycopy(mValues, index + 1, mValues, index, mSize - (index + 1));
143         mSize--;
144     }
145 
146     /**
147      * Adds a mapping from the specified key to the specified value,
148      * replacing the previous mapping from the specified key if there
149      * was one.
150      */
put(int key, long value)151     public void put(int key, long value) {
152         int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
153 
154         if (i >= 0) {
155             mValues[i] = value;
156         } else {
157             i = ~i;
158 
159             mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
160             mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
161             mSize++;
162         }
163     }
164 
165     /**
166      * Adds a mapping from the specified key to the specified value,
167      * <b>adding</b> its value to the previous mapping from the specified key if there
168      * was one.
169      *
170      * <p>This differs from {@link #put} because instead of replacing any previous value, it adds
171      * (in the numerical sense) to it.
172      *
173      * @hide
174      */
incrementValue(int key, long summand)175     public void incrementValue(int key, long summand) {
176         int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
177 
178         if (i >= 0) {
179             mValues[i] += summand;
180         } else {
181             i = ~i;
182 
183             mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
184             mValues = GrowingArrayUtils.insert(mValues, mSize, i, summand);
185             mSize++;
186         }
187     }
188 
189     /**
190      * Returns the number of key-value mappings that this SparseLongArray
191      * currently stores.
192      */
size()193     public int size() {
194         return mSize;
195     }
196 
197     /**
198      * Given an index in the range <code>0...size()-1</code>, returns
199      * the key from the <code>index</code>th key-value mapping that this
200      * SparseLongArray stores.
201      *
202      * <p>The keys corresponding to indices in ascending order are guaranteed to
203      * be in ascending order, e.g., <code>keyAt(0)</code> will return the
204      * smallest key and <code>keyAt(size()-1)</code> will return the largest
205      * key.</p>
206      *
207      * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
208      * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
209      * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
210      * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
211      */
keyAt(int index)212     public int keyAt(int index) {
213         if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
214             // The array might be slightly bigger than mSize, in which case, indexing won't fail.
215             // Check if exception should be thrown outside of the critical path.
216             throw new ArrayIndexOutOfBoundsException(index);
217         }
218         return mKeys[index];
219     }
220 
221     /**
222      * Given an index in the range <code>0...size()-1</code>, returns
223      * the value from the <code>index</code>th key-value mapping that this
224      * SparseLongArray stores.
225      *
226      * <p>The values corresponding to indices in ascending order are guaranteed
227      * to be associated with keys in ascending order, e.g.,
228      * <code>valueAt(0)</code> will return the value associated with the
229      * smallest key and <code>valueAt(size()-1)</code> will return the value
230      * associated with the largest key.</p>
231      *
232      * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
233      * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
234      * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
235      * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
236      */
valueAt(int index)237     public long valueAt(int index) {
238         if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
239             // The array might be slightly bigger than mSize, in which case, indexing won't fail.
240             // Check if exception should be thrown outside of the critical path.
241             throw new ArrayIndexOutOfBoundsException(index);
242         }
243         return mValues[index];
244     }
245 
246     /**
247      * Given an index in the range <code>0...size()-1</code>, sets a new
248      * value for the <code>index</code>th key-value mapping that this
249      * SparseLongArray stores.
250      *
251      * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
252      * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
253      * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
254      * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
255      *
256      * @hide
257      */
setValueAt(int index, long value)258     public void setValueAt(int index, long value) {
259         if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
260             // The array might be slightly bigger than mSize, in which case, indexing won't fail.
261             // Check if exception should be thrown outside of the critical path.
262             throw new ArrayIndexOutOfBoundsException(index);
263         }
264 
265         mValues[index] = value;
266     }
267 
268     /**
269      * Returns the index for which {@link #keyAt} would return the
270      * specified key, or a negative number if the specified
271      * key is not mapped.
272      */
indexOfKey(int key)273     public int indexOfKey(int key) {
274         return ContainerHelpers.binarySearch(mKeys, mSize, key);
275     }
276 
277     /**
278      * Returns an index for which {@link #valueAt} would return the
279      * specified key, or a negative number if no keys map to the
280      * specified value.
281      * Beware that this is a linear search, unlike lookups by key,
282      * and that multiple keys can map to the same value and this will
283      * find only one of them.
284      */
indexOfValue(long value)285     public int indexOfValue(long value) {
286         for (int i = 0; i < mSize; i++)
287             if (mValues[i] == value)
288                 return i;
289 
290         return -1;
291     }
292 
293     /**
294      * Removes all key-value mappings from this SparseLongArray.
295      */
clear()296     public void clear() {
297         mSize = 0;
298     }
299 
300     /**
301      * Puts a key/value pair into the array, optimizing for the case where
302      * the key is greater than all existing keys in the array.
303      */
append(int key, long value)304     public void append(int key, long value) {
305         if (mSize != 0 && key <= mKeys[mSize - 1]) {
306             put(key, value);
307             return;
308         }
309 
310         mKeys = GrowingArrayUtils.append(mKeys, mSize, key);
311         mValues = GrowingArrayUtils.append(mValues, mSize, value);
312         mSize++;
313     }
314 
315     /**
316      * {@inheritDoc}
317      *
318      * <p>This implementation composes a string by iterating over its mappings.
319      */
320     @Override
toString()321     public String toString() {
322         if (size() <= 0) {
323             return "{}";
324         }
325 
326         StringBuilder buffer = new StringBuilder(mSize * 28);
327         buffer.append('{');
328         for (int i=0; i<mSize; i++) {
329             if (i > 0) {
330                 buffer.append(", ");
331             }
332             int key = keyAt(i);
333             buffer.append(key);
334             buffer.append('=');
335             long value = valueAt(i);
336             buffer.append(value);
337         }
338         buffer.append('}');
339         return buffer.toString();
340     }
341 }
342