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