各種集合、對象初始創建默認大小


一、字符串類別(只詳細說了StringBuffer)

StringBuffer

  1、StringBuffer為線程安全的類,所有方法都使用synchronized修飾(如:public synchronized int length() {return count;})。StringBuffer的構造器有4種,底層為創建指定大小的char數組(JDK8及以前,JDK9開始將char數組修改為了byte數組)

    注:JDK9主要是因為String的底層從char數組修改為了byte數組,所以導致以String為基礎的StringBuffer和StringBuilder的底層都從char數組修改為了byte數組,動機:經過大數據統計,在創建String()對象的時候,堆中的String絕大多數是拉丁字母(ASCII表0-127的字母符號),使用byte(8位)數組即可滿足,如果使用char(16位)數組,那么將浪費一半的空間。

  如下代碼還是按JDK8的原來來做分析

  1)、無參的構造器,默認大小為16

    public StringBuffer() {
        super(16);
    }

  其中父類為AbstractStringBuilder,構造器為:

    /**
     * Creates an AbstractStringBuilder of the specified capacity.
     */
    AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }

 

  2)、指定大小的構造器

    /**
     * Constructs a string buffer with no characters in it and
     * the specified initial capacity.
     *
     * @param      capacity  the initial capacity.
     * @exception  NegativeArraySizeException  if the <code>capacity</code>
     *               argument is less than <code>0</code>.
     */
    public StringBuffer(int capacity) {
        super(capacity);
    }

 

   3)、指定字符的構造器,底層char數組長度為字符串長度+16(及最小16)

    /**
     * Constructs a string buffer initialized to the contents of the
     * specified string. The initial capacity of the string buffer is
     * <code>16</code> plus the length of the string argument.
     *
     * @param   str   the initial contents of the buffer.
     * @exception NullPointerException if <code>str</code> is <code>null</code>
     */
    public StringBuffer(String str) {
        super(str.length() + 16);
        append(str);
    }

 

   4)、指定CharSequence 的構造器,底層char數組長度為CharSequence串長度+16(及最小16)

    /**
     * Constructs a string buffer that contains the same characters
     * as the specified <code>CharSequence</code>. The initial capacity of
     * the string buffer is <code>16</code> plus the length of the
     * <code>CharSequence</code> argument.
     * <p>
     * If the length of the specified <code>CharSequence</code> is
     * less than or equal to zero, then an empty buffer of capacity
     * <code>16</code> is returned.
     *
     * @param      seq   the sequence to copy.
     * @exception NullPointerException if <code>seq</code> is <code>null</code>
     * @since 1.5
     */
    public StringBuffer(CharSequence seq) {
        this(seq.length() + 16);
        append(seq);
    }

 

  2、StringBuffer和StringBuilder的擴容,兩者的append(String value)都是使用父類AbstractStringBuilder的append方法:

  public synchronized StringBuffer append(String str) {
        super.append(str);
        return this;
    }

  父類中append的方法(下面的count表示當前數組已經存有數據的個數)

  public AbstractStringBuilder append(String str) {
        if (str == null) str = "null";
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

  在存儲數據的時候會檢查ensureCapacityInternal()當前數組的容量

  /**
     * This method has the same contract as ensureCapacity, but is
     * never synchronized.
     */
    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0)
            expandCapacity(minimumCapacity);
    }

 

   如果當前數據已有值的長度加上需要append的值的長度大於當前數組的長度(value.length表示當前數組的總長度),那么就會擴容。擴容后的大小為當前的2倍加2

    /**
     * This implements the expansion semantics of ensureCapacity with no
     * size check or synchronization.
     */
    void expandCapacity(int minimumCapacity) {
        int newCapacity = value.length * 2 + 2;
        if (newCapacity - minimumCapacity < 0)
            newCapacity = minimumCapacity;
        if (newCapacity < 0) {
            if (minimumCapacity < 0) // overflow
                throw new OutOfMemoryError();
            newCapacity = Integer.MAX_VALUE;
        }
        value = Arrays.copyOf(value, newCapacity);
    }

  在擴容過程中,擴容后的長度如果還是沒有當前需要存入新字符后的長度大,那么直接使用當前需要的長度。然后下一步在判斷是否已經超過了int的最大長度(2的31次方-1,最大正int整數) ,超過該長度就會變成一個負整數,所以是通過<0來判斷,如果超過最大int正整數,那么就報內存溢出。否則則將舊數組的內容復制到擴容后新數組中來。

 

