JDK源碼學習之 集合實現類

1、HashMapjava

(1) 簡介:java1.8版本以前HashMap的結構圖以下:算法

數組的每一個元素都是一個單鏈表的頭節點,鏈表是用來解決衝突的,若是不一樣的key映射到了數組的同一位置處,就將其放入單鏈表中。
但這就存在一個問題:在一個鏈表中查找一個節點時,將會花費O(n)的查找時間,會有很大的性能損失,因此在JDK1.8後,當同一個hash值的節點數不小於8時,再也不採用單鏈表形式存儲,而是採用紅黑樹,結構圖以下:數組

HashMap在底層將key-value當成一個總體進行處理,這個總體就是一個Node對象。HashMap底層採用一個Node[]數組來保存全部的key-value對,當須要存儲一個Node對象時,會根據key的hash算法來決定其在數組中的存儲位置,再根據equals方法決定其在該數組位置上的鏈表中的存儲位置;當須要取出一個Node時,也會根據key的hash算法找到其在數組中的存儲位置,再根據equals方法從該位置上的鏈表中取出該Node。緩存

(2)  Fields安全

/* ---------------- Fields -------------- */

/**
 * 存儲Node類的數組
 */
transient Node<K,V>[] table;

/**
 * entrySet()緩存
 */
transient Set<Map.Entry<K,V>> entrySet;

/**
 * 記錄hashmap中存儲鍵-值對的數量 
 **/
transient int size;

/**
 * hashmap結構被改變的次數,fail-fast機制
(modCount值記錄修改次數,對HashMap內容的修改都將增長這個值。迭代器初始化過程當中會將這個值賦給迭代器的expectedModCount,在迭代過程當中,判斷modCount跟expectedModCount是否相等,若是不相等就表示已經有其餘線程修改了Map,立刻拋出異常) */ transient int modCount; /** * 擴容的門限值,當size大於這個值時,table數組進行擴容  */ int threshold; /** * The load factor for the hash table. * * @serial */ final float loadFactor; /** * 默認初始化數組大小爲16,必須是2的倍數 */ static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 /** 最大數組容量 MAXIMUN_CAPCITY <= 1<<30. */ static final int MAXIMUM_CAPACITY = 1 << 30; /** * 默認裝載因子, */ static final float DEFAULT_LOAD_FACTOR = 0.75f; /** 鏈表的最大長度,當大於這個長度時,鏈表轉化爲紅黑樹 */ static final int TREEIFY_THRESHOLD = 8;

 (3) 構造函數: 可自定義初始容量和加載因子數據結構

public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);

    // Find a power of 2 >= initialCapacity
    int capacity = 1;
    while (capacity < initialCapacity)
        capacity <<= 1;

    this.loadFactor = loadFactor;
    threshold = (int)(capacity * loadFactor);
    table = new Entry[capacity];
    init();
}

  (4) 主要方法分析:併發

  a) put(K key, V value)函數

public V put(K key, V value) {
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key.hashCode());
    int i = indexFor(hash, table.length);
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }

    modCount++;
    addEntry(hash, key, value, i);
    return null;
}
// HashMap最多容許且僅容許一個key爲null
private V putForNullKey(V value) {
    for (Entry<K,V> e = table[0]; e != null; e = e.next) {
        if (e.key == null) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    modCount++;
    addEntry(0, null, value, 0);
    return null;
}

  b) get(Object key)性能

public V get(Object key) {
    if (key == null)
        return getForNullKey();
    int hash = hash(key.hashCode());
    for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
            return e.value;
    }
    return null;
}
//若是存在key=null,返回key對應的value,不然直接返回null
private V getForNullKey() {
    for (Entry<K,V> e = table[0]; e != null; e = e.next) {
        if (e.key == null)
            return e.value;
    }
    return null;
}

  c) resize(int newCapacity)this

void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }

        Entry[] newTable = new Entry[newCapacity];
        transfer(newTable);
        table = newTable;
        threshold = (int)(newCapacity * loadFactor);
}

  當HashMap中的元素個數超過數組大小*loadFactor時,就會進行數組擴容,loadFactor的默認值爲0.75,這是一個折中的取值。也就是說,默認狀況下,數組大小爲16,那麼當HashMap中元素個數超過16*0.75=12的時候,就把數組的大小擴展爲 2*16=32,即擴大一倍,而後從新計算每一個元素在數組中的位置,而這是一個很是消耗性能的操做,因此若是咱們已經預知HashMap中元素的個數,那麼預設元素的個數可以有效的提升HashMap的性能。

  d) Fail-Fast機制:

   HashMap不是線程安全的,所以若是在使用迭代器的過程當中有其餘線程修改了map,那麼將拋出ConcurrentModificationException,這就是所謂fail-fast策略。

     這一策略在源碼中的實現是經過modCount域,modCount顧名思義就是修改次數,對HashMap內容的修改都將增長這個值,那麼在迭代器初始化過程當中會將這個值賦給迭代器的expectedModCount

