1/* 2 * Copyright 2013, Google Inc. 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * * Redistributions in binary form must reproduce the above 12 * copyright notice, this list of conditions and the following disclaimer 13 * in the documentation and/or other materials provided with the 14 * distribution. 15 * * Neither the name of Google Inc. nor the names of its 16 * contributors may be used to endorse or promote products derived from 17 * this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32package org.jf.util; 33 34import java.util.Arrays; 35import java.util.Collections; 36import java.util.List; 37 38/** 39 * SparseArrays map integers to Objects. Unlike a normal array of Objects, 40 * there can be gaps in the indices. It is intended to be more efficient 41 * than using a HashMap to map Integers to Objects. 42 */ 43public class SparseArray<E> { 44 private static final Object DELETED = new Object(); 45 private boolean mGarbage = false; 46 47 /** 48 * Creates a new SparseArray containing no mappings. 49 */ 50 public SparseArray() { 51 this(10); 52 } 53 54 /** 55 * Creates a new SparseArray containing no mappings that will not 56 * require any additional memory allocation to store the specified 57 * number of mappings. 58 */ 59 public SparseArray(int initialCapacity) { 60 mKeys = new int[initialCapacity]; 61 mValues = new Object[initialCapacity]; 62 mSize = 0; 63 } 64 65 /** 66 * Gets the Object mapped from the specified key, or <code>null</code> 67 * if no such mapping has been made. 68 */ 69 public E get(int key) { 70 return get(key, null); 71 } 72 73 /** 74 * Gets the Object mapped from the specified key, or the specified Object 75 * if no such mapping has been made. 76 */ 77 public E get(int key, E valueIfKeyNotFound) { 78 int i = binarySearch(mKeys, 0, mSize, key); 79 80 if (i < 0 || mValues[i] == DELETED) { 81 return valueIfKeyNotFound; 82 } else { 83 return (E) mValues[i]; 84 } 85 } 86 87 /** 88 * Removes the mapping from the specified key, if there was any. 89 */ 90 public void delete(int key) { 91 int i = binarySearch(mKeys, 0, mSize, key); 92 93 if (i >= 0) { 94 if (mValues[i] != DELETED) { 95 mValues[i] = DELETED; 96 mGarbage = true; 97 } 98 } 99 } 100 101 /** 102 * Alias for {@link #delete(int)}. 103 */ 104 public void remove(int key) { 105 delete(key); 106 } 107 108 private void gc() { 109 // Log.e("SparseArray", "gc start with " + mSize); 110 111 int n = mSize; 112 int o = 0; 113 int[] keys = mKeys; 114 Object[] values = mValues; 115 116 for (int i = 0; i < n; i++) { 117 Object val = values[i]; 118 119 if (val != DELETED) { 120 if (i != o) { 121 keys[o] = keys[i]; 122 values[o] = val; 123 } 124 125 o++; 126 } 127 } 128 129 mGarbage = false; 130 mSize = o; 131 132 // Log.e("SparseArray", "gc end with " + mSize); 133 } 134 135 /** 136 * Adds a mapping from the specified key to the specified value, 137 * replacing the previous mapping from the specified key if there 138 * was one. 139 */ 140 public void put(int key, E value) { 141 int i = binarySearch(mKeys, 0, mSize, key); 142 143 if (i >= 0) { 144 mValues[i] = value; 145 } else { 146 i = ~i; 147 148 if (i < mSize && mValues[i] == DELETED) { 149 mKeys[i] = key; 150 mValues[i] = value; 151 return; 152 } 153 154 if (mGarbage && mSize >= mKeys.length) { 155 gc(); 156 157 // Search again because indices may have changed. 158 i = ~binarySearch(mKeys, 0, mSize, key); 159 } 160 161 if (mSize >= mKeys.length) { 162 int n = Math.max(mSize + 1, mKeys.length * 2); 163 164 int[] nkeys = new int[n]; 165 Object[] nvalues = new Object[n]; 166 167 // Log.e("SparseArray", "grow " + mKeys.length + " to " + n); 168 System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length); 169 System.arraycopy(mValues, 0, nvalues, 0, mValues.length); 170 171 mKeys = nkeys; 172 mValues = nvalues; 173 } 174 175 if (mSize - i != 0) { 176 // Log.e("SparseArray", "move " + (mSize - i)); 177 System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i); 178 System.arraycopy(mValues, i, mValues, i + 1, mSize - i); 179 } 180 181 mKeys[i] = key; 182 mValues[i] = value; 183 mSize++; 184 } 185 } 186 187 /** 188 * Returns the number of key-value mappings that this SparseArray 189 * currently stores. 190 */ 191 public int size() { 192 if (mGarbage) { 193 gc(); 194 } 195 196 return mSize; 197 } 198 199 /** 200 * Given an index in the range <code>0...size()-1</code>, returns 201 * the key from the <code>index</code>th key-value mapping that this 202 * SparseArray stores. 203 */ 204 public int keyAt(int index) { 205 if (mGarbage) { 206 gc(); 207 } 208 209 return mKeys[index]; 210 } 211 212 /** 213 * Given an index in the range <code>0...size()-1</code>, returns 214 * the value from the <code>index</code>th key-value mapping that this 215 * SparseArray stores. 216 */ 217 public E valueAt(int index) { 218 if (mGarbage) { 219 gc(); 220 } 221 222 return (E) mValues[index]; 223 } 224 225 /** 226 * Given an index in the range <code>0...size()-1</code>, sets a new 227 * value for the <code>index</code>th key-value mapping that this 228 * SparseArray stores. 229 */ 230 public void setValueAt(int index, E value) { 231 if (mGarbage) { 232 gc(); 233 } 234 235 mValues[index] = value; 236 } 237 238 /** 239 * Returns the index for which {@link #keyAt} would return the 240 * specified key, or a negative number if the specified 241 * key is not mapped. 242 */ 243 public int indexOfKey(int key) { 244 if (mGarbage) { 245 gc(); 246 } 247 248 return binarySearch(mKeys, 0, mSize, key); 249 } 250 251 /** 252 * Returns an index for which {@link #valueAt} would return the 253 * specified key, or a negative number if no keys map to the 254 * specified value. 255 * Beware that this is a linear search, unlike lookups by key, 256 * and that multiple keys can map to the same value and this will 257 * find only one of them. 258 */ 259 public int indexOfValue(E value) { 260 if (mGarbage) { 261 gc(); 262 } 263 264 for (int i = 0; i < mSize; i++) 265 if (mValues[i] == value) 266 return i; 267 268 return -1; 269 } 270 271 /** 272 * Removes all key-value mappings from this SparseArray. 273 */ 274 public void clear() { 275 int n = mSize; 276 Object[] values = mValues; 277 278 for (int i = 0; i < n; i++) { 279 values[i] = null; 280 } 281 282 mSize = 0; 283 mGarbage = false; 284 } 285 286 /** 287 * Puts a key/value pair into the array, optimizing for the case where 288 * the key is greater than all existing keys in the array. 289 */ 290 public void append(int key, E value) { 291 if (mSize != 0 && key <= mKeys[mSize - 1]) { 292 put(key, value); 293 return; 294 } 295 296 if (mGarbage && mSize >= mKeys.length) { 297 gc(); 298 } 299 300 int pos = mSize; 301 if (pos >= mKeys.length) { 302 int n = Math.max(pos + 1, mKeys.length * 2); 303 304 int[] nkeys = new int[n]; 305 Object[] nvalues = new Object[n]; 306 307 // Log.e("SparseArray", "grow " + mKeys.length + " to " + n); 308 System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length); 309 System.arraycopy(mValues, 0, nvalues, 0, mValues.length); 310 311 mKeys = nkeys; 312 mValues = nvalues; 313 } 314 315 mKeys[pos] = key; 316 mValues[pos] = value; 317 mSize = pos + 1; 318 } 319 320 /** 321 * Increases the size of the underlying storage if needed, to ensure that it can 322 * hold the specified number of items without having to allocate additional memory 323 * @param capacity the number of items 324 */ 325 public void ensureCapacity(int capacity) { 326 if (mGarbage && mSize >= mKeys.length) { 327 gc(); 328 } 329 330 if (mKeys.length < capacity) { 331 int[] nkeys = new int[capacity]; 332 Object[] nvalues = new Object[capacity]; 333 334 System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length); 335 System.arraycopy(mValues, 0, nvalues, 0, mValues.length); 336 337 mKeys = nkeys; 338 mValues = nvalues; 339 } 340 } 341 342 private static int binarySearch(int[] a, int start, int len, int key) { 343 int high = start + len, low = start - 1, guess; 344 345 while (high - low > 1) { 346 guess = (high + low) / 2; 347 348 if (a[guess] < key) 349 low = guess; 350 else 351 high = guess; 352 } 353 354 if (high == start + len) 355 return ~(start + len); 356 else if (a[high] == key) 357 return high; 358 else 359 return ~high; 360 } 361 362 /** 363 * @return a read-only list of the values in this SparseArray which are in ascending order, based on their 364 * associated key 365 */ 366 public List<E> getValues() { 367 return Collections.unmodifiableList(Arrays.asList((E[])mValues)); 368 } 369 370 private int[] mKeys; 371 private Object[] mValues; 372 private int mSize; 373} 374