總結:

  1)同類型的StringBuilder和StringBuffer的實現原理一樣,其父類都是AbstractStringBuilder。StringBuffer是線程安全的,StringBuilder是JDK 1.5新增的,其功能和StringBuffer類似,但是非線程安全。因此,在沒有多線程問題的前提下,使用StringBuilder會取得更好的性能。

  2)String是不可變對象,每次對String類型進行操作都等同於產生了一個新的String對象,然后指向新的String對象。所以盡量不要對String進行大量的拼接操作,否則會產生很多臨時對象,導致GC開始工作,影響系統性能。StringBuffer是對象本身操作,而不是產生新的對象,因此在有大量拼接的情況下,我們建議使用StringBuffer(線程安全)。

 

二、Map集合類別

HashTable 

  線程安全,默認長度為11,擴容為2*length+1,及在默認長度情況下,第一次擴容長度為23,第二次擴容長度為47。

  源碼解讀略。

 


 HashMap 

  非線程安全,默認長度為16,擴容為當前數組長度的2倍。

  具體擴容可以參考我另一篇文章:https://www.cnblogs.com/yanzige/p/8392142.html

 


 TreeMap

  非線程安全。繼承於AbstractMap,基於紅黑樹(Red-Black tree)實現。其映射根據鍵的自然順序進行排序,或者根據創建映射時提供的 Comparator 進行排序,具體取決於使用的構造方法。其基本操作 containsKey、get、put 和 remove 的時間復雜度是 log(n) 。TreeMap底層是樹(紅黑樹)形結構的Enrty鏈表(不是單鏈的鏈表,因為每一個Entry位置存在則他的父節點、左邊子節點和右邊子節點,所以我將它稱為樹形鏈表結構),所以沒有默認長度,也沒有擴容機制,新增一個數據,找到他key對應在樹形結構的位置,直接添加上去。

  首先看TreeMap中的Entry數據結構,key和value是為了存儲當前數據,left存小於當前節點的Entry對象(大於小於使用的都是兩個對象的key進行比較的,比較方式下面講),Right存大於當前節點的Entry對象,parent存父節點的Entry信息。

    static final class Entry<K,V> implements Map.Entry<K,V> {
        K key;
        V value;
        Entry<K,V> left;
        Entry<K,V> right;
        Entry<K,V> parent;
        boolean color = BLACK;
    
        // 下面省略了Entry里面的一些方法,只看上面的Entry屬性

  TreeMap有4中類型的構造器(由構造器可以看出,TreeMap支持兩種類型的排序,一種是自然順序的排序,另外一種就是制定比較器的排序)

  說明:自然順序的排序原理,如果key是自然數,則按照自然數的大小排序,如果key是對象,則根據key的hashcode進行排序

    // 無參構造器,默認排序比較器為空,存入數據的順序則按照自然順序
    public TreeMap() {
        comparator = null;
    }

    /**
     * 指定存放順序的比較器
     */
    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }

    /**
     * 指定了無序Map集合的構造器,將傳入的Map集合通過構造器放到TreeMap中
     */
    public TreeMap(Map<? extends K, ? extends V> m) {
        comparator = null;
        putAll(m);
    }

    /**
     * 指定了有序Map的構造器,TreeMap初始化的時候使用指定的有序Map的比較器,並將傳入的Map集合存放到TreeMap中
     */
    public TreeMap(SortedMap<K, ? extends V> m) {
        comparator = m.comparator();
        try {
            buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
        } catch (java.io.IOException cannotHappen) {
        } catch (ClassNotFoundException cannotHappen) {
        }
    }

 

