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