HashIterator() {
    expectedModCount = modCount;
    if (size > 0) { // advance to first entry
    Entry[] t = table;
    while (index < t.length && (next = t[index++]) == null)
        ;
    }
} 

  在迭代過程當中,判斷modCount跟expectedModCount是否相等,若是不相等就表示已經有其餘線程修改了Map。(modCount聲明爲volatile,保證了線程之間修改的可見性)

 

2、 HashTable

  1. Hashtable是基於哈希表的Map接口的同步實現,不容許使用null值和null鍵。
  2. 底層使用數組實現,數組中每一項是個單鏈表,即數組和鏈表的結合體
  3. Hashtable在底層將key-value當成一個總體進行處理,這個總體就是一個Entry對象。Hashtable底層採用一個Entry[]數組來保存全部的key-value對,當須要存儲一個Entry對象時,會根據key的hash算法來決定其在數組中的存儲位置,在根據equals方法決定其在該數組位置上的鏈表中的存儲位置;當須要取出一個Entry時,也會根據key的hash算法找到其在數組中的存儲位置,再根據equals方法從該位置上的鏈表中取出該Entry。
  4. synchronized是針對整張Hash表的,即每次鎖住整張表讓線程獨佔。

 

3、 ConcurrentHashMap
  1. ConcurrentHashMap容許多個修改操做併發進行,其關鍵在於使用了鎖分離技術。它使用了多個鎖來控制對hash表的不一樣段進行的修改,每一個段其實就是一個小的hashtable,它們有本身的鎖。只要多個併發發生在不一樣的段上,它們就能夠併發進行。
  2. ConcurrentHashMap在底層將key-value當成一個總體進行處理,這個總體就是一個Entry對象。Hashtable底層採用一個Entry[]數組來保存全部的key-value對,當須要存儲一個Entry對象時,會根據key的hash算法來決定其在數組中的存儲位置,在根據equals方法決定其在該數組位置上的鏈表中的存儲位置;當須要取出一個Entry時,也會根據key的hash算法找到其在數組中的存儲位置,再根據equals方法從該位置上的鏈表中取出該Entry。
  3. 與HashMap不一樣的是,ConcurrentHashMap使用多個子Hash表,也就是段(Segment)

  (注:JDK 1.8中放棄了Segment臃腫的設計,取而代之的是採用Node + CAS + Synchronized來保證併發安全進行實現。)
  4. ConcurrentHashMap徹底容許多個讀操做併發進行,讀操做並不須要加鎖。若是使用傳統的技術,如HashMap中的實現,若是容許能夠在hash鏈的中間添加或刪除元素,讀操做不加鎖將獲得不一致的數據。ConcurrentHashMap實現技術是保證HashEntry幾乎是不可變的。

 

4、 HashSet
  1. HashSet由哈希表(其實是一個HashMap實例)支持,不保證set的迭代順序,並容許使用null元素。
  2. 基於HashMap實現,API也是對HashMap的行爲進行了封裝,可參考HashMap。

 

5、LinkedHashMap
  1. LinkedHashMap繼承於HashMap,底層使用哈希表和雙向鏈表來保存全部元素,而且它是非同步,容許使用null值和null鍵。
  2. 基本操做與父類HashMap類似,經過重寫HashMap相關方法,從新定義了數組中保存的元素Entry,來實現本身的連接列表特性。該Entry除了保存當前對象的引用外,還保存了其上一個元素before和下一個元素after的引用,從而構成了雙向連接列表。


6、LinkedHashSet
  繼承了HashSet、又基於LinkedHashMap來實現的。LinkedHashSet底層使用LinkedHashMap來保存全部元素,它繼承與HashSet,其全部的方法操做上又與HashSet相同。


7、 ArrayList
  1. ArrayList是List接口的可變數組非同步實現,並容許包括null在內的全部元素。
  2. 底層使用數組實現,該集合是可變長度數組,數組擴容時,會將老數組中的元素從新拷貝一份到新的數組中,每次數組容量增加大約是其容量的1.5倍,這種操做的代價很高。
  3. 採用了Fail-Fast機制,面對併發的修改時,迭代器很快就會徹底失敗,而不是冒着在未來某個不肯定時間發生任意不肯定行爲的風險
  4. remove方法會讓下標到數組末尾的元素向前移動一個單位,並把最後一位的值置空,方便GC。

  5. 實現:

  a)  底層使用數組實現:

private transient Object[] elementData;  

  b)構造方法: ArrayList提供了三種方式的構造器,能夠構造一個默認初始容量爲10的空列表、構造一個指定初始容量的空列表以及構造一個包含指定collection的元素的列表,這些元素按照該collection的迭代器返回它們的順序排列。

public ArrayList() {
    this(10);
}

public ArrayList(int initialCapacity) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
    this.elementData = new Object[initialCapacity];
}