下面查看put方法(默認不可以鍵值為空):

注:關於鍵和值是否可以傳入空值參考:https://blog.csdn.net/u012156116/article/details/81073570

    public V put(K key, V value) {        
        Entry<K,V> t = root;
        // 第一次存值走該處,注意存儲完后直接返回,表明根節點為默認黑,如果不是第一次看下面代碼需要校驗節點顏色fixAfterInsertion(e);
        if (t == null) {
            // TreeMap中是否允許存入空值,需要看我們的構造器中傳入的比較器是否有對空值進行處理,如果沒有處理,使用默認比較器或者有比較器但是沒有對空值進行處理,都會報空指針異常
            compare(key, key); // type (and possibly null) check
            // root存儲的是樹形結構的根Entry對象,故他的parent為空
            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;
        // 實現了比較器的使用實現后的比較器,默認的如Interger,String等類都實現了,如果是自定義類如Person/Car/Dog這種需要自己去(1)實現Comparable接口,然后重新compareTo方法或者(2)實現Comparator接口,重新compare方法
        if (cpr != null) {
            // 從根節點循環向左或者向右比較,直到找到該數據在整個樹上的位置
            do {
                // 比較的時候從根節點開始比較,向左或者向右方向比較,直到t.left節點或者t.right節點為空(注:下面比較中,除開向左或者向右(這是key不同),如果key相同,則直接將新value覆蓋舊value,並將舊value返回)
                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();
               @SuppressWarnings("unchecked")
                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);
        }
     // 將該key,value放到節點中,並將該節點的父節點信息存入節點中 Entry
<K,V> e = new Entry<>(key, value, parent); // 找到位置后,需要將它的父節點的左節點或者右節點進行更新,使整個樹在加入新節點后連為一體 if (cmp < 0) parent.left = e; else parent.right = e;
     // 修復樹的平衡 fixAfterInsertion(e);
     // 跟新map大小 size
++; modCount++; return null; }

關於TreeMap的鍵值是否可以為空的總結:

  1)當未實現 Comparator 接口時,key 不可以為null,否則拋 NullPointerException 異常;

  2)當實現 Comparator 接口時,若未對 null 情況進行判斷,則可能拋 NullPointerException 異常。如果針對null情況實現了,可以存入,但是卻不能正常使用get()訪問,只能通過遍歷去訪問。

在新增put()數據的時候,fixAfterInsertion(e)個方法會對紅黑樹進行平衡操作,舉例:第一次放15,第二次放14,13....1,如果不進行平衡的話就成了一條單鏈表(向左的單向鏈表),如果需要查詢的話就會遍歷整個鏈表。

  private void fixAfterInsertion(Entry<K,V> x) {
        x.color = RED;

        //平衡樹調整的條件:當前存入Entry非空,當前節點非根節點,上一個節點為紅色
        while (x != null && x != root && x.parent.color == RED) {
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                Entry<K,V> y = rightOf(parentOf(parentOf(x)));
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    if (x == rightOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateLeft(x);
                    }
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateRight(parentOf(parentOf(x)));
                }
            } else {
                Entry<K,V> y = leftOf(parentOf(parentOf(x)));
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    if (x == leftOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateRight(x);
                    }
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateLeft(parentOf(parentOf(x)));
                }
            }
        }
        root.color = BLACK;
    }

通過rotateLeft()左旋和rotateRight()右旋,來保證樹的平衡性,以減少樹的深度,來保證查詢的效率。

