Java 集合框架淺析

這裏只解析一些經常使用的、比較重要的一些集合類,而且做者水平有限,有些地方可能解析不到位或者解析錯誤,還望各位讀者指出錯誤。html

Collection
     List
          ArrayList
          LinkedList
          Vector
               Stack
     Queue
          Deque
               LinkedList
     Set
          SortedSet
          HashSet
          TreeSet
Map
     HashMap
     HashTable
     TreeMap
     WeakHashMap

Java集合的一個大致框架,針對經常使用的方法來對集合類進行解析(JDK1.8)node

List
List就是一個接口,繼承了接口Iterator,是爲了得到迭代器(Iterator),用於遍歷集合中的數據
定義了一些集合經常使用的方法add(0)、remove()等等git

List--ArrayList
ArrayList底層數據結構是數組實現,非線程安全
成員變量:github

transient Object[] elementData;  //這就是ArrayList類中存放數據的數組
private int size;  //記錄的是當前在數組中可插入的索引值,表示的是當前數組的元素個數,並不表示當前數組的實際大小

經常使用方法解析:算法

1.construct
  public ArrayList();     //沒有指定大小的集合,elementData = {},在第一次執行add()方法的時候,就會設置數組的大小爲10(DEFAULT_CAPACITY)
  public ArrayList(Collection<? extends E> c);  //參數一個集合,而後將參數複製到elementData上
  public ArrayList(int initialCapacity);  //參數集合大小,建立對象的時候就將數組的大小經過參數傳進來,建立數組:elementData = new Object[initialCapacity];

2.add
  public boolean add(E e);  
  //添加element以前,先調用ensureCapacityInternal(size+1),比較數組空間大小和當前的索引值,若是當前要插入的索引值大於數組的大小,就要執行grow(minCapacity)方法,
  //就是加大數組空間grow(minCapacity)是這樣實現的,將獲得oldCapacity=elementData.length,而後newCapacity=oldCapacity+(oldCapacity>>1),也就是在原來數組空間的大小之上再   
  //加上原來的一半,具體仍是得看代碼   

  public void add(int index,E element);
  //在指定的索引出插入值,首先要判斷索引是否越界,條件是(index>size || index<0),符合任意條件,就拋出索引越界異常,由於這裏是跟size比較,size是當前要插入到數組中的索引值,並不是
  //數組的實際大小,這麼作是爲了防止這個浪費空間,若是size=10,index=100(假如數組實際大小超過100),那這樣就會致使index爲10~99之間的空間浪費了

  public boolean addAll(Collection<? extends E> c);
  public void addAll(int index,Collection<? extends E> c);
  //這兩個方法與上面相似,再也不贅述

3.remove
  public E remove(inde index);
  //index >= size,拋出數組索引越界異常
  //不然:E oldValue=elementData[index],將該索引位置後面的元素(直到index=size-1)都往前移動,而且elementData[--size]=null,而後返回oldValue
  
  public boolean remove(Object o);
  //首先遍歷elementData,判斷o是否在elementData中,沒有則返回false
  //不然:記錄當前的index,而後調用fastRemove()方法

  private void fastRemove(int index);
  //這個跟remove(index)方法有些相似,也是將當前索引位置後面的元素往前移動,而且elementData[--size]=null;

List--LinkedList
LinkedList,顧名思義,其實就是一個雙向鏈表,這也代表了LinkedList能夠存儲無數個元素,只要空間容許(由於地址不連續,只要有空間,就能夠用來開闢節點,而後連上鍊表),非線程安全,非線程安全
成員變量:數組

transient int size = 0;   //當前鏈表中的元素個數
transient Node<E> first;  //表頭節點,Node是內部類,以後三個屬性,E item(值),Node<E> next(下一個節點),Node<E> prev(前一個節點);
transient Node<E> last;   //表尾節點

經常使用方法解析:安全

1.construct
  public LinkedList();    //空實現
  public LinkedList(Collection<? extends E> c);  //執行上面的空方法,而後addAll(c),將c添加到鏈表中

