使用ArrayMap優(yōu)化Android App

當我們需要存儲健->值這樣的數據類型時,腦海里想到的第一個數據類型應該是HashMap。然后開始肆無忌憚的使用它,而從不考慮它帶來的性能影響。

使用HashMap時,Android Studio會發(fā)出警告,提示你使用ArrayMap來代替,但是通常被我們忽略了。

既然Android推薦了ArrayMap,那我們應該優(yōu)先考慮使用它而不是HashMap。下面簡單對比下HashMap和ArrayMap的內部實現,以便探求在什么場景下使用它。

HashMap vs ArrayMap

HashMap 位于 java.util.HashMap包中。
ArrayMap 位于 android.util.ArrayMap和android.support.v4.util.ArrayMap包中。

HashMap

我們知道,java的HashMap的存儲結構是一個數據加單向鏈表的形式。HashMap將每隔節(jié)點信息存儲在Entry<K,V>結構中。Entry<K,V>中存儲了節(jié)點對應的key、value、hash信息,同時存儲了當前節(jié)點的下一個節(jié)點的引用。因此,Entry<K,V>是一個單向鏈表。每一個key對應的hashCode,在HashMap數組中都可以找到一個位置,而如果多個key對應了相同的hashCode,那么他們在數組中對應在相同的位置上,這是HashMap將把對應的信息放到Entry<K,V>中,并使用鏈表連接這些Entry<K,V>。
HashMap基本上是一個HashMap.Entry<K,V>的數組,Entry<K,V>中包含以下字段:

  • 一個非基本數據類型的key
  • 一個非基本數據類型的value
  • 保存對象的hashCode
  • 指向下一個Entry<K,V>的指針

當有鍵值對插入時,HashMap會發(fā)生什么呢?

  • 首先,計算鍵的hashCode,然后這個值會付給Entry類中對應的hashCode。
  • 然后,使用這個hashCode找到它將要被存入的數組中的位置index。
  • 如果該位置已經有一個元素,那么新的元素將會被插入到這個位置上的鏈表的頭部,next指向上一個元素。
    現在,當使用key去查詢時,時間復雜度是O(1)。

雖然在時間上HashMap更快,但是它也花費了更多的內存空間。由于HashMap存儲的是非基本數據類型,因此自動裝箱的存在意味著每次插入都會有額外的對象創(chuàng)建,這會影響到內存的利用。另外,Entry對象本身是一層額外需要被創(chuàng)建以及被垃圾回收的對象。

在Android中,內存是至關重要的,因為持續(xù)的分發(fā)和釋放內存會觸發(fā)垃圾回收,導致應用出現卡頓。

ArrayMap

ArrayMap在設計上比傳統(tǒng)的HashMap更多的考慮了內存的優(yōu)化,可以理解為以時間換空間的一種優(yōu)化。它使用了兩個數組來存儲數據——一個整型數組存儲鍵的hashCode,另一個對象數組存儲鍵/值對。這樣既能避免為每個存入map中的鍵創(chuàng)建額外的對象,又能更積極的控制這些數據的長度的增加。因為增加長度只需要拷貝數組中的鍵,而不是重新構建一個哈希表。

需要注意的是,ArrayMap并不適用于可能含有大量條目的數據類型,前面說了,它是一種以時間換空間的優(yōu)化,通常比HashMap要慢,因為在查找時需要進行二分查找,增加或刪除時,需要在數組中插入或者刪除鍵,對于一個百數量級的容器來說,二者的性能差異是可以忽略的。
ArrayMap使用兩個數組,它的對象實例內部有用來存儲對象的Object[] mArray數組和用來存儲哈希值的int[] mHashes數組。

當插入一個鍵值對時:

鍵被插入到objects的下一個空閑位置。值對象唄插入到mArray的與對應鍵相鄰的位置。計算出的鍵的hashCode會被插入到mHashes數組的下一個空閑位置。

當查找一個key時:

先計算key的hashCode,在mHashes數組中二分查找此hashCode,這使得時間復雜度增加到了O(logN)。得到hashCode對應的索引index,鍵值對中的鍵就存儲在mArray[index<<1],而值就存儲在mArray[index<<1+1]的位置。
get方法:

@Override
public V get(Object key) {
 final int index = indexOfKey(key);
 return index >= 0 ?(V)mArray[(index<<1)+1] : null;
}

查找key的位置:

int indexOf(Object key, int hash) {
 final int N = mSize;
 // Important fast case: if nothing is in here, nothing to look for.
 if (N == 0) {
  return ~0;
 }
 int index =ContainerHelpers.binarySearch(mHashes, N, hash);
 // If the hash code wasn't found, then we have no entry for this key.
 if (index < 0) {
  return index;
 }
 // If the key at the returned index matches, that's what we want.
 if (key.equals(mArray[index<<1])) {
  return index;
 }
 // Search for a matching key after the index.
 int end;
 for (end = index + 1; end < N && mHashes[end] == hash; end++) {
 if (key.equals(mArray[end << 1]))
  return end;
 }
 // Search for a matching key before the index.
 for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) {
 if (key.equals(mArray[i << 1])) 
  return i;
 }
 // Key not found -- return negative value indicating where a
 // new entry for this key should go.  We use the end of the
 // hash chain to reduce the number of array entries that will
 // need to be copied when inserting.
 return ~end;
}

ArrayMap花費了更多的時間去查找,但是內存的效率提升了。通常在數百量級的情況下,這種時間差異是可以忽略的,但是內存的效率卻獲得了提升。

推薦的數據結構:
? ArrayMap<K,V> 替代 HashMap<K,V>
? ArraySet<K,V> 替代 HashSet<K,V>
? SparseArray<V> 替代 HashMap<Integer,V>
? SparseBooleanArray 替代 HashMap<Integer,Boolean>
? SparseIntArray 替代 HashMap<Integer,Integer>
? SparseLongArray 替代 HashMap<Integer,Long>
? LongSparseArray<V> 替代 HashMap<Long,V>

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 一、基本數據類型 注釋 單行注釋:// 區(qū)域注釋:/* */ 文檔注釋:/** */ 數值 對于byte類型而言...
    龍貓小爺閱讀 4,477評論 0 16
  • 實際上,HashSet 和 HashMap 之間有很多相似之處,對于 HashSet 而言,系統(tǒng)采用 Hash 算...
    曹振華閱讀 2,566評論 1 37
  • 我們不得不承認一個事實:我們正被信息淹沒著。 在多元化網絡的世界里,我們每時每刻不再接受著各種各樣的信息。通過24...
    婉琰閱讀 196評論 0 0
  • 一、KVC (key value code)的基本概念和用法 1、基本概念 2、適用情況:將服務器的內容轉化為數...
    IIronMan閱讀 813評論 0 15
  • 原本這不是我愿意看到的,我以為我能坦然接受,但是沒成想我沒那個本事,我做不到。稍微有一點風吹草動我就像霜打的茄子,...
    A001愛誰誰萱閱讀 356評論 0 0

友情鏈接更多精彩內容