總結:TreeMap與HashMap的區別:

  數據結構不同:

    HashMap是基於哈希表,由 數組+鏈表+紅黑樹 構成。

    TreeMap是基於紅黑樹實現。

  存儲方式不同:

    HashMap是通過key的hashcode對其內容進行快速查找。

    TreeMap中所有的元素都保持着某種固定的順序。

  排列順序:

    HashMap存儲順序不固定。

    TreeMap存儲順序固定,可以得到一個有序的結果集。

 


 ConcurrentHashMap 

  線程安全,默認長度為16,擴容為當前數組長度的2倍。

  JDK8源碼解讀:https://blog.csdn.net/ddxd0406/article/details/81389583

 


 

 Collections.SynchronizedMap

  底層使用的是Map,通過構造器傳對應Map的之類,返回你線程安全的Map子類集合對象,該方式其實就是各個操作都使用synchronized進行加鎖,來保證了線程安全。

    private static class SynchronizedMap<K,V>
        implements Map<K,V>, Serializable {
        private static final long serialVersionUID = 1978198479659022715L;

        // 底層還是使用Map對象
        private final Map<K,V> m;     // Backing Map

        // 使用Object mutex作為鎖對象
        final Object mutex;        // Object on which to synchronize

        // 同構構造器,傳Map子類,實現對應Map子類的安全類
        SynchronizedMap(Map<K,V> m) {
            this.m = Objects.requireNonNull(m);
            mutex = this;
        }

        SynchronizedMap(Map<K,V> m, Object mutex) {
            this.m = m;
            this.mutex = mutex;
        }
        
        public int size() {
            // 所有操作都進行代碼塊上鎖
            synchronized (mutex) {return m.size();}
        }

        public boolean isEmpty() {
            synchronized (mutex) {return m.isEmpty();}
        }
    }    
    

 


 LinkedHashMap 

  非線程安全,父類是HashMap,所以默認長度為16,負載引子為0.75,擴容為原來長度的2倍。

  繼承於HashMap,故很多方法都和父類一樣,底層也是一個維護了一個Entry數組,每一個位置有一個Entry鏈表,但是需要注意的是,每一個位置的Entry對象(因為LinkedHashMap中了的Entry繼承於HashMap的Entry,但是在繼承過程中新增了兩個Entry對象

Entry<K,V> before, after;用來記錄每個Entry對象的上一個和下一個Entry對象)會保存插入前后的其他Entry對象的位置(該位置可能不是同一個數組下標的Entry),通過這種方式,可以將不同數據的Entry鏈表串成一整個鏈表,也就是一個雙向鏈表。

  注:下面Entry的源碼是使用的JDK8,故LinkedHashMap中的Entry繼承HashMap中的Node對象,其實可以直接將JDK8中Node對象理解為JDK7中的Entry對象(兩者結構完全一樣,都是由final int hash;final K key;V value;Node<K,V> next;組成,只是名字叫法不同而已),所以文字統稱Entry。

  /**
     * HashMap.Node subclass for normal LinkedHashMap entries.
     */
    static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }

 

  由上面的Entry代碼可以看到,LinkedHashMap中的Entry除了和HashMap中的Entry一樣的int hash, K key, V value, Node<K,V> next(用來記錄同一個數組下標的鏈表信息)四個對象,還多了Entry<K,V> before, after兩個前后的Entry,用來記錄新增數據的前后Entry(新增數據時,可能hash沖突在同一個鏈表上新增,也可能在其他數據位置或者數組其他位置的鏈表上新增)。通過該方式維護了一個雙向的鏈表。

  相對HashMap,因為LinkedHashMap將數組下標每一個位置的鏈表鏈接起來了,維護了一個雙向的鏈表,故LinkedHashMap可以記錄數據的插入順序,而HashMap則不能記錄插入順序。

 總結:

  1、HashMap、TreeMap 和LinkedHashMap 是非線程安全的。HashMap無序的Map集合,TreeMap 有序(這個順序有key值決定)的Map集合,LinkedHashMap 記錄了插入順序。

  2、HashTable 是線程安全的,但是加鎖的方式是在方法上加synchronized,及對整個需要操作的對象HashTable加鎖,可以理解為全表鎖,只有有任意一個線程訪問HashTable對象,其他線程都需要等待。故並發效率很低。

  3、HashMap、TreeMap 和ConcurrentHashMap 繼承於AbstractMap 類,HashTable 是繼承於Dictionary 類,而LinkedHashMap 是繼承於HashMap 類,他們5者都實現了Map接口。

  4、LinkedHashMap 繼承於HashMap,底層實現原理和基本操作和HashMap 一樣,只是多維護了一個雙向的鏈表,使得LinkedHashMap 能夠記錄數據的插入順序。

  5、ConcurrentHashMap默認長度也為16,擴容方式也和HashMap一樣。

    1)ConcurrentHashMap在JDK7中使用分段鎖,相比於HashTable的全表鎖,多個線程訪問ConcurrentHashMap對象,只要不是同一段的數據,可以多線程同時操作。

    2)ConcurrentHashMap在JDK8中使用原子操作(CAS)和代碼塊synchronized,故加鎖的粒度更細,並發效果更好。

 