2.add
  public boolean add(E e);
  //直接調用linkLast(e) 方法,而後返回true
  //看一下linkLast(E e)方法,這個方法實現的就是在雙向鏈表的表尾添加一個元素,這個具體能夠去搜索一下雙向鏈表

  public void add(int index,E e);
  //首先判斷index是否越界,判斷標準就是index>=0 && index<=size
  //若是index=size,至關於在表尾添加節點,直接調用linkLast(e);
  //不然:調用linkBefore(e,node(index))函數,node(index)函數的做用是爲了獲取到當前所在索引的節點,由於鏈表每一個節點的地址不是連續的,因此沒法使用索引,這裏的index只是標識了在這個
  //鏈表中的第幾個
  //再看一下linkBefore(e,nodex(index))函數,經過node(index)獲取當前索引的節點,接下來要新增的節點就處在當前節點的前面,具體實現搜索雙向鏈表插入節點

  public boolean addAll(Collection<? extends E> c);
  public void addAll(int index,Collection<? extends E> c);
  //相似

  public void linkFirst(E e);
  //從表頭添加節點
  public void linkLast(E e);
 //從表尾添加節點

3.remove
  public E remove();
  //調用removeFirst();

  public E remove(int index);
  //判斷index的合法性
  //定位到index所在的節點,而後刪除該節點

  public boolean remove(Object o);
  //判斷o是否在鏈表中,如在鏈表中,便可刪除

  public E removeFirst();
  //刪除鏈表的頭節點

  public E removeLast();
  //刪除鏈表的尾節點

List--Vector
Vector與ArrayList相似,底層數據結構也是數組,可是Vector是線程安全的
成員變量:數據結構

protected Object[] elementData;   //存放數據的數組  //被protectd修飾的成員變量,子類可使用,例如Stack
protected int elementCount;       //數組中的實際元素個數
protected int capacityIncrement;  //數組容量的每次增量大小

經常使用方法解析;框架

1.construct
  public Vector();   --> 調用this(10);
  public Vector(int initialCapacity);  --> 調用this(initialCapacity,0),數組容量的增量沒有指定的話,就爲0,在每次給數組從新設置容量的時候,容量爲原來的2倍
  public Vector(int initialCapacity,int capacityIncrement);  每次給數組重設容量的時候,新容量=舊容量+增量
  public Vector(Collection<? extends E> c);   //將集合c中的數據複製到elementData
  
2.add
  //與ArrayList除了線程安全,其他大同小異
  public synchronized boolean add(E e);

  public void add(int index,E element);
  //調用了synchronized void insertElement(E obj,int index)

  public synchronized void addElement(E obj);

3.remove
  public synchronized void removeElementAt(inde index);
  public synchronized boolean removeElement(Object obj);
  public synchronized E remove(int index);

List--Vector--Stack
Stack繼承自Vector,是一個棧,進棧元素添加在數組最後面,出棧將最後一個元素彈出,push()和pop()方法體內調用的都是父類Vector的方法,因此也是線程安全的
經常使用方法解析:函數

1.construct
  public Stack();  //空方法實現

2.public E push(E item); 
  //實際上調用的是父類的addElement(E e)方法,並返回item

  public synchronized E pop();
  //調用peek()方法獲取棧頂元素,而後調用父類的removeElement(int index)方法刪除棧頂元素,並返回棧頂元素

  public synchronized E peek();
  //返回棧頂元素

Queue
Queue是一個接口,隊列

Queue--Deque
Deque也是一個接口,繼承了Queue接口,並定義至關多的方法

Queue--Deque--LinkedList
LinkedList實現了Deque接口,非線程安全,要建立一個隊列,就讓一個Queue引用指向LinkedList對象,隊列的特色就是先進先出,跟排隊買東西時同樣的,
LinkedList在上面已經說過了,再也不贅述

Set
Set是一個不包含重複元素的Collection接口,Set最多隻能有一個null元素

Set--SortedSet
也是一個接口

