Java性能調優之容器擴容問題

hiiyep 7年前發布 | 11K 次閱讀 Java Java開發

在Java和Android編程中,我們經常使用類似ArrayList,HashMap等這些容器。這些容器少則存儲幾條,多則上千甚至更多。作為性能調優的一部分,容器調優往往被我們忽略,本文將嘗試探索闡述一些關于容器調優中的擴容問題。雖然以Java為例,但是也同樣適用于其他編程語言。

首先以我們最常用的ArrayList為例,它是一個基于數組的List實現。

public static void main(String[] args) {
    ArrayList<Object> collection = new ArrayList();
    for (int i = 0; i< 1000; i++) {
        collection.add(new Object());
    }
}

以上代碼很簡單,不用贅述。那我們使用NetBeans的profile插件 來看一下關于Object對象分配的stacktrace

從stacktrace中,我們可以發現

  • Object對象trace始于ArrayList.add方法
  • 經過了一個叫做ArrayList.grow方法

以上我們可以推斷,ArrayList對象發生了擴容操作。因為使用無參的構造方法,會初始化一個存儲容量為0的數組。

如下代碼為ArrayList的構造方法

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
    }
}


public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

然而想要容量1000個Object實例,這個過程中則需要不斷的擴容,在這個過程中發生了以下幾點

  • 確定新的容量,并以新容量為大小創建新的數組
  • 將舊數組的數據拷貝到新數組中
  • 舊的數組將會后續被GC回收掉

除此之外,擴容還會增加CPU高速緩存的 命中率。因為

在JVM中,一般來說,由于對象和其字段常常都需要同時引用,將對象和其字段盡可能放在內存中相鄰位置能夠減少CPU高速緩存的未命中率。

而ArrayList擴容后的新數組可能不在于該對象相鄰,所以擴容理論上會增加CPU高速緩存的未命中率。

注意:上面提到的都是CPU高速緩存的未命中率,不是命中率。

更容易擴容的HashMap

HashMap作為一個高效的key-value的容器,內部也維護了一個Entry數組,也存在擴容的問題。

然而,HashMap為了更加有效的避免數組沖突,引入了兩個概念。

  • threshold 閾(yu,四聲)值,當內部數據占用量超過這個值,進行擴容。
  • loadFactor 通常為0.75,用來計算threshold值,即threshold = 容量 * loadFactor

舉個例子

  • 創建一個HashMap設置初始化容量為16,使用默認的loadFactor 0.75,即threshold為12
  • 然后不斷的填充key,value數據
  • 當內部數據占用量超過12時,就會觸發擴容操作,而不是等到16的時候。
  • 通常的擴容為雙倍擴容,即變成原來的兩倍,這里為32.

因此說HashMap更容易觸發擴容,但是這其實是一種在hash與容量占用的一種平衡。

如何解決或者改善擴容問題

使用預設較為合理的初始容量

SQLiteDatabase提供了方便的ContentValues簡化了我們處理列名與值的映射,ContentValues內部采用了HashMap來存儲Key-Value數據,ContentValues的初始容量是8,如果當添加的數據超過8之前,則會進行雙倍擴容操作,

因此建議對ContentValues填入的內容進行估量,根據實際需要的字段數量,設置合理的初始化數量。

嘗試使用其他非基于數組的數據結構

數組的一大優點就是隨機訪問很高效,這是鏈表所無法匹敵的。

但是并不是所有的時候都數組都有明顯優勢

  • 不需要隨機訪問或者數據量很小
  • 在頻繁的增加和刪除數據的時候,鏈表有明顯的優勢。

一些替代方案

  • 對于List,可以考慮使用LinkedList
  • 對于Map,可以考慮使用TreeMap
  • 關于替代HashMap,Android引入了一個叫做ArrayMap的類,用來解決HashMap內存占用的問題。

關于擴容的問題就是以上內容,當我們無論是使用任何數據結構時都需要考慮到具體的環境和需要,確保能夠做到最優。

 

來自:http://droidyue.com/blog/2017/03/05/java-performance-tuning-collection-size-growth/

 

 本文由用戶 hiiyep 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!