三、Set集合類

HashSet 

  非線程安全,父類AbstractSet,底層使用HashMap實現,所有初始默認大小為16,負載因子為0.75,擴容為原來長度的兩倍

  看HashSet源碼就知道,HashSet存值的時候其實就是利用的HashMap的key,故HashMap中key的特性正好對應HashSet的特性,如:HashMap中的key值不能重復,HashSet中對象不能重復,HashMap中key值可以為空,HashSet中可以有空值等。

    /**
     * 無參構造器
     */
    public HashSet() {
        map = new HashMap<>();
    }

    /**
     * 指定集合上線的的構造器
     */
    public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }

    /**
     * 指定初始容量和負載因子的構造器
     */
    public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }

    /**
     * 指定初始容量的構造器
     */
    public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }

    /**
     * 非Public,為子類LinkedHashMap提供
     */
    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

 

從構造器我們可以發現,實例化HashSet,其實底層就是創建了HashMap,添加元素的時候就是往key中存值

    private static final Object PRESENT = new Object();
    //所有的value都為static final修飾的固定值Object對象 
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

 


 LinkedHashSet

  非線程安全,父類是HashSet,底層實現原理是LinkedHashMap。所以特性和LinkedHashMap一樣。初始默認大小為16,負載因子為0.75,擴容為原來長度的2倍。底層也維護了一個雙向的鏈表,故能記錄插入數據的順序。

  LinkedHashSet在實例化的時候就是使用HashSet中的默認修飾符的構造器構造了一個LinkedHashMap。

 


 TreeSet

  非線程安全,父類是AbstractSet,底層是用TreeMap實現,沒有初始大小和擴容機制。

總結:

      --Set接口

            --AbstractSet

                  --SortedSet接口

                        --TreeSet實現類

            --HashSet實現類

                  --LinkedHashSet實現類

  1、HashSet和TreeSet都是繼承於AbstractSet類,HashSet底層為HashMap實現,TreeSet底層為TreeMap實現。

  2、LinkedHashSet繼承HashSet,底層為LinkedHashMap實現。

  3、HashSet、TreeSet和LinkedHashSet都是非線程安全的,HashSet是無序Set集合,TreeSet是有序Set集合,LinkedHashSet記錄的插入順序。

 

四、List集合類別

