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 com.android.internal.util.ArrayUtils; 20 21 /** 22 * SparseArray mapping longs to Objects. Unlike a normal array of Objects, 23 * there can be gaps in the indices. It is intended to be more memory efficient 24 * than using a HashMap to map Longs to Objects, both because it avoids 25 * auto-boxing keys and its data structure doesn't rely on an extra entry object 26 * for each mapping. 27 * 28 * <p>Note that this container keeps its mappings in an array data structure, 29 * using a binary search to find keys. The implementation is not intended to be appropriate for 30 * data structures 31 * that may contain large numbers of items. It is generally slower than a traditional 32 * HashMap, since lookups require a binary search and adds and removes require inserting 33 * and deleting entries in the array. For containers holding up to hundreds of items, 34 * the performance difference is not significant, less than 50%.</p> 35 * 36 * <p>To help with performance, the container includes an optimization when removing 37 * keys: instead of compacting its array immediately, it leaves the removed entry marked 38 * as deleted. The entry can then be re-used for the same key, or compacted later in 39 * a single garbage collection step of all removed entries. This garbage collection will 40 * need to be performed at any time the array needs to be grown or the the map size or 41 * entry values are retrieved.</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 public class LongSparseArray<E> implements Cloneable { 50 private static final Object DELETED = new Object(); 51 private boolean mGarbage = false; 52 53 private long[] mKeys; 54 private Object[] mValues; 55 private int mSize; 56 57 /** 58 * Creates a new LongSparseArray containing no mappings. 59 */ 60 public LongSparseArray() { 61 this(10); 62 } 63 64 /** 65 * Creates a new LongSparseArray containing no mappings that will not 66 * require any additional memory allocation to store the specified 67 * number of mappings. If you supply an initial capacity of 0, the 68 * sparse array will be initialized with a light-weight representation 69 * not requiring any additional array allocations. 70 */ 71 public LongSparseArray(int initialCapacity) { 72 if (initialCapacity == 0) { 73 mKeys = ContainerHelpers.EMPTY_LONGS; 74 mValues = ContainerHelpers.EMPTY_OBJECTS; 75 } else { 76 initialCapacity = ArrayUtils.idealLongArraySize(initialCapacity); 77 mKeys = new long[initialCapacity]; 78 mValues = new Object[initialCapacity]; 79 } 80 mSize = 0; 81 } 82 83 @Override 84 @SuppressWarnings("unchecked") 85 public LongSparseArray<E> clone() { 86 LongSparseArray<E> clone = null; 87 try { 88 clone = (LongSparseArray<E>) 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 Object mapped from the specified key, or <code>null</code> 99 * if no such mapping has been made. 100 */ 101 public E get(long key) { 102 return get(key, null); 103 } 104 105 /** 106 * Gets the Object mapped from the specified key, or the specified Object 107 * if no such mapping has been made. 108 */ 109 @SuppressWarnings("unchecked") 110 public E get(long key, E valueIfKeyNotFound) { 111 int i = ContainerHelpers.binarySearch(mKeys, mSize, key); 112 113 if (i < 0 || mValues[i] == DELETED) { 114 return valueIfKeyNotFound; 115 } else { 116 return (E) mValues[i]; 117 } 118 } 119 120 /** 121 * Removes the mapping from the specified key, if there was any. 122 */ 123 public void delete(long key) { 124 int i = ContainerHelpers.binarySearch(mKeys, mSize, key); 125 126 if (i >= 0) { 127 if (mValues[i] != DELETED) { 128 mValues[i] = DELETED; 129 mGarbage = true; 130 } 131 } 132 } 133 134 /** 135 * Alias for {@link #delete(long)}. 136 */ 137 public void remove(long key) { 138 delete(key); 139 } 140 141 /** 142 * Removes the mapping at the specified index. 143 */ 144 public void removeAt(int index) { 145 if (mValues[index] != DELETED) { 146 mValues[index] = DELETED; 147 mGarbage = true; 148 } 149 } 150 151 private void gc() { 152 // Log.e("SparseArray", "gc start with " + mSize); 153 154 int n = mSize; 155 int o = 0; 156 long[] keys = mKeys; 157 Object[] values = mValues; 158 159 for (int i = 0; i < n; i++) { 160 Object val = values[i]; 161 162 if (val != DELETED) { 163 if (i != o) { 164 keys[o] = keys[i]; 165 values[o] = val; 166 values[i] = null; 167 } 168 169 o++; 170 } 171 } 172 173 mGarbage = false; 174 mSize = o; 175 176 // Log.e("SparseArray", "gc end with " + mSize); 177 } 178 179 /** 180 * Adds a mapping from the specified key to the specified value, 181 * replacing the previous mapping from the specified key if there 182 * was one. 183 */ 184 public void put(long key, E value) { 185 int i = ContainerHelpers.binarySearch(mKeys, mSize, key); 186 187 if (i >= 0) { 188 mValues[i] = value; 189 } else { 190 i = ~i; 191 192 if (i < mSize && mValues[i] == DELETED) { 193 mKeys[i] = key; 194 mValues[i] = value; 195 return; 196 } 197 198 if (mGarbage && mSize >= mKeys.length) { 199 gc(); 200 201 // Search again because indices may have changed. 202 i = ~ContainerHelpers.binarySearch(mKeys, mSize, key); 203 } 204 205 if (mSize >= mKeys.length) { 206 int n = ArrayUtils.idealLongArraySize(mSize + 1); 207 208 long[] nkeys = new long[n]; 209 Object[] nvalues = new Object[n]; 210 211 // Log.e("SparseArray", "grow " + mKeys.length + " to " + n); 212 System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length); 213 System.arraycopy(mValues, 0, nvalues, 0, mValues.length); 214 215 mKeys = nkeys; 216 mValues = nvalues; 217 } 218 219 if (mSize - i != 0) { 220 // Log.e("SparseArray", "move " + (mSize - i)); 221 System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i); 222 System.arraycopy(mValues, i, mValues, i + 1, mSize - i); 223 } 224 225 mKeys[i] = key; 226 mValues[i] = value; 227 mSize++; 228 } 229 } 230 231 /** 232 * Returns the number of key-value mappings that this LongSparseArray 233 * currently stores. 234 */ 235 public int size() { 236 if (mGarbage) { 237 gc(); 238 } 239 240 return mSize; 241 } 242 243 /** 244 * Given an index in the range <code>0...size()-1</code>, returns 245 * the key from the <code>index</code>th key-value mapping that this 246 * LongSparseArray stores. 247 * 248 * <p>The keys corresponding to indices in ascending order are guaranteed to 249 * be in ascending order, e.g., <code>keyAt(0)</code> will return the 250 * smallest key and <code>keyAt(size()-1)</code> will return the largest 251 * key.</p> 252 */ 253 public long keyAt(int index) { 254 if (mGarbage) { 255 gc(); 256 } 257 258 return mKeys[index]; 259 } 260 261 /** 262 * Given an index in the range <code>0...size()-1</code>, returns 263 * the value from the <code>index</code>th key-value mapping that this 264 * LongSparseArray stores. 265 * 266 * <p>The values corresponding to indices in ascending order are guaranteed 267 * to be associated with keys in ascending order, e.g., 268 * <code>valueAt(0)</code> will return the value associated with the 269 * smallest key and <code>valueAt(size()-1)</code> will return the value 270 * associated with the largest key.</p> 271 */ 272 @SuppressWarnings("unchecked") 273 public E valueAt(int index) { 274 if (mGarbage) { 275 gc(); 276 } 277 278 return (E) mValues[index]; 279 } 280 281 /** 282 * Given an index in the range <code>0...size()-1</code>, sets a new 283 * value for the <code>index</code>th key-value mapping that this 284 * LongSparseArray stores. 285 */ 286 public void setValueAt(int index, E value) { 287 if (mGarbage) { 288 gc(); 289 } 290 291 mValues[index] = value; 292 } 293 294 /** 295 * Returns the index for which {@link #keyAt} would return the 296 * specified key, or a negative number if the specified 297 * key is not mapped. 298 */ 299 public int indexOfKey(long key) { 300 if (mGarbage) { 301 gc(); 302 } 303 304 return ContainerHelpers.binarySearch(mKeys, mSize, key); 305 } 306 307 /** 308 * Returns an index for which {@link #valueAt} would return the 309 * specified key, or a negative number if no keys map to the 310 * specified value. 311 * Beware that this is a linear search, unlike lookups by key, 312 * and that multiple keys can map to the same value and this will 313 * find only one of them. 314 */ 315 public int indexOfValue(E value) { 316 if (mGarbage) { 317 gc(); 318 } 319 320 for (int i = 0; i < mSize; i++) 321 if (mValues[i] == value) 322 return i; 323 324 return -1; 325 } 326 327 /** 328 * Removes all key-value mappings from this LongSparseArray. 329 */ 330 public void clear() { 331 int n = mSize; 332 Object[] values = mValues; 333 334 for (int i = 0; i < n; i++) { 335 values[i] = null; 336 } 337 338 mSize = 0; 339 mGarbage = false; 340 } 341 342 /** 343 * Puts a key/value pair into the array, optimizing for the case where 344 * the key is greater than all existing keys in the array. 345 */ 346 public void append(long key, E value) { 347 if (mSize != 0 && key <= mKeys[mSize - 1]) { 348 put(key, value); 349 return; 350 } 351 352 if (mGarbage && mSize >= mKeys.length) { 353 gc(); 354 } 355 356 int pos = mSize; 357 if (pos >= mKeys.length) { 358 int n = ArrayUtils.idealLongArraySize(pos + 1); 359 360 long[] nkeys = new long[n]; 361 Object[] nvalues = new Object[n]; 362 363 // Log.e("SparseArray", "grow " + mKeys.length + " to " + n); 364 System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length); 365 System.arraycopy(mValues, 0, nvalues, 0, mValues.length); 366 367 mKeys = nkeys; 368 mValues = nvalues; 369 } 370 371 mKeys[pos] = key; 372 mValues[pos] = value; 373 mSize = pos + 1; 374 } 375 376 /** 377 * {@inheritDoc} 378 * 379 * <p>This implementation composes a string by iterating over its mappings. If 380 * this map contains itself as a value, the string "(this Map)" 381 * will appear in its place. 382 */ 383 @Override 384 public String toString() { 385 if (size() <= 0) { 386 return "{}"; 387 } 388 389 StringBuilder buffer = new StringBuilder(mSize * 28); 390 buffer.append('{'); 391 for (int i=0; i<mSize; i++) { 392 if (i > 0) { 393 buffer.append(", "); 394 } 395 long key = keyAt(i); 396 buffer.append(key); 397 buffer.append('='); 398 Object value = valueAt(i); 399 if (value != this) { 400 buffer.append(value); 401 } else { 402 buffer.append("(this Map)"); 403 } 404 } 405 buffer.append('}'); 406 return buffer.toString(); 407 } 408 } 409