Set--HashSet
底層數據結構竟然是用HashMap實現的,也就形成了元素是無序的,也就沒法經過索引來獲取值,不包含重複數據,非線程安全
每次調用add(E e)方法的時候,都是直接把參數e看成數據容器map的key(從這能夠看出來爲何HashSet不能包含重複值了),PRESENT看成value
而且要遍歷HashSet只能經過獲取迭代器來說HashSet中的元素迭代出來
成員變量:

HashMap<E,Object> map;   //存放數據的容器
private static final Object PRESENT = new Object();   //就是爲了填充map的value的那個位置,沒有其餘任何做用

經常使用方法解析:

1.construct
  public HashSet(){
     map = new HashMap<>();  }   //hashmap沒有指定容量 
  public HashSet(int initialCapacity){
     map = new HashMap<>(initialCapacity);  }   //指定初始化容量
  public HashSet(int initialCapacity,float loadFactor){
     map = new HashMap<>(initialCapacity,loadFactor);  }   //指定初始化容量,並
  public HashSet(int initialCapacity,float loadFactor,boolean dummy){
     map = new LinkedHashMap<>(initialCapacity,loadFactor);  }
  public HashSet(Collection<? extends E> c){
     map = new HashMap<>(Math.max((int)(c.size()/.75f)+1,16));  }

2.add
  public boolean add(E e){
     return map.put(e,PRESENT) == null;
  }
  //很簡單,就是把要插入的元素看成HashMap的key,而HashMap的key是不能重複的,因此HashSet是不能包含重複值的

3.remove
  public boolean remove(Object o){
     return map.remove(o) == PRESENT;
  }

4.iterator
  public Iterator<E> iterator(){
     return map.keySet.iterator();
  }
  //經過迭代器遍歷HashSet中的元素

Map
Map是一個Key-Value的數據存儲結構,是一個接口,定義了大量的接口方法

Map--HashMap
HashMap是Map的實現類,能夠看到HashMap的底層也是使用數組實現的,但不一樣的是,數組的每一個元素又是一個鏈表的頭節點,因此HashMap的底層數據結構是數組+單向鏈表(若是鏈表的節點數量過多,則使用紅黑樹)來實現的,非線程安全
成員變量:

transient int size;   //當前數組中的元素個數   (transient關鍵字)
transient Node<K,V>[] table;  //存放數據的容器,是一個散列表(數組+單向鏈表),數組中的每一個元素都是一個鏈表的頭節點,
transient Set<Map.Entry<K,V>> entrySet;  //
final float loadFactor;  //負載因子,定義爲:散列表的實際數目/散列表的容量,若是size > loadFactor*capacity,那就須要擴容了,默認是0.75,
                         //負載因子衡量的是一個散列表的空間的使用程度,負載因子越大表示散列表的裝填程度越高,反之愈小。對於使用鏈表法的散列表來講,查找一個元素的平均時間是O(n),
                         //所以,若是負載因子越大,對空間的利用率很高,而後後果就是查找效率的下降,集中表現就是對鏈表的迭代遍歷會變慢;若是負載因子過小,那麼散列表的數據過於稀疏,對空間形成
                         //嚴重浪費
transient int modCount;  //修改次數,因爲HashMap是非線程安全的,因此若是在使用迭代器的過程當中,若是有其餘線程修改了map,那麼將拋出ConcurrentModificationException,
                         //這就是所謂的fail-fast策略,這一實現就是在內部類迭代器中添加成員變量expectedModCount來記錄當前的modCount,而後再迭代的過程當中判斷modCount和expectedModCount    
                         //是否相等,不相等就代表有其餘線程修改了該map

經常使用方法解析

1.construct
public HashMap();  //構建一個初始容量爲16,負載因子爲0.75的HashMap
public HashMap(int initialCapacity);  //構建一個初始容量爲initialCapacity,負載因子爲0.75的HashMap
public HashMap(int innitialCapacity,floadloadFactor);  //指定初始容量和負載因子建立一個HashMap