ArrayList 

  非線程安全,除指定大小外默認長度為10,擴容為上次長度的1.5倍。若第一次創建對象使用無參構造器,則在put的時候將空數組(0)擴容到默認長度10。

  1、ArrayList有三種構造器

  0)首先看類中定義的變量

    /**
     * 初始容量大小
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 初始化數組
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * ArrayList對象存放數據的對象 底層為Object數組
     */
    private transient Object[] elementData;

    /**
     * ArrayList存放數據量的大小
     */
    private int size;

  

  1)無參構造器

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        super();
        this.elementData = EMPTY_ELEMENTDATA;
    } 

  在無參構造器創建對象的時候,並沒有指定容器大小,指定容器大小是在第一次add()的時候

  public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

  在ensureCapacityInternal(size + 1)方法中,放入數據的時候,會檢查該數組(ArrayList對象是否為初始化數組),如果為初始化對象的話,則將當前長度1和默認長度10比較,取最大值,然后執行ensureExplicitCapacity(minCapacity)

    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

  進入ensureExplicitCapacity()會比較當前大小和elementData.length大小,如果當前大於ArrayList中的數據長度,則進行擴容。第一次進來肯定擴容grow(minCapacity),因為當前elementData.length的值為0,所以第一次進來肯定要擴容。

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

  下面為擴容的代碼,默認將新數組長度擴充到原數組長度的1.5倍,下面代碼使用原來的長度加上原長度右移1位(>>1),即新長度=(1+0.5)原長度,並將原數組數據copy到新的數組中。如果為第一次新增add()數據的時候,oldCapacity 為0,則newCapacity = minCapacity即newCapacity =10。所以第一次擴容擴將原來長度為0的Object數組擴容到長度為10的Object數組。

注:右移一位的時候,如果末尾為0即偶數,右移一位減半,如果末尾為1即奇數,則右移一位相當於舍棄了1,然后在減半

