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 17 package android.util; 18 19 import android.annotation.NonNull; 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 import com.android.internal.util.function.LongObjPredicate; 26 27 import java.util.Arrays; 28 import java.util.Objects; 29 30 /** 31 * SparseArray mapping longs to Objects. Unlike a normal array of Objects, 32 * there can be gaps in the indices. It is intended to be more memory efficient 33 * than using a HashMap to map Longs to Objects, both because it avoids 34 * auto-boxing keys and its data structure doesn't rely on an extra entry object 35 * for each mapping. 36 * 37 * <p>Note that this container keeps its mappings in an array data structure, 38 * using a binary search to find keys. The implementation is not intended to be appropriate for 39 * data structures 40 * that may contain large numbers of items. It is generally slower than a traditional 41 * HashMap, since lookups require a binary search and adds and removes require inserting 42 * and deleting entries in the array. For containers holding up to hundreds of items, 43 * the performance difference is not significant, less than 50%.</p> 44 * 45 * <p>To help with performance, the container includes an optimization when removing 46 * keys: instead of compacting its array immediately, it leaves the removed entry marked 47 * as deleted. The entry can then be re-used for the same key, or compacted later in 48 * a single garbage collection step of all removed entries. This garbage collection will 49 * need to be performed at any time the array needs to be grown or the the map size or 50 * entry values are retrieved.</p> 51 * 52 * <p>It is possible to iterate over the items in this container using 53 * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using 54 * <code>keyAt(int)</code> with ascending values of the index will return the 55 * keys in ascending order, or the values corresponding to the keys in ascending 56 * order in the case of <code>valueAt(int)</code>.</p> 57 */ 58 @android.ravenwood.annotation.RavenwoodKeepWholeClass 59 public class LongSparseArray<E> implements Cloneable { 60 private static final Object DELETED = new Object(); 61 private boolean mGarbage = false; 62 63 private long[] mKeys; 64 private Object[] mValues; 65 private int mSize; 66 67 /** 68 * Creates a new LongSparseArray containing no mappings. 69 */ LongSparseArray()70 public LongSparseArray() { 71 this(10); 72 } 73 74 /** 75 * Creates a new LongSparseArray containing no mappings that will not 76 * require any additional memory allocation to store the specified 77 * number of mappings. If you supply an initial capacity of 0, the 78 * sparse array will be initialized with a light-weight representation 79 * not requiring any additional array allocations. 80 */ LongSparseArray(int initialCapacity)81 public LongSparseArray(int initialCapacity) { 82 if (initialCapacity == 0) { 83 mKeys = EmptyArray.LONG; 84 mValues = EmptyArray.OBJECT; 85 } else { 86 mKeys = ArrayUtils.newUnpaddedLongArray(initialCapacity); 87 mValues = ArrayUtils.newUnpaddedObjectArray(initialCapacity); 88 } 89 mSize = 0; 90 } 91 92 @Override 93 @SuppressWarnings("unchecked") clone()94 public LongSparseArray<E> clone() { 95 LongSparseArray<E> clone = null; 96 try { 97 clone = (LongSparseArray<E>) super.clone(); 98 clone.mKeys = mKeys.clone(); 99 clone.mValues = mValues.clone(); 100 } catch (CloneNotSupportedException cnse) { 101 /* ignore */ 102 } 103 return clone; 104 } 105 106 /** 107 * Gets the Object mapped from the specified key, or <code>null</code> 108 * if no such mapping has been made. 109 */ get(long key)110 public E get(long key) { 111 return get(key, null); 112 } 113 114 /** 115 * Gets the Object mapped from the specified key, or the specified Object 116 * if no such mapping has been made. 117 */ 118 @SuppressWarnings("unchecked") get(long key, E valueIfKeyNotFound)119 public E get(long key, E valueIfKeyNotFound) { 120 int i = ContainerHelpers.binarySearch(mKeys, mSize, key); 121 122 if (i < 0 || mValues[i] == DELETED) { 123 return valueIfKeyNotFound; 124 } else { 125 return (E) mValues[i]; 126 } 127 } 128 129 /** 130 * Removes the mapping from the specified key, if there was any. 131 */ delete(long key)132 public void delete(long key) { 133 int i = ContainerHelpers.binarySearch(mKeys, mSize, key); 134 135 if (i >= 0) { 136 if (mValues[i] != DELETED) { 137 mValues[i] = DELETED; 138 mGarbage = true; 139 } 140 } 141 } 142 143 /** 144 * Alias for {@link #delete(long)}. 145 */ remove(long key)146 public void remove(long key) { 147 delete(key); 148 } 149 150 /** @hide */ 151 @SuppressWarnings("unchecked") removeIf(@onNull LongObjPredicate<? super E> filter)152 public void removeIf(@NonNull LongObjPredicate<? super E> filter) { 153 Objects.requireNonNull(filter); 154 for (int i = 0; i < mSize; ++i) { 155 if (mValues[i] != DELETED && filter.test(mKeys[i], (E) mValues[i])) { 156 mValues[i] = DELETED; 157 mGarbage = true; 158 } 159 } 160 } 161 162 /** 163 * Removes the mapping at the specified index. 164 * 165 * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for 166 * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an 167 * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting 168 * {@link android.os.Build.VERSION_CODES#Q} and later.</p> 169 */ removeAt(int index)170 public void removeAt(int index) { 171 if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) { 172 // The array might be slightly bigger than mSize, in which case, indexing won't fail. 173 // Check if exception should be thrown outside of the critical path. 174 throw new ArrayIndexOutOfBoundsException(index); 175 } 176 if (mValues[index] != DELETED) { 177 mValues[index] = DELETED; 178 mGarbage = true; 179 } 180 } 181 gc()182 private void gc() { 183 // Log.e("SparseArray", "gc start with " + mSize); 184 185 int n = mSize; 186 int o = 0; 187 long[] keys = mKeys; 188 Object[] values = mValues; 189 190 for (int i = 0; i < n; i++) { 191 Object val = values[i]; 192 193 if (val != DELETED) { 194 if (i != o) { 195 keys[o] = keys[i]; 196 values[o] = val; 197 values[i] = null; 198 } 199 200 o++; 201 } 202 } 203 204 mGarbage = false; 205 mSize = o; 206 207 // Log.e("SparseArray", "gc end with " + mSize); 208 } 209 210 /** 211 * Returns the index of the first element whose key is greater than or equal to the given key. 212 * 213 * @param key The key for which to search the array. 214 * @return The smallest {@code index} for which {@code (keyAt(index) >= key)} is 215 * {@code true}, or {@link #size() size} if no such {@code index} exists. 216 * @hide 217 */ firstIndexOnOrAfter(long key)218 public int firstIndexOnOrAfter(long key) { 219 if (mGarbage) { 220 gc(); 221 } 222 final int index = Arrays.binarySearch(mKeys, 0, size(), key); 223 return (index >= 0) ? index : -index - 1; 224 } 225 226 /** 227 * Returns the index of the last element whose key is less than or equal to the given key. 228 * 229 * @param key The key for which to search the array. 230 * @return The largest {@code index} for which {@code (keyAt(index) <= key)} is 231 * {@code true}, or {@code -1} if no such {@code index} exists. 232 * @hide 233 */ lastIndexOnOrBefore(long key)234 public int lastIndexOnOrBefore(long key) { 235 final int index = firstIndexOnOrAfter(key); 236 237 if (index < size() && keyAt(index) == key) { 238 return index; 239 } 240 return index - 1; 241 } 242 243 /** 244 * Adds a mapping from the specified key to the specified value, 245 * replacing the previous mapping from the specified key if there 246 * was one. 247 */ put(long key, E value)248 public void put(long key, E value) { 249 int i = ContainerHelpers.binarySearch(mKeys, mSize, key); 250 251 if (i >= 0) { 252 mValues[i] = value; 253 } else { 254 i = ~i; 255 256 if (i < mSize && mValues[i] == DELETED) { 257 mKeys[i] = key; 258 mValues[i] = value; 259 return; 260 } 261 262 if (mGarbage && mSize >= mKeys.length) { 263 gc(); 264 265 // Search again because indices may have changed. 266 i = ~ContainerHelpers.binarySearch(mKeys, mSize, key); 267 } 268 269 mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key); 270 mValues = GrowingArrayUtils.insert(mValues, mSize, i, value); 271 mSize++; 272 } 273 } 274 275 /** 276 * Returns the number of key-value mappings that this LongSparseArray 277 * currently stores. 278 */ size()279 public int size() { 280 if (mGarbage) { 281 gc(); 282 } 283 284 return mSize; 285 } 286 287 /** 288 * Given an index in the range <code>0...size()-1</code>, returns 289 * the key from the <code>index</code>th key-value mapping that this 290 * LongSparseArray stores. 291 * 292 * <p>The keys corresponding to indices in ascending order are guaranteed to 293 * be in ascending order, e.g., <code>keyAt(0)</code> will return the 294 * smallest key and <code>keyAt(size()-1)</code> will return the largest 295 * key.</p> 296 * 297 * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for 298 * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an 299 * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting 300 * {@link android.os.Build.VERSION_CODES#Q} and later.</p> 301 */ keyAt(int index)302 public long keyAt(int index) { 303 if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) { 304 // The array might be slightly bigger than mSize, in which case, indexing won't fail. 305 // Check if exception should be thrown outside of the critical path. 306 throw new ArrayIndexOutOfBoundsException(index); 307 } 308 if (mGarbage) { 309 gc(); 310 } 311 312 return mKeys[index]; 313 } 314 315 /** 316 * Given an index in the range <code>0...size()-1</code>, returns 317 * the value from the <code>index</code>th key-value mapping that this 318 * LongSparseArray stores. 319 * 320 * <p>The values corresponding to indices in ascending order are guaranteed 321 * to be associated with keys in ascending order, e.g., 322 * <code>valueAt(0)</code> will return the value associated with the 323 * smallest key and <code>valueAt(size()-1)</code> will return the value 324 * associated with the largest key.</p> 325 * 326 * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for 327 * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an 328 * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting 329 * {@link android.os.Build.VERSION_CODES#Q} and later.</p> 330 */ 331 @SuppressWarnings("unchecked") valueAt(int index)332 public E valueAt(int index) { 333 if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) { 334 // The array might be slightly bigger than mSize, in which case, indexing won't fail. 335 // Check if exception should be thrown outside of the critical path. 336 throw new ArrayIndexOutOfBoundsException(index); 337 } 338 if (mGarbage) { 339 gc(); 340 } 341 342 return (E) mValues[index]; 343 } 344 345 /** 346 * Given an index in the range <code>0...size()-1</code>, sets a new 347 * value for the <code>index</code>th key-value mapping that this 348 * LongSparseArray stores. 349 * 350 * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for 351 * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an 352 * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting 353 * {@link android.os.Build.VERSION_CODES#Q} and later.</p> 354 */ setValueAt(int index, E value)355 public void setValueAt(int index, E value) { 356 if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) { 357 // The array might be slightly bigger than mSize, in which case, indexing won't fail. 358 // Check if exception should be thrown outside of the critical path. 359 throw new ArrayIndexOutOfBoundsException(index); 360 } 361 if (mGarbage) { 362 gc(); 363 } 364 365 mValues[index] = value; 366 } 367 368 /** 369 * Returns the index for which {@link #keyAt} would return the 370 * specified key, or a negative number if the specified 371 * key is not mapped. 372 */ indexOfKey(long key)373 public int indexOfKey(long key) { 374 if (mGarbage) { 375 gc(); 376 } 377 378 return ContainerHelpers.binarySearch(mKeys, mSize, key); 379 } 380 381 /** 382 * Returns an index for which {@link #valueAt} would return the 383 * specified key, or a negative number if no keys map to the 384 * specified value. 385 * Beware that this is a linear search, unlike lookups by key, 386 * and that multiple keys can map to the same value and this will 387 * find only one of them. 388 */ indexOfValue(E value)389 public int indexOfValue(E value) { 390 if (mGarbage) { 391 gc(); 392 } 393 394 for (int i = 0; i < mSize; i++) { 395 if (mValues[i] == value) { 396 return i; 397 } 398 } 399 return -1; 400 } 401 402 /** 403 * Returns an index for which {@link #valueAt} would return the 404 * specified key, or a negative number if no keys map to the 405 * specified value. 406 * <p>Beware that this is a linear search, unlike lookups by key, 407 * and that multiple keys can map to the same value and this will 408 * find only one of them. 409 * <p>Note also that this method uses {@code equals} unlike {@code indexOfValue}. 410 * @hide 411 */ indexOfValueByValue(E value)412 public int indexOfValueByValue(E value) { 413 if (mGarbage) { 414 gc(); 415 } 416 417 for (int i = 0; i < mSize; i++) { 418 if (value == null) { 419 if (mValues[i] == null) { 420 return i; 421 } 422 } else { 423 if (value.equals(mValues[i])) { 424 return i; 425 } 426 } 427 } 428 return -1; 429 } 430 431 /** 432 * Removes all key-value mappings from this LongSparseArray. 433 */ clear()434 public void clear() { 435 int n = mSize; 436 Object[] values = mValues; 437 438 for (int i = 0; i < n; i++) { 439 values[i] = null; 440 } 441 442 mSize = 0; 443 mGarbage = false; 444 } 445 446 /** 447 * Puts a key/value pair into the array, optimizing for the case where 448 * the key is greater than all existing keys in the array. 449 */ append(long key, E value)450 public void append(long key, E value) { 451 if (mSize != 0 && key <= mKeys[mSize - 1]) { 452 put(key, value); 453 return; 454 } 455 456 if (mGarbage && mSize >= mKeys.length) { 457 gc(); 458 } 459 460 mKeys = GrowingArrayUtils.append(mKeys, mSize, key); 461 mValues = GrowingArrayUtils.append(mValues, mSize, value); 462 mSize++; 463 } 464 465 /** 466 * {@inheritDoc} 467 * 468 * <p>This implementation composes a string by iterating over its mappings. If 469 * this map contains itself as a value, the string "(this Map)" 470 * will appear in its place. 471 */ 472 @Override toString()473 public String toString() { 474 if (size() <= 0) { 475 return "{}"; 476 } 477 478 StringBuilder buffer = new StringBuilder(mSize * 28); 479 buffer.append('{'); 480 for (int i=0; i<mSize; i++) { 481 if (i > 0) { 482 buffer.append(", "); 483 } 484 long key = keyAt(i); 485 buffer.append(key); 486 buffer.append('='); 487 Object value = valueAt(i); 488 if (value != this) { 489 buffer.append(value); 490 } else { 491 buffer.append("(this Map)"); 492 } 493 } 494 buffer.append('}'); 495 return buffer.toString(); 496 } 497 498 /** 499 * @hide 500 */ 501 public static class StringParcelling implements 502 com.android.internal.util.Parcelling<LongSparseArray<String>> { 503 @Override parcel(LongSparseArray<String> array, Parcel dest, int parcelFlags)504 public void parcel(LongSparseArray<String> array, Parcel dest, int parcelFlags) { 505 if (array == null) { 506 dest.writeInt(-1); 507 return; 508 } 509 510 int size = array.mSize; 511 512 dest.writeInt(size); 513 dest.writeLongArray(array.mKeys); 514 515 dest.writeStringArray(Arrays.copyOfRange(array.mValues, 0, size, String[].class)); 516 } 517 518 @Override unparcel(Parcel source)519 public LongSparseArray<String> unparcel(Parcel source) { 520 int size = source.readInt(); 521 if (size == -1) { 522 return null; 523 } 524 525 LongSparseArray<String> array = new LongSparseArray<>(0); 526 array.mSize = size; 527 array.mKeys = source.createLongArray(); 528 array.mValues = source.createStringArray(); 529 530 // Make sure array is valid 531 Preconditions.checkArgument(array.mKeys.length >= size); 532 Preconditions.checkArgument(array.mValues.length >= size); 533 534 if (size > 0) { 535 long last = array.mKeys[0]; 536 for (int i = 1; i < size; i++) { 537 Preconditions.checkArgument(last < array.mKeys[i]); 538 } 539 } 540 541 return array; 542 } 543 } 544 } 545