2.put
  public V put(K key,V value){
    return putVal(hash(key),key,value,false,true);  //調用putVal方法
  }

  public V putVal(int hash,K key,V value,boolean onlyIfAbsent,boolean evict);
  //1.若table是否爲空或者長度爲0,則調用resize()方法,則給數組(table)重設大小
  //2.不然獲取table的長度n,並(n-1)&hash得到該元素在table中的索引,並獲取當前索引位置的元素p,若該元素爲空,則直接將新元素放在table上
      而且若是(++size > threshold) = true,那麼調用resize()方法,重置數組大小,並返回null(由於該索引處沒有節點,因此爲null),方法結束
  //3.不然的話,若(p.hash == hash && ((k=p.key)==key || (key!=null && key.equals(k)))),這個判斷語句是在判斷原索引上的key和要插入的key是不是同一個key,
      若是是的話,則賦值爲臨時變量e
  //4.不然說明要遍歷以p爲鏈表頭節點的鏈表,遍歷過程當中每一個節點的key都要判斷和新增的key是否同樣,若同樣,賦值給臨時變量e
  //5.不然,將當前的key和value構形成一個Node<K,V>對象,而後從這個鏈表的頭節點前插入
  //6.注意還沒結束,e!=null爲true(e至關因而舊值),則返回e.value
  //7.不然 若是(++size > threshold) = true,那麼調用resize()方法,重置數組大小,並返回null(由於該索引處沒有節點,因此爲null),方法結束
  //注意:1)這裏獲取key所在table中的索引值算法,請看這個:http://pengranxiang.iteye.com/blog/543893
          2)若是鏈表過長,會將鏈表轉換成紅黑樹

3.final Node<K,V>[] resize();
  //1.若oldCap(原來數組的大小)大於MAXIMUM_CAPACITY,也就是超過最大值,那就沒辦法了,隨你去碰撞了
  //2.不然:newCap = oldCap<<1,擴大2倍

4.remove
  public V remove(Object key){
     Node<K,V> e;
     reutrn (e = removeNode(hash(key),key,null,false,true)) == null ? null : e.value;
  }

  final Node<K,V> removeNode(int hash,Object key,Object value,boolean matchValue,movable);
  //1.若table==null || table.length<=0,則直接返回null,方法結束
  //2.不然:經過((n=table.length-1) & hash)獲取到該key所在數組中的索引,並將該索引處的元素賦值給p,
      若(p.hash=hash && ((k=p.key)==key || (key !=null && key.equals(k)))) = true,說明該索引所在的位置正在是要刪除的節點,而後將該節點賦值給臨時變量node
  //3.不然:遍歷以p爲頭節點的鏈表,找到對應的節點,而後賦值給臨時變量node,將node的前一個節點賦值給p
  //注意:node是要刪除的節點,p是node的前一個節點(若是node是鏈表的頭節點,那麼p=null),鏈表的具體操做能夠看這個方法

Map--HashTable
HashTable也是Map的實現類,跟HashMap差很少,線程安全的
HashMap和HashTable的區別

1.HashMap幾乎能夠等價於Hashtable,除了HashMap是非synchronized的,並能夠接受null(HashMap能夠接受爲null的鍵值(key)和值(value),而Hashtable則不行)。

2.HashMap是非synchronized,而Hashtable是synchronized,這意味着Hashtable是線程安全的,多個線程能夠共享一個Hashtable;而若是沒有正確的同步的話,多個線程是不能共享HashMap的。Java 5提供了
  ConcurrentHashMap,它是HashTable的替代,比HashTable的擴展性更好。

3.另外一個區別是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。因此當有其它線程改變了HashMap的結構(增長或者移除元素),將會拋出
  ConcurrentModificationException,但迭代器自己的remove()方法移除元素則不會拋出ConcurrentModificationException異常。但這並非一個必定發生的行爲,要看JVM。這條一樣也是Enumeration和
  Iterator的區別。

4.因爲Hashtable是線程安全的也是synchronized,因此在單線程環境下它比HashMap要慢。若是你不須要同步,只須要單一線程,那麼使用HashMap性能要好過Hashtable。

5.HashMap不能保證隨着時間的推移Map中的元素次序是不變的。

小總結:
圖片描述
參考:HashMap
HashMap底層算法解析
HashMap
HashTable的區別

相關文章
相關標籤/搜索