Java基礎強化——集合框架

集合框架常用的數據結構

ArrayList動態擴容機制

ArrayList三種初始化方式:

//默認的構造器,將會以默認的大小來初始化內部的數組
public ArrayList();

//用一個Collection對象來構造,並將該集合的元素添加到ArrayList
public ArrayList(Collection<? extends E> c);

// 用指定的大小來初始化內部的數組
public ArrayList(int initialCapacity);

擴容條件:根據傳入的最小需要容量minCapacity來和數組的容量長度對比,若minCapactity大於或等於數組容量,則需要進行擴容。jdk7中採用>>位運算,右移動一位, 容量相當於擴大了1.5倍。(1+右移一位0.5)

在JDK1.7中,如果通過無參構造的話,初始數組容量爲0,當真正對數組進行添加時,才真正分配容量(默認10)。每次按照1.5倍(位運算)的比率通過copeOf的方式擴容。(newCapacity = oldCapacity + (oldCapacity >> 1))

ArrayList源碼中的構造方法:

	/**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    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);
        }
    }

另外需要區分容量和大小的關係,通常情況下容量要大於等於大小(Capacity >= size)

import java.lang.reflect.Field;
import java.util.ArrayList;

public class ArrayListTest {

    public static void main(String[] args) {
        //創建初始容量長度爲20的數組
        ArrayList<String> list = new ArrayList<>(20);

        /** 利用反射機制,獲取ArrayList的容量*/
        int capacity = 0;
        Class c = list.getClass();
        Field f;
        try {
            f = c.getDeclaredField("elementData");
            f.setAccessible(true);

            Object[] o = (Object[]) f.get(list);
            capacity = o.length;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }

        System.out.println("Capacity: " + capacity); 

        /** ArrayList 的大小*/
        int size = list.size();
        System.out.println("Size: " + size);
    }
}

//Output:
//Capacity: 20
//Size: 0

HashMap及其擴容機制

HashMap存儲數據採用的是散列表結構(數組+鏈表的結構),在JDK8中HashMap的底層數據結構已經變爲數組+鏈表+紅黑樹的結構,這主要原因是爲了減少hash衝突帶來的影響。

HashMap的基本原理是散列表+拉鍊法,就是在往HashMap中put元素時,會先根據key的hash值得到這個元素在數組中的位置(即下標),然後把這個元素放到對應的位置中。如果這個元素所在的位子上已經存放有其他元素了,那麼在同一個位子上的元素將以鏈表的形式存放,新加入的放在鏈頭,最先加入的放在鏈尾。

實際情況下,我們希望HashMap裏面的元素位置儘量的分佈均勻些,儘量使得每個位置上的元素數量只有一個,那麼當我們用hash算法求得這個位置的時候,馬上就可以知道對應位置的元素是我們想要元素,而不用再去遍歷鏈表。最容易的做法就是把hashcode對數組長度取模運算,這樣一來,元素的分佈相對來說是比較均勻的。但是“模”運算的消耗是比較大的,在Java中選擇了另外一種更快速,消耗更小的方式:首先算得key得hashcode值,然後跟數組的長度-1做一次“與”運算(&)

static int indexFor(int h, int length) {
	return h & (length-1);
}  

HashMap中get()方法的執行過程是:首先計算key的hashcode,找到數組中對應位置的某一元素,然後通過key的equals方法在對應位置的鏈表中找到需要的元素。所以hashcodeequals方法是找到對應元素的兩個關鍵方法,通過改寫key對象的equalshashcode方法,我們就可以將任意的業務對象作爲map的key。在判斷兩個對象是否真的相等時,必須保證它們的hashcode相同,且保證調用 equals()方法返回true。

HashMap擴容

在JDK7中,HashMap數據結構是數組+鏈表的方式,HashMap內存儲數據的Entry數組默認是16,如果沒有對Entry擴容機制的話,當存儲的數據一多,Entry內部的鏈表會很長,這就失去了HashMap的存儲意義了,所以HasnMap內部有擴容機制來進行處理。

HashMap內部有:變量size,記錄HashMap的底層數組中已用槽的數量;變量threshold,它是HashMap的閾值,用於判斷是否需要調整HashMap的容量(threshold = 容量*加載因子);變量DEFAULT_LOAD_FACTOR = 0.75f,默認加載因子爲0.75。

HashMap擴容的條件是:當size大於threshold時,對HashMap進行擴容。

//舉個例子假如現在有三個元素(3,5,7)要放入map裏面,table的的容量是2,如下
[0]=null  
[1]=3->5->7  

//現在將table的大小擴容成4,分佈如下:
[0]=null  
[1]=5->7  
[2]=null  
[3]=3  

在JDK8裏面,HashMap的底層數據結構已經變爲數組+鏈表+紅黑樹的結構了,因爲在hash衝突嚴重的情況下,鏈表的查詢效率是O(n),所以JDK8做了優化對於單個鏈表的個數大於8的鏈表,會直接轉爲紅黑樹結構算是以空間換時間,這樣以來查詢的效率就變爲O(logN)。

簡單總結就是,JDK7裏面是先判斷table的存儲元素的數量是否超過當前的threshold=table.length*loadFactor(默認0.75),如果超過就先擴容,在JDK8裏面是先插入數據,插入之後在判斷下一次++size的大小是否會超過當前的閾值,如果超過就擴容。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章