舉例:  原長度為10,1010(原長度)+101(右移一位)=1111(10+5) 擴容后變成15

       繼續擴容 1111+111(右移一位舍棄了最右的1)=10110(15+7) 擴容后變成22

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

  在上面擴容過程中,並對長度進行校驗,判斷擴容的長度是否操作最大數組長度和最大整型(int)正整數長度,及2的31次方-1

    /**
     * The maximum size of array to allocate.
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
     * 最大整型正整數 2的31次方-1,16進制為0x7fffffff; 
     */
    public static final int   MAX_VALUE = 0x7fffffff; 
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

  

  2)指定容量的構造器

    /**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
    }

 

  3)指定上限的集合構造器

    /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    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);
    }

總結:

  1)ArrayList底層是一個Object數組,在初始化對象的時候,如果使用無參構造器,則默認大小為10,在第一次添加數據的時候設置初始大小。

  2)ArrayList擴容就是將原來的數組的數據復制到新的數組中,除第一次外,每次擴容都變成之前容量的1.5倍

  3)ArrayList底層是數組,所以數據存儲是連續的,所以它們支持用下標來訪問元素,索引數據的速度比較快

 


 Vector

  線程安全,該類基本上所有的方法都是使用synchronized修飾(如:public synchronized boolean isEmpty() {return elementCount == 0;}),故線程安全。和ArrayList一樣繼承於AbstractList<E>類,無參構造器默認容量大小為10,默認擴容為原來的2倍,Vector構造器有4個,一個無參構造器,一個指定容量大小的構造器,一個指定容量大小和超過存儲量后按指定大小擴容的構造器,還有一個指定上限的集合構造器。

  1、前三個構造器底層都是調用兩個參數的構造器:一個指定容量大小和超過存儲量后按指定大小擴容的構造器

    /**
     * Constructs an empty vector with the specified initial capacity and
     * capacity increment.
     *
     * @param   initialCapacity     the initial capacity of the vector
     * @param   capacityIncrement   the amount by which the capacity is
     *                              increased when the vector overflows
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    }

    /**
     * Constructs an empty vector with the specified initial capacity and
     * with its capacity increment equal to zero.
     *
     * @param   initialCapacity   the initial capacity of the vector
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }

    /**
     * Constructs an empty vector so that its internal data array
     * has size {@code 10} and its standard capacity increment is
     * zero.
     */
    public Vector() {
        this(10);
    }

  2、指定上限的集合構造器

    /**
     * Constructs a vector containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param c the collection whose elements are to be placed into this
     *       vector
     * @throws NullPointerException if the specified collection is null
     * @since   1.2
     */
    public Vector(Collection<? extends E> c) {
        elementData = c.toArray();
        elementCount = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
    }

  3、在擴容的時候,如果沒有使用指定擴容大小,即沒有使用上面構造器public Vector(int initialCapacity, int capacityIncrement)初始化對象,那么在擴容的時候直接擴容為原來的2倍

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

 


 LinkedList 

  非線程安全,LinkedList 底層維護了一個雙向鏈表,該鏈表有一個前向節點Node<E> first和一個后向節點Node<E> last。每次新增數據add()的時候,會在最后一個節點last后添加新節點,並且把剛添加的新節點賦值給last。如果為第一次新增,那么該新增的節點既是first節點,又是last節點。

  注:Node<E>節點里面會存一個前節點的數據、后節點的數據以及自身的值。每次新增節點后,新增節點的上一個節點數據則為新增前最末節點,新增節點的后一個節點則為空,同時會將當前新增節點的數據存入上一個節點中的下節點信息中。這樣就串為了一整個鏈表,鏈表中除了first和last節點外,每一個節點都包含上一個節點和下一個節點的信息。因為Node節點中存有前一節點和后一節點的數據,所以只要打開任意一個Node節點數據,向前可以查看該節點前的所有數據,向后可以查看該節點后的所有數據。如果該節點處於頭部,那么向后可以查看鏈表的所有數據,如果該節點處於尾部,向前可以查看該鏈表的所有數據。

  如下圖處於鏈表尾部,該節點下一個節點為空,但是向前可以查看所有數據:

 

  1、add()添加數據的方法

  add()添加數據使用方法linkLast(),將該鏈表中最末節點數據存入當前Node節點的前一個節點信息,當前Node節點的下一個節點為空,並將當前Node替換為最末節點。從添加流程代碼可以看出來,LinkedList 在添加的時候並沒有對重復數據進行處理,所以LinkedList 中是可以存儲重復數據的。添加完成后,最后將鏈表長度數據size大小加1,然后將操作次數modCount數據加1.

    public boolean add(E e) {
        linkLast(e);
        return true;
    }
 
        
    /**
     * Links e as last element.
     */
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

 

  2、下面我們看幾個remove()方法

  public E remove() {
        return removeFirst();
    }

    public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }

    public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    } 

  remove方法通過調用unlinkFirst(f)和unlinkLast(l)進行刪除,無論是刪除頭部還是尾部,或者是中間位置的元素,都需要把鏈表前后位置進行連接。

    /**
     * 刪除頭部數據first,先將該頭部數據fist的下一個數據為新的first節點,然后將first節點中的prev節點設置為空,
   * 再將原firt頭部節點數據刪除,再將原頭部節點的next數據置空。最后將鏈表長度數據size減一,將操作次數modCount加一
*/ private E unlinkFirst(Node<E> f) { // assert f == first && f != null; final E element = f.item; final Node<E> next = f.next; f.item = null; f.next = null; // help GC first = next; if (next == null) last = null; else next.prev = null; size--; modCount++; return element; } /** * 刪除尾部數據last,先將該尾部節點數據的prev取出來,並且將取出的上一個節點設置為last,在設置的同時將其中的next設置為空,
   * 然后將該尾部數據以及該節點中的prev置空。最后將鏈表長度數據size減一和操作次數modCount加一
*/ private E unlinkLast(Node<E> l) { // assert l == last && l != null; final E element = l.item; final Node<E> prev = l.prev; l.item = null; l.prev = null; // help GC last = prev; if (prev == null) first = null; else prev.next = null; size--; modCount++; return element; } /** * 刪除中間節點數據,取出該節點的next節點和prev節點數據,然后將前一個節點prev的next設置為刪除數據中的next,將后一個節點的prev節點數據的next數據設置為即將刪除數據的next數據,
   * 然后將被刪除的數據的所有數據都置空,最后將鏈表長度數據size減一和操作次數modCount加一
*/ E unlink(Node<E> x) { // assert x != null; final E element = x.item; final Node<E> next = x.next; final Node<E> prev = x.prev; if (prev == null) { first = next; } else { prev.next = next; x.prev = null; } if (next == null) { last = prev; } else { next.prev = prev; x.next = null; } x.item = null; size--; modCount++; return element; }

 


 CopyOnWriteArrayList 

  線程安全,在並發包concurrent包下,使用了ReentrantLock鎖來保證讀寫數據安全,同一時刻只能有一個線程進行寫操作,但是可以有多個線程進行並發讀數據,同時通過關鍵字volatile來保證讀數據時候的可見性,及每次數據在主內存中修改后,工作內存中讀取的數據會跟新為最新的數據,來保證讀數據的線程安全。CopyOnWriteArrayList沒有初始容量大小。

底層也是通過維護數組來存儲數據,該數組使用volatile修飾的,保證數據的可見性

private transient volatile Object[] array;

  CopyOnWriteArrayList 在讀寫的時候使用ReentrantLock來保證線程安全,為什么卻說CopyOnWriteArrayList 非絕對線程安全啦?原因在於CopyOnWriteArrayList在於為保證讀寫效率,在寫和修改的時候可以進行數據的讀取,那么在數據刪除的時候就會存在線程安全(報錯)問題,當數據在進行刪除的時候,同時在讀取該數據,刪除完成的瞬間,然后讀數據發生,就會報錯:數組下標越界。可以參考:https://www.jianshu.com/p/fc0ee3aaf2df

關於更多CopyOnWriteArrayList的相關解讀可以查考:https://www.cnblogs.com/myseries/p/10877420.html

 

總結,關於ArrayList,Vector,LinkedList和CopyOnWriteArrayList 的對比:

  1、 ArrayList和Vector底層原理一樣,都是使用Object數據,都是繼承於AbstractList<E>類,未指定初始容量的話默認都為10,擴容的時候ArrayList為原來的1.5倍,而Vector擴容為原來的2倍。其次ArrayList為線程不安全的類,而Vector為線程安全的。因為Vector使用synchronize來保證線程安全,所以在效率上和ArrayList相比要低。

  2、CopyOnWriteArrayList底層也為數組,但是沒有初始容量。CopyOnWriteArrayList線程安全,和線程安全的Vector相比,因為CopyOnWriteArrayList使用ReentrantLock鎖對指定代碼塊,能滿足多線程讀的需求,並且在寫的過程中可以進行讀數據,相比於Vector類中的synchronize對整個方法進行加鎖的方式,並發效率更高。

  3、LinkedList底層使用一個雙向鏈表,和上面兩個原理不一樣,是線程不安全的。因為LinkedList底層是鏈表,所以LinkedList在新增和刪除數據的時候效率很高,但是在查詢的時候需要依次遍歷,所以查詢效率較低。相反,ArrayList和Vector底層使用數組,內存地址是連續的,所以在查詢的時候效率很高,但是在新增和刪除的時候設計擴容(數組的復制),所以效率相比LinkedList要低。

 

特此說明:

  上面所有類的相關特性對比描述,都是根據源碼進行總結的,當然在總結之前也有看過網上一些資料,所以可能存在總結不完整或者總結有誤的地方,還請大牛和心細的網友批評指正。此文章目的主要通過對比總結,便於記憶,並沒有對每一個集合類進行深入完整的剖析。如果想要深入理解某個類底層原理和邏輯,還是需要查看源碼和網上相應的說明文章。

  此外,如果問題也可以留言交流....

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM