集合類咱們平時用的挺多的,今天心血來潮想看下源代碼,總結一下.數組
List、Set、Map是這個集合體系中最主要的三個接口。 List和Set繼承自Collection接口。 Map也屬於集合系統,但和Collection接口不一樣。安全
1.Collection:數據結構
(1)Collection繼承Iterable函數
int size(); boolean isEmpty(); boolean contains(Object o); Iterator<E> iterator(); Object[] toArray(); <T> T[] toArray(T[] a); boolean add(E e); boolean remove(Object o); boolean containsAll(Collection<?> c); boolean addAll(Collection<? extends E> c); boolean removeAll(Collection<?> c); boolean retainAll(Collection<?> c); void clear(); boolean equals(Object o); int hashCode();
具體怎麼實如今下面細說性能
(2)List(有序、可重複)this
List裏存放的對象是有序的,同時也是能夠重複的,List關注的是索引,擁有一系列和索引相關的方法,查詢速度快。由於往list集合裏插入或刪除數據時,會伴隨着後面數據的移動,全部插入刪除數據速度慢。加密
1)ArrayList:基於動態數組(其實底層就是個數組),便於查找,不便於增刪線程
ps:緣由是數據只要給定索引就能夠直接獲得結果,可是增刪的話,就要移動後面的全部元素code
例子:增長元素:add(E e),add(int index, E element)對象
public void add(int index, E element) { rangeCheckForAdd(index);//判斷是否大於數組下標或者小於0 ensureCapacityInternal(size + 1); // 修改次數,fail_fast機制 System.arraycopy(elementData, index, elementData, index + 1, size - index);//拷貝數組,把插入位置的後面數據日後推一位 參數含義(原數組,從原數組的目標位開始,目標數組,目標數組的起始位置,要copy的數據長度) elementData[index] = element;//插入數據 size++; }
2)LinkList:基於鏈表,便於增刪,不便於查找
ps:緣由是LinkList在內存裏面是離散的,不是連續的,並且每個元素都有下一個元素的引用,增刪的話只要修改前一個元素的引用指向增長元素,增長元素指向下一個元素.查找的話要從第一個元素找逐個找到目標元素.
例子:
先看看鏈表:
//結點元素 private static class Node<E> { E item;//值 Node<E> next;//尾元素 Node<E> prev;//頭元素 //構造方法 Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } }
仍是以add爲例子來說add()方法裏面調用下面方法:
//在最後加 void linkLast(E e) { final Node<E> l = last;//把原來的尾元素賦值給l final Node<E> newNode = new Node<>(l, e, null);//建立一個新的結點,頭元素指向原來的元素 last = newNode;//把原來的尾元素指向新建立的結點 if (l == null)//判斷是否是第一次添加結點 first = newNode;//頭元素就是建立的結點 else l.next = newNode;//末尾的尾元素建立的結點(末尾的尾元素指向本元素) size++; modCount++; }
//在中間加 listA.add(1,"rick"); void linkBefore(E e, Node<E> succ(index=1 所在的元素,下面簡稱x元素)) { // assert succ != null; final Node<E> pred = succ.prev;//拿出x元素的頭元素 final Node<E> newNode = new Node<>(pred, e, succ);//建立新結點,頭元素指向x元素的頭元素 succ.prev = newNode;//x元素的頭元素指向新建立的元素 if (pred == null)//判斷是否是第一次添加 first = newNode;//頭元素指向本元素 else pred.next = newNode;//頭元素指向新建立的元素 size++; modCount++; }
3)Vector:ArrayList的線程安全版,可是性能較低,Vector的方法都是synchronized的,因此是線程安全的。當Vector中的元素超過它的初始大小時,Vector會將它的容量翻倍。arrayList是是增長容量的一半.
4)Stack 繼承了Vector,因此他們也是基於數組的,依賴於有序得以實現
class Stack<E> extends Vector<E>
public E push(E item) {//入棧 addElement(item); return item; } public synchronized E pop() {//出棧 E obj; int len = size(); obj = peek(); removeElementAt(len - 1); return obj; } public synchronized E peek() {//獲取棧頂元素 int len = size(); if (len == 0) throw new EmptyStackException(); return elementAt(len - 1); } public boolean empty() {//判斷棧長度是否等於0 return size() == 0; } public synchronized int search(Object o) {//查找棧元素信息 int i = lastIndexOf(o); if (i >= 0) { return size() - i; } return -1; }
(3)Set(無序、不能重複)
Set裏存放的對象是無序,不能重複的,集合中的對象不按特定的方式排序,只是簡單地把對象加入集合中.
1)HashSet
//構造方法 /** * 初始容量是16,擴張係數是0.75 */ public HashSet() { map = new HashMap<>(); }
證實:set是基於HashMap的
仍是以add方法做爲例子來了解set:
//set的add()方法 private static final Object PRESENT = new Object(); public boolean add(E e) { return map.put(e, PRESENT)==null; }
以添加元素爲key,靜態變量對象PRESENT爲value.
由此咱們瞭解到:由於添加元素是key,根據map 的特性,咱們能夠推出,set是無序,不能重複的.
2)TreeSet
public TreeSet() { this(new TreeMap<E,Object>()); }
TreeSet(NavigableMap<E,Object> m) { this.m = m; }
證實:TreeSet是基於TreeMap,具體咱們先去看看map,而後再返回來看這二者具體區別在哪裏.
2.Map(鍵值對、鍵惟1、值不惟一)
Map集合中存儲的是鍵值對,鍵不能重複,值能夠重複。根據鍵獲得值,對map集合遍歷時先獲得鍵的set集合,對set集合進行遍歷,獲得相應的值。
(1)Map接口:
int size(); boolean isEmpty(); boolean containsKey(Object key); boolean containsValue(Object value); V get(Object key); V put(K key, V value); V remove(Object key); void putAll(Map<? extends K, ? extends V> m); void clear(); Set<K> keySet(); Collection<V> values();
1)HashMap
//構造函數 public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR;//初始容量,默認16 //DEFAULT_INITIAL_CAPACITY 是加載因子,threshold 是極限值 threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); table = new Entry[DEFAULT_INITIAL_CAPACITY]; init(); }
初始容量只是哈希表在建立時的容量。加載因子 是哈希表在其容量自動增長以前能夠達到多滿的一種尺度。當哈希表中的條目數超出了加載因子與當前容量的乘積時,經過調用 rehash 方法將容量翻倍。
而後來看看Entry類:
//截取部分 static class Entry<K,V> implements Map.Entry<K,V> { final K key;//鍵 V value;//值 Entry<K,V> next;//下一個元素 final int hash; *** }
HashMap 的底層是Entry數組,key是下標,由下列方法計算得出,value是Entry對象(就是插入的對象)
int hash = hash(key.hashCode()); int i = indexFor(hash, table.length);
static int indexFor(int h, int length) { return h & (length-1);//與操做 }
總結:Entry就是一個鍵值對,包含下個元素
而後咱們來說下這個hash:
hashCode方法就是根據必定的規則將與對象相關的信息(好比對象的存儲地址,對象的字段等)映射成一個數值,這個數值稱做爲散列值.感受跟文件md5加密原理相似.
爲何存在這個hashCode():(從網上看到的解釋)
考慮一種狀況,當向集合中插入對象時,如何判別在集合中是否已經存在該對象了?(注意:集合中不容許重複的元素存在)
也許大多數人都會想到調用equals方法來逐個進行比較,這個方法確實可行。可是若是集合中已經存在一萬條數據或者更多的數據,若是採用equals方法去逐一比較,效率必然是一個問題。此時hashCode方法的做用就體現出來了,當集合要添加新的對象時,先調用這個對象的hashCode方法,獲得對應的hashcode值,實際上在HashMap的具體實現中會用一個table保存已經存進去的對象的hashcode值,若是table中沒有該hashcode值,它就能夠直接存進去,不用再進行任何比較了;若是存在該hashcode值, 就調用它的equals方法與新元素進行比較,相同的話就不存了,不相同就散列其它的地址,因此這裏存在一個衝突解決的問題,這樣一來實際調用equals方法的次數就大大下降了
總得來講,就是爲了判斷實現集合不容許存在重複元素的一種比較高效的方法.
接下來咱們仍是拿put方法來看下:
public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key.hashCode());//按照必定的規則進行運算取得hash值 int i = indexFor(hash, table.length);//進行與操做獲得table(Entry的數組)的下標 //key已存在 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; }
void addEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; //註冊到table數組裏面,若是下標所在位置已經有值,就把他設置爲next table[bucketIndex] = new Entry<>(hash, key, value, e); //若是超過極限值.容量翻倍 if (size++ >= threshold) resize(2 * table.length); }
索性把hash()這個方法也說下吧:
static int hash(int h) { h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }
靜態方法,h>>>20 的意思是 h/(2^20)
2)TreeMap
首先咱們先要明白一些定義:TreeMap底層採用一棵紅黑樹(自平衡排序二叉樹,NavigableMap)來保存集合中的 Entry,每次進行增刪都要經過不斷循環來找到相應的元素,因此TreeMap比HashMap效率低,可是他的優點在於TreeMap 中的全部 Entry 老是按 key 根據指定排序規則保持有序狀態.
構造方法:
public TreeMap() { comparator = null; }
public TreeMap(Comparator<? super K> comparator) { this.comparator = comparator; }
能夠自定義排序規則,若是不指定,按照默認,compareTo()方法在Comparator接口定義,具體實現按照繼承類不一樣而不一樣.
private final Comparator<? super K> comparator; final int compare(Object k1, Object k2) { return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2) : comparator.compare((K)k1, (K)k2); }
//紅黑樹 static final class Entry<K,V> implements Map.Entry<K,V> { K key; V value; Entry<K,V> left = null; Entry<K,V> right = null; Entry<K,V> parent; boolean color = BLACK; ... }
而後咱們仍是從增長方法來看:
private transient Entry<K,V> root = null;//根節點 public V put(K key, V value) { Entry<K,V> t = root; if (t == null) {//判斷是不是第一次添加 compare(key, key); // type (and possibly null) check 類型檢查(有多是空) root = new Entry<>(key, value, null);//把第一次插入的值設爲根節點 size = 1; modCount++; return null; } //再次插入 int cmp; Entry<K,V> parent;//父類結點 // split comparator and comparable paths Comparator<? super K> cpr = comparator; if (cpr != null) { do {//循環找到增長的未知 parent = t; cmp = cpr.compare(key, t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } else { if (key == null) throw new NullPointerException(); Comparable<? super K> k = (Comparable<? super K>) key; do { parent = t; cmp = k.compareTo(t.key);//默認比較方法 if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } Entry<K,V> e = new Entry<>(key, value, parent); if (cmp < 0) parent.left = e; else parent.right = e; fixAfterInsertion(e);//修復紅黑樹 size++; modCount++; return null; }
ps:比較HashMpa()和TreeMap()的區別:
(1)HashMap:基於哈希表實現,TreeMap基於紅黑樹
(2)HashMap :適用於在Map中插入、刪除和定位元素。Treemap:適用於按天然順序或自定義順序遍歷鍵(key)。
(可能不全,之後再發現回來補充)
HashMap一般比TreeMap快一點(樹和哈希表的數據結構使然),建議多使用HashMap,在須要排序的Map時候才用TreeMap。
3)LinkedHashMap
繼承HashMap
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>{}
構造函數:
public LinkedHashMap() { super(); accessOrder = false;//false:基於插入順序 true:基於訪問順序 }
private static class Entry<K,V> extends HashMap.Entry<K,V> { Entry<K,V> before, after; Entry(int hash, K key, V value, HashMap.Entry<K,V> next) { super(hash, key, value, next); } ... }
從Entry咱們能夠看到,LinkedHashMap是基於鏈表結構,包含頭元素,尾元素,默認是按照插入元素排序,若是設置accessOrder爲true,排序基於訪問順序
4)HashTable
HashMap是Hashtable的輕量級實現(非線程安全的實現),他們都完成了Map接口,主要區別在於HashMap容許空(null)鍵值(key),因爲非線程安全,效率上可能高於Hashtable。
HashTable 繼承自Dictionary抽象類
public abstract class Dictionary<K,V> { public Dictionary() { } abstract public int size(); abstract public boolean isEmpty(); abstract public Enumeration<K> keys(); abstract public Enumeration<V> elements(); abstract public V get(Object key); abstract public V put(K key, V value); abstract public V remove(Object key); }
public synchronized V put(K key, V value) {//線程安全 if (value == null) { throw new NullPointerException(); }//值不能爲空 // Makes sure the key is not already in the hashtable. Entry tab[] = table; int hash = key.hashCode();//若是key爲null,會報錯,ps:只有對象纔有hashCode() ... }
1.HashMap容許將null做爲一個entry的key或者value,而Hashtable不容許。 2.HashMap把Hashtable的contains方法去掉了,改爲containsvalue和containsKey。由於contains方法容易讓人引發誤解。 3.Hashtable繼承自Dictionary類,而HashMap是Java1.2引進的Map interface的一個實現。 4.最大的不一樣是,Hashtable的方法是Synchronize的,而HashMap不是,在多個線程訪問Hashtable時,不須要本身爲它的方法實現同步,而HashMap 就必須爲之提供外同步。