這裏只解析一些經常使用的、比較重要的一些集合類,而且做者水平有限,有些地方可能解析不到位或者解析錯誤,還望各位讀者指出錯誤。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中的元素次序是不變的。