Collection

集合類咱們平時用的挺多的,今天心血來潮想看下源代碼,總結一下.數組

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 就必須爲之提供外同步。 

相關文章
相關標籤/搜索