由于网上有朋友对于这个问题已经有了很详细的研究,所以我就不班门弄斧了:
转载于:http://android-performance.com/android/2014/02/10/android-sparsearray-vs-hashmap.html
http://liuzhichao.com/p/832.html
http://www.codes51.com/article/detail_163576.html
源码:
1 /* 2 * Copyright (C) 2006 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 package android.util; 17 import com.android.internal.util.ArrayUtils; 18 import com.android.internal.util.GrowingArrayUtils; 19 import libcore.util.EmptyArray; 20 /** 21 * SparseArrays map integers to Objects. Unlike a normal array of Objects, 22 * there can be gaps in the indices. It is intended to be more memory efficient 23 * than using a HashMap to map Integers to Objects, both because it avoids 24 * auto-boxing keys and its data structure doesn't rely on an extra entry object 25 * for each mapping. 26 * 27 * <p>Note that this container keeps its mappings in an array data structure, 28 * using a binary search to find keys. The implementation is not intended to be appropriate for 29 * data structures 30 * that may contain large numbers of items. It is generally slower than a traditional 31 * HashMap, since lookups require a binary search and adds and removes require inserting 32 * and deleting entries in the array. For containers holding up to hundreds of items, 33 * the performance difference is not significant, less than 50%.</p> 34 * 35 * <p>To help with performance, the container includes an optimization when removing 36 * keys: instead of compacting its array immediately, it leaves the removed entry marked 37 * as deleted. The entry can then be re-used for the same key, or compacted later in 38 * a single garbage collection step of all removed entries. This garbage collection will 39 * need to be performed at any time the array needs to be grown or the the map size or 40 * entry values are retrieved.</p> 41 * 42 * <p>It is possible to iterate over the items in this container using 43 * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using 44 * <code>keyAt(int)</code> with ascending values of the index will return the 45 * keys in ascending order, or the values corresponding to the keys in ascending 46 * order in the case of <code>valueAt(int)</code>.</p> 47 */ 48 public class SparseArray<E> implements Cloneable { 49 private static final Object DELETED = new Object(); 50 private boolean mGarbage = false; 51 private int[] mKeys; 52 private Object[] mValues; 53 private int mSize; 54 /** 55 * Creates a new SparseArray containing no mappings. 56 */ 57 public SparseArray() { 58 this(10); 59 } 60 /** 61 * Creates a new SparseArray containing no mappings that will not 62 * require any additional memory allocation to store the specified 63 * number of mappings. If you supply an initial capacity of 0, the 64 * sparse array will be initialized with a light-weight representation 65 * not requiring any additional array allocations. 66 */ 67 public SparseArray(int initialCapacity) { 68 if (initialCapacity == 0) { 69 mKeys = EmptyArray.INT; 70 mValues = EmptyArray.OBJECT; 71 } else { 72 mValues = ArrayUtils.newUnpaddedObjectArray(initialCapacity); 73 mKeys = new int[mValues.length]; 74 } 75 mSize = 0; 76 } 77 @Override 78 @SuppressWarnings("unchecked") 79 public SparseArray<E> clone() { 80 SparseArray<E> clone = null; 81 try { 82 clone = (SparseArray<E>) super.clone(); 83 clone.mKeys = mKeys.clone(); 84 clone.mValues = mValues.clone(); 85 } catch (CloneNotSupportedException cnse) { 86 /* ignore */ 87 } 88 return clone; 89 } 90 /** 91 * Gets the Object mapped from the specified key, or <code>null</code> 92 * if no such mapping has been made. 93 */ 94 public E get(int key) { 95 return get(key, null); 96 } 97 /** 98 * Gets the Object mapped from the specified key, or the specified Object 99 * if no such mapping has been made. 100 */ 101 @SuppressWarnings("unchecked") 102 public E get(int key, E valueIfKeyNotFound) { 103 int i = ContainerHelpers.binarySearch(mKeys, mSize, key); 104 if (i < 0 || mValues[i] == DELETED) { 105 return valueIfKeyNotFound; 106 } else { 107 return (E) mValues[i]; 108 } 109 } 110 /** 111 * Removes the mapping from the specified key, if there was any. 112 */ 113 public void delete(int key) { 114 int i = ContainerHelpers.binarySearch(mKeys, mSize, key); 115 if (i >= 0) { 116 if (mValues[i] != DELETED) { 117 mValues[i] = DELETED; 118 mGarbage = true; 119 } 120 } 121 } 122 /** 123 * @hide 124 * Removes the mapping from the specified key, if there was any, returning the old value. 125 */ 126 public E removeReturnOld(int key) { 127 int i = ContainerHelpers.binarySearch(mKeys, mSize, key); 128 if (i >= 0) { 129 if (mValues[i] != DELETED) { 130 final E old = (E) mValues[i]; 131 mValues[i] = DELETED; 132 mGarbage = true; 133 return old; 134 } 135 } 136 return null; 137 } 138 /** 139 * Alias for {@link #delete(int)}. 140 */ 141 public void remove(int key) { 142 delete(key); 143 } 144 /** 145 * Removes the mapping at the specified index. 146 */ 147 public void removeAt(int index) { 148 if (mValues[index] != DELETED) { 149 mValues[index] = DELETED; 150 mGarbage = true; 151 } 152 } 153 /** 154 * Remove a range of mappings as a batch. 155 * 156 * @param index Index to begin at 157 * @param size Number of mappings to remove 158 */ 159 public void removeAtRange(int index, int size) { 160 final int end = Math.min(mSize, index + size); 161 for (int i = index; i < end; i++) { 162 removeAt(i); 163 } 164 } 165 private void gc() { 166 // Log.e("SparseArray", "gc start with " + mSize); 167 int n = mSize; 168 int o = 0; 169 int[] keys = mKeys; 170 Object[] values = mValues; 171 for (int i = 0; i < n; i++) { 172 Object val = values[i]; 173 if (val != DELETED) { 174 if (i != o) { 175 keys[o] = keys[i]; 176 values[o] = val; 177 values[i] = null; 178 } 179 o++; 180 } 181 } 182 mGarbage = false; 183 mSize = o; 184 // Log.e("SparseArray", "gc end with " + mSize); 185 } 186 /** 187 * Adds a mapping from the specified key to the specified value, 188 * replacing the previous mapping from the specified key if there 189 * was one. 190 */ 191 public void put(int key, E value) { 192 int i = ContainerHelpers.binarySearch(mKeys, mSize, key); 193 if (i >= 0) { 194 mValues[i] = value; 195 } else { 196 i = ~i; 197 if (i < mSize && mValues[i] == DELETED) { 198 mKeys[i] = key; 199 mValues[i] = value; 200 return; 201 } 202 if (mGarbage && mSize >= mKeys.length) { 203 gc(); 204 // Search again because indices may have changed. 205 i = ~ContainerHelpers.binarySearch(mKeys, mSize, key); 206 } 207 mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key); 208 mValues = GrowingArrayUtils.insert(mValues, mSize, i, value); 209 mSize++; 210 } 211 } 212 /** 213 * Returns the number of key-value mappings that this SparseArray 214 * currently stores. 215 */ 216 public int size() { 217 if (mGarbage) { 218 gc(); 219 } 220 return mSize; 221 } 222 /** 223 * Given an index in the range <code>0...size()-1</code>, returns 224 * the key from the <code>index</code>th key-value mapping that this 225 * SparseArray stores. 226 * 227 * <p>The keys corresponding to indices in ascending order are guaranteed to 228 * be in ascending order, e.g., <code>keyAt(0)</code> will return the 229 * smallest key and <code>keyAt(size()-1)</code> will return the largest 230 * key.</p> 231 */ 232 public int keyAt(int index) { 233 if (mGarbage) { 234 gc(); 235 } 236 return mKeys[index]; 237 } 238 /** 239 * Given an index in the range <code>0...size()-1</code>, returns 240 * the value from the <code>index</code>th key-value mapping that this 241 * SparseArray stores. 242 * 243 * <p>The values corresponding to indices in ascending order are guaranteed 244 * to be associated with keys in ascending order, e.g., 245 * <code>valueAt(0)</code> will return the value associated with the 246 * smallest key and <code>valueAt(size()-1)</code> will return the value 247 * associated with the largest key.</p> 248 */ 249 @SuppressWarnings("unchecked") 250 public E valueAt(int index) { 251 if (mGarbage) { 252 gc(); 253 } 254 return (E) mValues[index]; 255 } 256 /** 257 * Given an index in the range <code>0...size()-1</code>, sets a new 258 * value for the <code>index</code>th key-value mapping that this 259 * SparseArray stores. 260 */ 261 public void setValueAt(int index, E value) { 262 if (mGarbage) { 263 gc(); 264 } 265 mValues[index] = value; 266 } 267 /** 268 * Returns the index for which {@link #keyAt} would return the 269 * specified key, or a negative number if the specified 270 * key is not mapped. 271 */ 272 public int indexOfKey(int key) { 273 if (mGarbage) { 274 gc(); 275 } 276 return ContainerHelpers.binarySearch(mKeys, mSize, key); 277 } 278 /** 279 * Returns an index for which {@link #valueAt} would return the 280 * specified key, or a negative number if no keys map to the 281 * specified value. 282 * <p>Beware that this is a linear search, unlike lookups by key, 283 * and that multiple keys can map to the same value and this will 284 * find only one of them. 285 * <p>Note also that unlike most collections' {@code indexOf} methods, 286 * this method compares values using {@code ==} rather than {@code equals}. 287 */ 288 public int indexOfValue(E value) { 289 if (mGarbage) { 290 gc(); 291 } 292 for (int i = 0; i < mSize; i++) 293 if (mValues[i] == value) 294 return i; 295 return -1; 296 } 297 /** 298 * Removes all key-value mappings from this SparseArray. 299 */ 300 public void clear() { 301 int n = mSize; 302 Object[] values = mValues; 303 for (int i = 0; i < n; i++) { 304 values[i] = null; 305 } 306 mSize = 0; 307 mGarbage = false; 308 } 309 /** 310 * Puts a key/value pair into the array, optimizing for the case where 311 * the key is greater than all existing keys in the array. 312 */ 313 public void append(int key, E value) { 314 if (mSize != 0 && key <= mKeys[mSize - 1]) { 315 put(key, value); 316 return; 317 } 318 if (mGarbage && mSize >= mKeys.length) { 319 gc(); 320 } 321 mKeys = GrowingArrayUtils.append(mKeys, mSize, key); 322 mValues = GrowingArrayUtils.append(mValues, mSize, value); 323 mSize++; 324 } 325 /** 326 * {@inheritDoc} 327 * 328 * <p>This implementation composes a string by iterating over its mappings. If 329 * this map contains itself as a value, the string "(this Map)" 330 * will appear in its place. 331 */ 332 @Override 333 public String toString() { 334 if (size() <= 0) { 335 return "{}"; 336 } 337 StringBuilder buffer = new StringBuilder(mSize * 28); 338 buffer.append('{'); 339 for (int i=0; i<mSize; i++) { 340 if (i > 0) { 341 buffer.append(", "); 342 } 343 int key = keyAt(i); 344 buffer.append(key); 345 buffer.append('='); 346 Object value = valueAt(i); 347 if (value != this) { 348 buffer.append(value); 349 } else { 350 buffer.append("(this Map)"); 351 } 352 } 353 buffer.append('}'); 354 return buffer.toString(); 355 } 356 }
SparseArray是Android框架独有的类,在标准的JDK中不存在这个类。它要比 HashMap 节省内存,某些情况下比HashMap性能更好,按照官方问答的解释,主要是因为SparseArray不需要对key和value进行auto-boxing(将原始类型封装为对象类型,比如把int类型封装成Integer类型),结构比HashMap简单(SparseArray内部主要使用两个一维数组来保存数据,一个用来存key,一个用来存value)不需要额外的额外的数据结构(主要是针对HashMap中的HashMapEntry而言的)。是骡子是马得拉出来遛遛,下面我们就通过几段程序来证明SparseArray在各方面表现如何,下面的试验结果时在我的Hike X1(Android 4.2.2)手机上运行得出的。
代码1:
int MAX = 100000;
long start = System.currentTimeMillis();
HashMap<Integer, String> hash = new HashMap<Integer, String>();
for (int i = 0; i < MAX; i++) {
hash.put(i, String.valueOf(i));
}
long ts = System.currentTimeMillis() - start;
代码2:
int MAX = 100000;
long start = System.currentTimeMillis();
SparseArray<String> sparse = new SparseArray<String>();
for (int i = 0; i < MAX; i++) {
sparse.put(i, String.valueOf(i));
}
long ts = System.currentTimeMillis() - start;
我们分别在long start
处和long ts
处设置断点,然后通过DDMS工具查看内存使用情况。
代码1中,我们使用HashMap来创建100000条数据,开始创建前的系统内存情况为:
创建HashMap之后,应用内存情况为: 可见创建HashMap用去约 13.2M内存。
再看 代码2,同样是创建100000条数据,我们用SparseArray来试试,开始创建前的内存使用情况为:
创建SparseArray之后的情况: 创建SparseArray共用去 8.626M内存。
可见使用 SparseArray 的确比 HashMap 节省内存,大概节省 35%左右的内存。
我们再比较一下插入数据的效率如何,我们在加两段代码(主要就是把插入顺序变换一下,从大到小插入):
代码3:
int MAX = 100000;
long start = System.currentTimeMillis();
HashMap<Integer, String> hash = new HashMap<Integer, String>();
for (int i = 0; i < MAX; i++) {
hash.put(MAX - i -1, String.valueOf(i));
}
long ts = System.currentTimeMillis() - start;
代码4:
int MAX = 100000;
long start = System.currentTimeMillis();
SparseArray<String> sparse = new SparseArray<String>();
for (int i = 0; i < MAX; i++) {
sparse.put(MAX - i -1, String.valueOf(i));
}
long ts = System.currentTimeMillis() - start;
我们分别把这4代码分别运行5次,对比一下ts的时间(单位毫秒):
# | 代码1 | 代码2 | 代码3 | 代码4 |
---|---|---|---|---|
1 | 10750ms | 7429ms | 10862ms | 90527ms |
2 | 10718ms | 7386ms | 10711ms | 87990ms |
3 | 10816ms | 7462ms | 11033ms | 88259ms |
4 | 10943ms | 7386ms | 10854ms | 88474ms |
5 | 10671ms | 7317ms | 10786ms | 90630ms |
通过结果我们看出,在正序插入数据时候,SparseArray比HashMap要快一些;HashMap不管是倒序还是正序开销几乎是一样的;但是SparseArray的倒序插入要比正序插入要慢10倍以上,这时为什么呢?我们再看下面一段代码:
代码5:
SparseArray<String> sparse = new SparseArray<String>(3);
sparse.put(1, "s1");
sparse.put(3, "s3");
sparse.put(2, "s2");
我们在Eclipse的debug模式中,看Variables窗口,如图:
及时我们是按照1,3,2的顺序排列的,但是在SparseArray内部还是按照正序排列的,这时因为SparseArray在检索数据的时候使用的是二分查找,所以每次插入新数据的时候SparseArray都需要重新排序,所以代码4中,逆序是最差情况。
下面我们在简单看下检索情况:
代码5:
long start4search = System.currentTimeMillis();
for (int i = 0; i < MAX; i++) {
hash.get(33333); //针对固定值检索
}
long end4search = System.currentTimeMillis() - start4search;
代码6:
long start4search = System.currentTimeMillis();
for (int i = 0; i < MAX; i++) {
hash.get(i); //顺序检索
}
long end4search = System.currentTimeMillis() - start4search;
代码7:
long start4search = System.currentTimeMillis();
for (int i = 0; i < MAX; i++) {
sparse.get(33333); //针对固定值检索
}
long end4search = System.currentTimeMillis() - start4search;
代码8:
long start4search = System.currentTimeMillis();
for (int i = 0; i < MAX; i++) {
sparse.get(i); //顺序检索
}
long end4search = System.currentTimeMillis() - start4search;
表1:
# | 代码5 | 代码6 | 代码7 | 代码8 |
---|---|---|---|---|
1 | 4072ms | 4318ms | 3442ms | 3390ms |
2 | 4349ms | 4536ms | 3402ms | 3420ms |
3 | 4599ms | 4203ms | 3472ms | 3376ms |
4 | 4149ms | 4086ms | 3429ms | 3786ms |
5 | 4207ms | 4219ms | 3439ms | 3376ms |
代码9,我们试一些离散的数据。
//使用Foo为了避免由原始类型被自动封装(auto-boxing,比如把int类型自动转存Integer对象类型)造成的干扰。
class FOO{
Integer objKey;
int intKey;
}
...
int MAX = 100000;
HashMap<Integer, String> hash = new HashMap<Integer, String>();
SparseArray<String> sparse = new SparseArray<String>();
for (int i = 0; i < MAX; i++) {
hash.put(i, String.valueOf(i));
sparse.put(i, String.valueOf(i));
}
List<FOO> keylist4search = new ArrayList<FOO>();
for (int i = 0; i < MAX; i++) {
FOO f = new FOO();
f.intKey = i;
f.objKey = Integer.valueOf(i);
keylist4search.add(f);
}
long start4search = System.currentTimeMillis();
for (int i = 0; i < MAX; i++) {
hash.get(keylist4search.get(i).objKey);
}
long end4searchHash = System.currentTimeMillis() - start4search;
long start4search2 = System.currentTimeMillis();
for (int i = 0; i < MAX; i++) {
sparse.get(keylist4search.get(i).intKey);
}
long end4searchSparse = System.currentTimeMillis() - start4search2;
代码9,运行5次之后的结果如下:
表2:
# | end4searchHash | end4searchSparse |
---|---|---|
1 | 2402ms | 4577ms |
2 | 2249ms | 4188ms |
3 | 2649ms | 4821ms |
4 | 2404ms | 4598ms |
5 | 2413ms | 4547ms |
从上面两个表中我们可以看出,当SparseArray中存在需要检索的下标时,SparseArray的性能略胜一筹(表1)。但是当检索的下标比较离散时,SparseArray需要使用多次二分检索,性能显然比hash检索方式要慢一些了(表2),但是按照官方文档的说法性能差异不是很大,不超过50%( For containers holding up to hundreds of items, the performance difference is not significant, less than 50%.)
总体而言,在Android这种内存比CPU更金贵的系统中,能经济地使用内存还是上策,何况SparseArray在其他方面的表现也不算差(另外,SparseArray删除数据的时候也做了优化——使用了延迟整理数组的方法,可参考官方文档SparseArray,读者可以自行把代码9中的hash.get
和sparse.get
改成hash.remove
和sparse.delete
试试,你会发现二者的性能相差无几)。而且,使用SparseArray代替HashMap<integer,e>也是官方推荐的做法,在Eclipse中也会提示你优先使用SparseArray,如图:
另外,我们还可以用 LongSparseArray来替代HashMap<long,e>。SparseBooleanArray来替代HashMap<integer,boolean>。
=========================================================================================
HashMap是java里比较常用的一个集合类,我比较习惯用来缓存一些处理后的结果。最近在做一个Android项目,在代码中定义这样一个变量,实例化时,Eclipse却给出了一个 performance 警告。
意思就是说用SparseArray<E>来替代,以获取更好性能。老实说,对SparseArray并不熟悉,第一感觉应该是Android提供的一个类。按住Ctrl点击进入SparseArray的源码,果不其然,确定是Android提供的一个工具类。
单纯从字面上来理解,SparseArray指的是稀疏数组(Sparse array),所谓稀疏数组就是数组中大部分的内容值都未被使用(或都为零),在数组中仅有少部分的空间使用。因此造成内存空间的浪费,为了节省内存空间,并且不影响数组中原有的内容值,我们可以采用一种压缩的方式来表示稀疏数组的内容。
假设有一个9*7的数组,其内容如下:
在此数组中,共有63个空间,但却只使用了5个元素,造成58个元素空间的浪费。以下我们就使用稀疏数组重新来定义这个数组:
其中在稀疏数组中第一部分所记录的是原数组的列数和行数以及元素使用的个数、第二部分所记录的是原数组中元素的位置和内容。经过压缩之后,原来需要声明大小为63的数组,而使用压缩后,只需要声明大小为6*3的数组,仅需18个存储空间。
继续阅读SparseArray的源码,从构造方法我们可以看出,它和一般的List一样,可以预先设置容器大小,默认的大小是10:
public SparseArray() {
this(10);
}
public SparseArray(int initialCapacity) {
initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity);
mKeys = new int[initialCapacity];
mValues = new Object[initialCapacity];
mSize = 0;
}
再来看看它对数据的“增删改查”。
它有两个方法可以添加键值对:
public void put(int key, E value) {}
public void append(int key, E value){}
有四个方法可以执行删除操作:
public void delete(int key) {}
public void remove(int key) {} //直接调用的delete(int key)
public void removeAt(int index){}
public void clear(){}
修改数据起初以为只有setValueAt(int index, E value)可以修改数据,但后来发现put(int key, E value)也可以修改数据,我们查看put(int key, E value)的源码可知,在put数据之前,会先查找要put的数据是否已经存在,如果存在就是修改,不存在就添加。
public void put(int key, E value) {
int i = binarySearch(mKeys, 0, mSize, key);
if (i >= 0) {
mValues[i] = value;
} else {
i = ~i;
if (i < mSize && mValues[i] == DELETED) {
mKeys[i] = key;
mValues[i] = value;
return;
}
if (mGarbage && mSize >= mKeys.length) {
gc();
// Search again because indices may have changed.
i = ~binarySearch(mKeys, 0, mSize, key);
}
…………
所以,修改数据实际也有两种方法:
public void put(int key, E value)
public void setValueAt(int index, E value)
最后再来看看如何查找数据。有两个方法可以查询取值:
public E get(int key)
public E get(int key, E valueIfKeyNotFound)
其中get(int key)也只是调用了 get(int key,E valueIfKeyNotFound),最后一个从传参的变量名就能看出,传入的是找不到的时候返回的值.get(int key)当找不到的时候,默认返回null。
查看第几个位置的键:
public int keyAt(int index)
有一点需要注意的是,查看键所在位置,由于是采用二分法查找键的位置,所以找不到时返回小于0的数值,而不是返回-1。返回的负值是表示它在找不到时所在的位置。
查看第几个位置的值:
public E valueAt(int index)
查看值所在位置,没有的话返回-1:
public int indexOfValue(E value)
最后,发现其核心就是折半查找函数(binarySearch),算法设计的很不错。
private static int binarySearch(int[] a, int start, int len, int key) {
int high = start + len, low = start - 1, guess;
while (high - low > 1) {
guess = (high + low) / 2;
if (a[guess] < key)
low = guess;
else
high = guess;
}
if (high == start + len)
return ~(start + len);
else if (a[high] == key)
return high;
else
return ~high;
}
相应的也有SparseBooleanArray,用来取代HashMap<Integer, Boolean>,SparseIntArray用来取代HashMap<Integer, Integer>,大家有兴趣的可以研究。
总结:SparseArray是android里为<Interger,Object>这样的Hashmap而专门写的类,目的是提高效率,其核心是折半查找函数(binarySearch)。在Android中,当我们需要定义
HashMap<Integer, E> hashMap = new HashMap<Integer, E>();
时,我们可以使用如下的方式来取得更好的性能.
SparseArray<E> sparseArray = new SparseArray<E>();
关于ArrayMap与HashMap性能比较:
这里先附上ArrayMap源码:
1 /* 2 * Copyright (C) 2013 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 package android.util; 17 import libcore.util.EmptyArray; 18 import java.util.Collection; 19 import java.util.Map; 20 import java.util.Set; 21 /** 22 * ArrayMap is a generic key->value mapping data structure that is 23 * designed to be more memory efficient than a traditional {@link java.util.HashMap}. 24 * It keeps its mappings in an array data structure -- an integer array of hash 25 * codes for each item, and an Object array of the key/value pairs. This allows it to 26 * avoid having to create an extra object for every entry put in to the map, and it 27 * also tries to control the growth of the size of these arrays more aggressively 28 * (since growing them only requires copying the entries in the array, not rebuilding 29 * a hash map). 30 * 31 * <p>Note that this implementation is not intended to be appropriate for 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>Because this container is intended to better balance memory use, unlike most other 38 * standard Java containers it will shrink its array as items are removed from it. Currently 39 * you have no control over this shrinking -- if you set a capacity and then remove an 40 * item, it may reduce the capacity to better match the current size. In the future an 41 * explicit call to set the capacity should turn off this aggressive shrinking behavior.</p> 42 */ 43 public final class ArrayMap<K, V> implements Map<K, V> { 44 private static final boolean DEBUG = false; 45 private static final String TAG = "ArrayMap"; 46 /** 47 * The minimum amount by which the capacity of a ArrayMap will increase. 48 * This is tuned to be relatively space-efficient. 49 */ 50 private static final int BASE_SIZE = 4; 51 /** 52 * Maximum number of entries to have in array caches. 53 */ 54 private static final int CACHE_SIZE = 10; 55 /** 56 * @hide Special immutable empty ArrayMap. 57 */ 58 public static final ArrayMap EMPTY = new ArrayMap(true); 59 /** 60 * Caches of small array objects to avoid spamming garbage. The cache 61 * Object[] variable is a pointer to a linked list of array objects. 62 * The first entry in the array is a pointer to the next array in the 63 * list; the second entry is a pointer to the int[] hash code array for it. 64 */ 65 static Object[] mBaseCache; 66 static int mBaseCacheSize; 67 static Object[] mTwiceBaseCache; 68 static int mTwiceBaseCacheSize; 69 /** 70 * Special hash array value that indicates the container is immutable. 71 */ 72 static final int[] EMPTY_IMMUTABLE_INTS = new int[0]; 73 int[] mHashes; 74 Object[] mArray; 75 int mSize; 76 MapCollections<K, V> mCollections; 77 int indexOf(Object key, int hash) { 78 final int N = mSize; 79 // Important fast case: if nothing is in here, nothing to look for. 80 if (N == 0) { 81 return ~0; 82 } 83 int index = ContainerHelpers.binarySearch(mHashes, N, hash); 84 // If the hash code wasn't found, then we have no entry for this key. 85 if (index < 0) { 86 return index; 87 } 88 // If the key at the returned index matches, that's what we want. 89 if (key.equals(mArray[index<<1])) { 90 return index; 91 } 92 // Search for a matching key after the index. 93 int end; 94 for (end = index + 1; end < N && mHashes[end] == hash; end++) { 95 if (key.equals(mArray[end << 1])) return end; 96 } 97 // Search for a matching key before the index. 98 for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) { 99 if (key.equals(mArray[i << 1])) return i; 100 } 101 // Key not found -- return negative value indicating where a 102 // new entry for this key should go. We use the end of the 103 // hash chain to reduce the number of array entries that will 104 // need to be copied when inserting. 105 return ~end; 106 } 107 int indexOfNull() { 108 final int N = mSize; 109 // Important fast case: if nothing is in here, nothing to look for. 110 if (N == 0) { 111 return ~0; 112 } 113 int index = ContainerHelpers.binarySearch(mHashes, N, 0); 114 // If the hash code wasn't found, then we have no entry for this key. 115 if (index < 0) { 116 return index; 117 } 118 // If the key at the returned index matches, that's what we want. 119 if (null == mArray[index<<1]) { 120 return index; 121 } 122 // Search for a matching key after the index. 123 int end; 124 for (end = index + 1; end < N && mHashes[end] == 0; end++) { 125 if (null == mArray[end << 1]) return end; 126 } 127 // Search for a matching key before the index. 128 for (int i = index - 1; i >= 0 && mHashes[i] == 0; i--) { 129 if (null == mArray[i << 1]) return i; 130 } 131 // Key not found -- return negative value indicating where a 132 // new entry for this key should go. We use the end of the 133 // hash chain to reduce the number of array entries that will 134 // need to be copied when inserting. 135 return ~end; 136 } 137 private void allocArrays(final int size) { 138 if (mHashes == EMPTY_IMMUTABLE_INTS) { 139 throw new UnsupportedOperationException("ArrayMap is immutable"); 140 } 141 if (size == (BASE_SIZE*2)) { 142 synchronized (ArrayMap.class) { 143 if (mTwiceBaseCache != null) { 144 final Object[] array = mTwiceBaseCache; 145 mArray = array; 146 mTwiceBaseCache = (Object[])array[0]; 147 mHashes = (int[])array[1]; 148 array[0] = array[1] = null; 149 mTwiceBaseCacheSize--; 150 if (DEBUG) Log.d(TAG, "Retrieving 2x cache " + mHashes 151 + " now have " + mTwiceBaseCacheSize + " entries"); 152 return; 153 } 154 } 155 } else if (size == BASE_SIZE) { 156 synchronized (ArrayMap.class) { 157 if (mBaseCache != null) { 158 final Object[] array = mBaseCache; 159 mArray = array; 160 mBaseCache = (Object[])array[0]; 161 mHashes = (int[])array[1]; 162 array[0] = array[1] = null; 163 mBaseCacheSize--; 164 if (DEBUG) Log.d(TAG, "Retrieving 1x cache " + mHashes 165 + " now have " + mBaseCacheSize + " entries"); 166 return; 167 } 168 } 169 } 170 mHashes = new int[size]; 171 mArray = new Object[size<<1]; 172 } 173 private static void freeArrays(final int[] hashes, final Object[] array, final int size) { 174 if (hashes.length == (BASE_SIZE*2)) { 175 synchronized (ArrayMap.class) { 176 if (mTwiceBaseCacheSize < CACHE_SIZE) { 177 array[0] = mTwiceBaseCache; 178 array[1] = hashes; 179 for (int i=(size<<1)-1; i>=2; i--) { 180 array[i] = null; 181 } 182 mTwiceBaseCache = array; 183 mTwiceBaseCacheSize++; 184 if (DEBUG) Log.d(TAG, "Storing 2x cache " + array 185 + " now have " + mTwiceBaseCacheSize + " entries"); 186 } 187 } 188 } else if (hashes.length == BASE_SIZE) { 189 synchronized (ArrayMap.class) { 190 if (mBaseCacheSize < CACHE_SIZE) { 191 array[0] = mBaseCache; 192 array[1] = hashes; 193 for (int i=(size<<1)-1; i>=2; i--) { 194 array[i] = null; 195 } 196 mBaseCache = array; 197 mBaseCacheSize++; 198 if (DEBUG) Log.d(TAG, "Storing 1x cache " + array 199 + " now have " + mBaseCacheSize + " entries"); 200 } 201 } 202 } 203 } 204 /** 205 * Create a new empty ArrayMap. The default capacity of an array map is 0, and 206 * will grow once items are added to it. 207 */ 208 public ArrayMap() { 209 mHashes = EmptyArray.INT; 210 mArray = EmptyArray.OBJECT; 211 mSize = 0; 212 } 213 /** 214 * Create a new ArrayMap with a given initial capacity. 215 */ 216 public ArrayMap(int capacity) { 217 if (capacity == 0) { 218 mHashes = EmptyArray.INT; 219 mArray = EmptyArray.OBJECT; 220 } else { 221 allocArrays(capacity); 222 } 223 mSize = 0; 224 } 225 private ArrayMap(boolean immutable) { 226 // If this is immutable, use the sentinal EMPTY_IMMUTABLE_INTS 227 // instance instead of the usual EmptyArray.INT. The reference 228 // is checked later to see if the array is allowed to grow. 229 mHashes = immutable ? EMPTY_IMMUTABLE_INTS : EmptyArray.INT; 230 mArray = EmptyArray.OBJECT; 231 mSize = 0; 232 } 233 /** 234 * Create a new ArrayMap with the mappings from the given ArrayMap. 235 */ 236 public ArrayMap(ArrayMap<K, V> map) { 237 this(); 238 if (map != null) { 239 putAll(map); 240 } 241 } 242 /** 243 * Make the array map empty. All storage is released. 244 */ 245 @Override 246 public void clear() { 247 if (mSize > 0) { 248 freeArrays(mHashes, mArray, mSize); 249 mHashes = EmptyArray.INT; 250 mArray = EmptyArray.OBJECT; 251 mSize = 0; 252 } 253 } 254 /** 255 * @hide 256 * Like {@link #clear}, but doesn't reduce the capacity of the ArrayMap. 257 */ 258 public void erase() { 259 if (mSize > 0) { 260 final int N = mSize<<1; 261 final Object[] array = mArray; 262 for (int i=0; i<N; i++) { 263 array[i] = null; 264 } 265 mSize = 0; 266 } 267 } 268 /** 269 * Ensure the array map can hold at least <var>minimumCapacity</var> 270 * items. 271 */ 272 public void ensureCapacity(int minimumCapacity) { 273 if (mHashes.length < minimumCapacity) { 274 final int[] ohashes = mHashes; 275 final Object[] oarray = mArray; 276 allocArrays(minimumCapacity); 277 if (mSize > 0) { 278 System.arraycopy(ohashes, 0, mHashes, 0, mSize); 279 System.arraycopy(oarray, 0, mArray, 0, mSize<<1); 280 } 281 freeArrays(ohashes, oarray, mSize); 282 } 283 } 284 /** 285 * Check whether a key exists in the array. 286 * 287 * @param key The key to search for. 288 * @return Returns true if the key exists, else false. 289 */ 290 @Override 291 public boolean containsKey(Object key) { 292 return indexOfKey(key) >= 0; 293 } 294 /** 295 * Returns the index of a key in the set. 296 * 297 * @param key The key to search for. 298 * @return Returns the index of the key if it exists, else a negative integer. 299 */ 300 public int indexOfKey(Object key) { 301 return key == null ? indexOfNull() : indexOf(key, key.hashCode()); 302 } 303 int indexOfValue(Object value) { 304 final int N = mSize*2; 305 final Object[] array = mArray; 306 if (value == null) { 307 for (int i=1; i<N; i+=2) { 308 if (array[i] == null) { 309 return i>>1; 310 } 311 } 312 } else { 313 for (int i=1; i<N; i+=2) { 314 if (value.equals(array[i])) { 315 return i>>1; 316 } 317 } 318 } 319 return -1; 320 } 321 /** 322 * Check whether a value exists in the array. This requires a linear search 323 * through the entire array. 324 * 325 * @param value The value to search for. 326 * @return Returns true if the value exists, else false. 327 */ 328 @Override 329 public boolean containsValue(Object value) { 330 return indexOfValue(value) >= 0; 331 } 332 /** 333 * Retrieve a value from the array. 334 * @param key The key of the value to retrieve. 335 * @return Returns the value associated with the given key, 336 * or null if there is no such key. 337 */ 338 @Override 339 public V get(Object key) { 340 final int index = indexOfKey(key); 341 return index >= 0 ? (V)mArray[(index<<1)+1] : null; 342 } 343 /** 344 * Return the key at the given index in the array. 345 * @param index The desired index, must be between 0 and {@link #size()}-1. 346 * @return Returns the key stored at the given index. 347 */ 348 public K keyAt(int index) { 349 return (K)mArray[index << 1]; 350 } 351 /** 352 * Return the value at the given index in the array. 353 * @param index The desired index, must be between 0 and {@link #size()}-1. 354 * @return Returns the value stored at the given index. 355 */ 356 public V valueAt(int index) { 357 return (V)mArray[(index << 1) + 1]; 358 } 359 /** 360 * Set the value at a given index in the array. 361 * @param index The desired index, must be between 0 and {@link #size()}-1. 362 * @param value The new value to store at this index. 363 * @return Returns the previous value at the given index. 364 */ 365 public V setValueAt(int index, V value) { 366 index = (index << 1) + 1; 367 V old = (V)mArray[index]; 368 mArray[index] = value; 369 return old; 370 } 371 /** 372 * Return true if the array map contains no items. 373 */ 374 @Override 375 public boolean isEmpty() { 376 return mSize <= 0; 377 } 378 /** 379 * Add a new value to the array map. 380 * @param key The key under which to store the value. If 381 * this key already exists in the array, its value will be replaced. 382 * @param value The value to store for the given key. 383 * @return Returns the old value that was stored for the given key, or null if there 384 * was no such key. 385 */ 386 @Override 387 public V put(K key, V value) { 388 final int hash; 389 int index; 390 if (key == null) { 391 hash = 0; 392 index = indexOfNull(); 393 } else { 394 hash = key.hashCode(); 395 index = indexOf(key, hash); 396 } 397 if (index >= 0) { 398 index = (index<<1) + 1; 399 final V old = (V)mArray[index]; 400 mArray[index] = value; 401 return old; 402 } 403 index = ~index; 404 if (mSize >= mHashes.length) { 405 final int n = mSize >= (BASE_SIZE*2) ? (mSize+(mSize>>1)) 406 : (mSize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE); 407 if (DEBUG) Log.d(TAG, "put: grow from " + mHashes.length + " to " + n); 408 final int[] ohashes = mHashes; 409 final Object[] oarray = mArray; 410 allocArrays(n); 411 if (mHashes.length > 0) { 412 if (DEBUG) Log.d(TAG, "put: copy 0-" + mSize + " to 0"); 413 System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length); 414 System.arraycopy(oarray, 0, mArray, 0, oarray.length); 415 } 416 freeArrays(ohashes, oarray, mSize); 417 } 418 if (index < mSize) { 419 if (DEBUG) Log.d(TAG, "put: move " + index + "-" + (mSize-index) 420 + " to " + (index+1)); 421 System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index); 422 System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1); 423 } 424 mHashes[index] = hash; 425 mArray[index<<1] = key; 426 mArray[(index<<1)+1] = value; 427 mSize++; 428 return null; 429 } 430 /** 431 * Special fast path for appending items to the end of the array without validation. 432 * The array must already be large enough to contain the item. 433 * @hide 434 */ 435 public void append(K key, V value) { 436 int index = mSize; 437 final int hash = key == null ? 0 : key.hashCode(); 438 if (index >= mHashes.length) { 439 throw new IllegalStateException("Array is full"); 440 } 441 if (index > 0 && mHashes[index-1] > hash) { 442 RuntimeException e = new RuntimeException("here"); 443 e.fillInStackTrace(); 444 Log.w(TAG, "New hash " + hash 445 + " is before end of array hash " + mHashes[index-1] 446 + " at index " + index + " key " + key, e); 447 put(key, value); 448 return; 449 } 450 mSize = index+1; 451 mHashes[index] = hash; 452 index <<= 1; 453 mArray[index] = key; 454 mArray[index+1] = value; 455 } 456 /** 457 * The use of the {@link #append} function can result in invalid array maps, in particular 458 * an array map where the same key appears multiple times. This function verifies that 459 * the array map is valid, throwing IllegalArgumentException if a problem is found. The 460 * main use for this method is validating an array map after unpacking from an IPC, to 461 * protect against malicious callers. 462 * @hide 463 */ 464 public void validate() { 465 final int N = mSize; 466 if (N <= 1) { 467 // There can't be dups. 468 return; 469 } 470 int basehash = mHashes[0]; 471 int basei = 0; 472 for (int i=1; i<N; i++) { 473 int hash = mHashes[i]; 474 if (hash != basehash) { 475 basehash = hash; 476 basei = i; 477 continue; 478 } 479 // We are in a run of entries with the same hash code. Go backwards through 480 // the array to see if any keys are the same. 481 final Object cur = mArray[i<<1]; 482 for (int j=i-1; j>=basei; j--) { 483 final Object prev = mArray[j<<1]; 484 if (cur == prev) { 485 throw new IllegalArgumentException("Duplicate key in ArrayMap: " + cur); 486 } 487 if (cur != null && prev != null && cur.equals(prev)) { 488 throw new IllegalArgumentException("Duplicate key in ArrayMap: " + cur); 489 } 490 } 491 } 492 } 493 /** 494 * Perform a {@link #put(Object, Object)} of all key/value pairs in <var>array</var> 495 * @param array The array whose contents are to be retrieved. 496 */ 497 public void putAll(ArrayMap<? extends K, ? extends V> array) { 498 final int N = array.mSize; 499 ensureCapacity(mSize + N); 500 if (mSize == 0) { 501 if (N > 0) { 502 System.arraycopy(array.mHashes, 0, mHashes, 0, N); 503 System.arraycopy(array.mArray, 0, mArray, 0, N<<1); 504 mSize = N; 505 } 506 } else { 507 for (int i=0; i<N; i++) { 508 put(array.keyAt(i), array.valueAt(i)); 509 } 510 } 511 } 512 /** 513 * Remove an existing key from the array map. 514 * @param key The key of the mapping to remove. 515 * @return Returns the value that was stored under the key, or null if there 516 * was no such key. 517 */ 518 @Override 519 public V remove(Object key) { 520 final int index = indexOfKey(key); 521 if (index >= 0) { 522 return removeAt(index); 523 } 524 return null; 525 } 526 /** 527 * Remove the key/value mapping at the given index. 528 * @param index The desired index, must be between 0 and {@link #size()}-1. 529 * @return Returns the value that was stored at this index. 530 */ 531 public V removeAt(int index) { 532 final Object old = mArray[(index << 1) + 1]; 533 if (mSize <= 1) { 534 // Now empty. 535 if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0"); 536 freeArrays(mHashes, mArray, mSize); 537 mHashes = EmptyArray.INT; 538 mArray = EmptyArray.OBJECT; 539 mSize = 0; 540 } else { 541 if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) { 542 // Shrunk enough to reduce size of arrays. We don't allow it to 543 // shrink smaller than (BASE_SIZE*2) to avoid flapping between 544 // that and BASE_SIZE. 545 final int n = mSize > (BASE_SIZE*2) ? (mSize + (mSize>>1)) : (BASE_SIZE*2); 546 if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to " + n); 547 final int[] ohashes = mHashes; 548 final Object[] oarray = mArray; 549 allocArrays(n); 550 mSize--; 551 if (index > 0) { 552 if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0"); 553 System.arraycopy(ohashes, 0, mHashes, 0, index); 554 System.arraycopy(oarray, 0, mArray, 0, index << 1); 555 } 556 if (index < mSize) { 557 if (DEBUG) Log.d(TAG, "remove: copy from " + (index+1) + "-" + mSize 558 + " to " + index); 559 System.arraycopy(ohashes, index + 1, mHashes, index, mSize - index); 560 System.arraycopy(oarray, (index + 1) << 1, mArray, index << 1, 561 (mSize - index) << 1); 562 } 563 } else { 564 mSize--; 565 if (index < mSize) { 566 if (DEBUG) Log.d(TAG, "remove: move " + (index+1) + "-" + mSize 567 + " to " + index); 568 System.arraycopy(mHashes, index + 1, mHashes, index, mSize - index); 569 System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1, 570 (mSize - index) << 1); 571 } 572 mArray[mSize << 1] = null; 573 mArray[(mSize << 1) + 1] = null; 574 } 575 } 576 return (V)old; 577 } 578 /** 579 * Return the number of items in this array map. 580 */ 581 @Override 582 public int size() { 583 return mSize; 584 } 585 /** 586 * {@inheritDoc} 587 * 588 * <p>This implementation returns false if the object is not a map, or 589 * if the maps have different sizes. Otherwise, for each key in this map, 590 * values of both maps are compared. If the values for any key are not 591 * equal, the method returns false, otherwise it returns true. 592 */ 593 @Override 594 public boolean equals(Object object) { 595 if (this == object) { 596 return true; 597 } 598 if (object instanceof Map) { 599 Map<?, ?> map = (Map<?, ?>) object; 600 if (size() != map.size()) { 601 return false; 602 } 603 try { 604 for (int i=0; i<mSize; i++) { 605 K key = keyAt(i); 606 V mine = valueAt(i); 607 Object theirs = map.get(key); 608 if (mine == null) { 609 if (theirs != null || !map.containsKey(key)) { 610 return false; 611 } 612 } else if (!mine.equals(theirs)) { 613 return false; 614 } 615 } 616 } catch (NullPointerException ignored) { 617 return false; 618 } catch (ClassCastException ignored) { 619 return false; 620 } 621 return true; 622 } 623 return false; 624 } 625 /** 626 * {@inheritDoc} 627 */ 628 @Override 629 public int hashCode() { 630 final int[] hashes = mHashes; 631 final Object[] array = mArray; 632 int result = 0; 633 for (int i = 0, v = 1, s = mSize; i < s; i++, v+=2) { 634 Object value = array[v]; 635 result += hashes[i] ^ (value == null ? 0 : value.hashCode()); 636 } 637 return result; 638 } 639 /** 640 * {@inheritDoc} 641 * 642 * <p>This implementation composes a string by iterating over its mappings. If 643 * this map contains itself as a key or a value, the string "(this Map)" 644 * will appear in its place. 645 */ 646 @Override 647 public String toString() { 648 if (isEmpty()) { 649 return "{}"; 650 } 651 StringBuilder buffer = new StringBuilder(mSize * 28); 652 buffer.append('{'); 653 for (int i=0; i<mSize; i++) { 654 if (i > 0) { 655 buffer.append(", "); 656 } 657 Object key = keyAt(i); 658 if (key != this) { 659 buffer.append(key); 660 } else { 661 buffer.append("(this Map)"); 662 } 663 buffer.append('='); 664 Object value = valueAt(i); 665 if (value != this) { 666 buffer.append(value); 667 } else { 668 buffer.append("(this Map)"); 669 } 670 } 671 buffer.append('}'); 672 return buffer.toString(); 673 } 674 // ------------------------------------------------------------------------ 675 // Interop with traditional Java containers. Not as efficient as using 676 // specialized collection APIs. 677 // ------------------------------------------------------------------------ 678 private MapCollections<K, V> getCollection() { 679 if (mCollections == null) { 680 mCollections = new MapCollections<K, V>() { 681 @Override 682 protected int colGetSize() { 683 return mSize; 684 } 685 @Override 686 protected Object colGetEntry(int index, int offset) { 687 return mArray[(index<<1) + offset]; 688 } 689 @Override 690 protected int colIndexOfKey(Object key) { 691 return indexOfKey(key); 692 } 693 @Override 694 protected int colIndexOfValue(Object value) { 695 return indexOfValue(value); 696 } 697 @Override 698 protected Map<K, V> colGetMap() { 699 return ArrayMap.this; 700 } 701 @Override 702 protected void colPut(K key, V value) { 703 put(key, value); 704 } 705 @Override 706 protected V colSetValue(int index, V value) { 707 return setValueAt(index, value); 708 } 709 @Override 710 protected void colRemoveAt(int index) { 711 removeAt(index); 712 } 713 @Override 714 protected void colClear() { 715 clear(); 716 } 717 }; 718 } 719 return mCollections; 720 } 721 /** 722 * Determine if the array map contains all of the keys in the given collection. 723 * @param collection The collection whose contents are to be checked against. 724 * @return Returns true if this array map contains a key for every entry 725 * in <var>collection</var>, else returns false. 726 */ 727 public boolean containsAll(Collection<?> collection) { 728 return MapCollections.containsAllHelper(this, collection); 729 } 730 /** 731 * Perform a {@link #put(Object, Object)} of all key/value pairs in <var>map</var> 732 * @param map The map whose contents are to be retrieved. 733 */ 734 @Override 735 public void putAll(Map<? extends K, ? extends V> map) { 736 ensureCapacity(mSize + map.size()); 737 for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) { 738 put(entry.getKey(), entry.getValue()); 739 } 740 } 741 /** 742 * Remove all keys in the array map that exist in the given collection. 743 * @param collection The collection whose contents are to be used to remove keys. 744 * @return Returns true if any keys were removed from the array map, else false. 745 */ 746 public boolean removeAll(Collection<?> collection) { 747 return MapCollections.removeAllHelper(this, collection); 748 } 749 /** 750 * Remove all keys in the array map that do <b>not</b> exist in the given collection. 751 * @param collection The collection whose contents are to be used to determine which 752 * keys to keep. 753 * @return Returns true if any keys were removed from the array map, else false. 754 */ 755 public boolean retainAll(Collection<?> collection) { 756 return MapCollections.retainAllHelper(this, collection); 757 } 758 /** 759 * Return a {@link java.util.Set} for iterating over and interacting with all mappings 760 * in the array map. 761 * 762 * <p><b>Note:</b> this is a very inefficient way to access the array contents, it 763 * requires generating a number of temporary objects and allocates additional state 764 * information associated with the container that will remain for the life of the container.</p> 765 * 766 * <p><b>Note:</b></p> the semantics of this 767 * Set are subtly different than that of a {@link java.util.HashMap}: most important, 768 * the {@link java.util.Map.Entry Map.Entry} object returned by its iterator is a single 769 * object that exists for the entire iterator, so you can <b>not</b> hold on to it 770 * after calling {@link java.util.Iterator#next() Iterator.next}.</p> 771 */ 772 @Override 773 public Set<Map.Entry<K, V>> entrySet() { 774 return getCollection().getEntrySet(); 775 } 776 /** 777 * Return a {@link java.util.Set} for iterating over and interacting with all keys 778 * in the array map. 779 * 780 * <p><b>Note:</b> this is a fairly inefficient way to access the array contents, it 781 * requires generating a number of temporary objects and allocates additional state 782 * information associated with the container that will remain for the life of the container.</p> 783 */ 784 @Override 785 public Set<K> keySet() { 786 return getCollection().getKeySet(); 787 } 788 /** 789 * Return a {@link java.util.Collection} for iterating over and interacting with all values 790 * in the array map. 791 * 792 * <p><b>Note:</b> this is a fairly inefficient way to access the array contents, it 793 * requires generating a number of temporary objects and allocates additional state 794 * information associated with the container that will remain for the life of the container.</p> 795 */ 796 @Override 797 public Collection<V> values() { 798 return getCollection().getValues(); 799 } 800 }