因爲網上有朋友對於這個問題已經有了很詳細的研究,因此我就不班門弄斧了:html
轉載於:http://android-performance.com/android/2014/02/10/android-sparsearray-vs-hashmap.htmljava
http://liuzhichao.com/p/832.htmlandroid
http://www.codes51.com/article/detail_163576.html算法
源碼:express
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)手機上運行得出的。apache
代碼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條數據,開始建立前的系統內存狀況爲: app
建立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 }