public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    size = elementData.length;
    // c.toArray might (incorrectly) not return Object[] (see 6260652)
    if (elementData.getClass() != Object[].class)
        elementData = Arrays.copyOf(elementData, size, Object[].class);
}

  c) 存儲&獲取&移除(這裏列出了較爲經常使用的幾種): 

  set(int index, E element)

//用指定的元素替代此列表中指定位置上的元素,並返回之前位於該位置上的元素。
public E set(int index, E element) {
    RangeCheck(index);

    E oldValue = (E) elementData[index];
    elementData[index] = element;
    return oldValue;
}
private void RangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(
	"Index: "+index+", Size: "+size);
}

  add(E e)

// 將指定的元素添加到此列表的尾部。
public boolean add(E e) {
    ensureCapacity(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

  add(int index, E element)

// 將指定的元素添加到此列表的指定位置。
public void add(int index, E element) {
   if (index > size || index < 0)
       throw new IndexOutOfBoundsException(
	"Index: "+index+", Size: "+size);

    ensureCapacity(size+1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,
		 size - index);
   elementData[index] = element;
   size++;
}

  addAll(Collection<? extends E> c)

//按照指定collection的迭代器所返回的元素順序,將該collection中的全部元素添加到此列表的尾部。 
public boolean addAll(Collection<? extends E> c) {
       Object[] a = c.toArray();
       int numNew = a.length;
       ensureCapacity(size + numNew);  // Increments modCount
       System.arraycopy(a, 0, elementData, size, numNew);
       size += numNew;
       return numNew != 0;
}    

  get(int index)

// 獲取指定位置上的元素
public E get(int index) {
    RangeCheck(index);
    return (E) elementData[index];
}

  remove(Object o)

//移除此列表中首次出現的指定元素(若是存在, 針對null作特殊處理)
public boolean remove(Object o) {
    if (o == null) {
           for (int index = 0; index < size; index++)
	if (elementData[index] == null) {
	    fastRemove(index);
	    return true;
	}
    } else {
        for (int index = 0; index < size; index++)
	    if (o.equals(elementData[index])) {
	        fastRemove(index);
	        return true;
	    }
           }
    return false;
}

  ensureCapacity(int minCapacity) : 調整數組容量

  每當向數組中添加元素時,都要檢查添加後元素的個數是否會超出當前數組的長度,若是超出,數組將會進行擴容,以知足添加數據的需求。數組擴容經過一個公開的方法ensureCapacity(int minCapacity)來實現。在實際添加大量元素前,我也可使用ensureCapacity來手動增長ArrayList實例的容量,以減小遞增式再分配的數量。 

public void ensureCapacity(int minCapacity) {
    modCount++;
    int oldCapacity = elementData.length;
    if (minCapacity > oldCapacity) {
        Object oldData[] = elementData;
        int newCapacity = (oldCapacity * 3)/2 + 1;
   	    if (newCapacity < minCapacity)
	    newCapacity = minCapacity;
           // minCapacity is usually close to size, so this is a win:
           elementData = Arrays.copyOf(elementData, newCapacity);
    }
}

  數組進行擴容時,會將老數組中的元素從新拷貝一份到新的數組中,每次數組容量的增加大約是其原容量的1.5倍。這種操做的代價是很高的,所以在實際使用時,咱們應該儘可能避免數組容量的擴張。當咱們可預知要保存的元素的多少時,要在構造ArrayList實例時,就指定其容量,以免數組擴容的發生。或者根據實際需求,經過調用ensureCapacity方法來手動增長ArrayList實例的容量。

   ArrayList還給咱們提供了將底層數組的容量調整爲當前列表保存的實際元素的大小的功能。它能夠經過trimToSize方法來實現。代碼以下:

public void trimToSize() {
    modCount++;
    int oldCapacity = elementData.length;
    if (size < oldCapacity) {
           elementData = Arrays.copyOf(elementData, size);
    }
}

  ArrayList一樣也採用了快速失敗的機制,經過記錄modCount參數來實現。在面對併發的修改時,迭代器很快就會徹底失敗,而不是冒着在未來某個不肯定時間發生任意不肯定行爲的風險。

8、 LinkedList

  1. LinkedList是List接口的雙向鏈表非同步實現,並容許包括null在內的全部元素。
  2. 底層的數據結構是基於雙向鏈表的,該數據結構咱們稱爲節點
  3. 雙向鏈表節點對應的類Node的實例,Node中包含成員變量:prev,next,item。其中,prev是該節點的上一個節點,next是該節點的下一個節點,item是該節點所包含的值。
  4.它的查找是分兩半查找,先判斷index是在鏈表的哪一半,而後再去對應區域查找,這樣最多隻要遍歷鏈表的一半節點便可找到。

 

參考博文:http://blog.csdn.net/qq_25868207/article/details/55259978

相關文章
相關標籤/搜索