關於Android中ArrayMap/SparseArray比HashMap性能好的深刻研究

因爲網上有朋友對於這個問題已經有了很詳細的研究,因此我就不班門弄斧了: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

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.getsparse.get改爲hash.removesparse.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 }
ArrayMap
相關文章
相關標籤/搜索