jdk源碼解析--集合類

最近在整理JAVA 基礎知識,從jdk源碼入手,今天就jdk中 java.util包下集合類進行理解

先看圖

從類圖結構可以瞭解 java.util包下的2個大類:

  1、Collecton:可以理解爲主要存放的是單個對象

  2、Map:可以理解爲主要存儲key-value類型的對象

一、Collection

  Collection繼承了Iterate接口,Iterate用於集合內迭代器抽象接口,其子類均實現接口中方法,看下ArrayList下實現:

複製代碼
 1  /**
 2      * Returns an iterator over the elements in this list in proper sequence.
 3      *
 4      * <p>The returned iterator is <a href="#fail-fast"><i>fail-fast</i></a>.
 5      *
 6      * @return an iterator over the elements in this list in proper sequence
 7      */
 8     public Iterator<E> iterator() {
 9         return new Itr(); // 返回內部類實例
10     }
11 
12     /**
13      * An optimized version of AbstractList.Itr
14      */
15     private class Itr implements Iterator<E> {
16         int cursor;       // index of next element to return 指向下一個位置索引id
17         int lastRet = -1; // index of last element returned; -1 if no such 指向上一個位置索引id
18         int expectedModCount = modCount;
19 
20         public boolean hasNext() {
21             return cursor != size;
22         }
23 
24         @SuppressWarnings("unchecked")
25         public E next() {
26             checkForComodification();
27             int i = cursor;
28             if (i >= size)
29                 throw new NoSuchElementException();
30             Object[] elementData = ArrayList.this.elementData;
31             if (i >= elementData.length)
32                 throw new ConcurrentModificationException();
33             cursor = i + 1;
34             return (E) elementData[lastRet = i];
35         }
36 
37         public void remove() {
38             if (lastRet < 0)
39                 throw new IllegalStateException();
40             checkForComodification();
41 
42             try {
43                 ArrayList.this.remove(lastRet);
44                 cursor = lastRet;
45                 lastRet = -1;
46                 expectedModCount = modCount;
47             } catch (IndexOutOfBoundsException ex) {
48                 throw new ConcurrentModificationException();
49             }
50         }
51 
52         @Override
53         @SuppressWarnings("unchecked")
54         public void forEachRemaining(Consumer<? super E> consumer) {
55             Objects.requireNonNull(consumer);
56             final int size = ArrayList.this.size;
57             int i = cursor;
58             if (i >= size) {
59                 return;
60             }
61             final Object[] elementData = ArrayList.this.elementData;
62             if (i >= elementData.length) {
63                 throw new ConcurrentModificationException();
64             }
65             while (i != size && modCount == expectedModCount) {
66                 consumer.accept((E) elementData[i++]);
67             }
68             // update once at end of iteration to reduce heap write traffic
69             cursor = i;
70             lastRet = i - 1;
71             checkForComodification();
72         }
73 
74         final void checkForComodification() {
75             if (modCount != expectedModCount)
76                 throw new ConcurrentModificationException();
77         }
78     }
複製代碼

1、List

  特點:有序結果、順序遍歷、索引、允許有重複值

  (1) ArrayList 

    以上特點實現:

複製代碼
transient Object[] elementData; // List內部存儲對象數組結果

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
}
// 添加對象時先識別是否越界,沒有越界則數組對象當前索引值的下一個添   
// 添加對象時,不識別重複,所以有序允許重複值

/**
 * Removes all of the elements from this list.  The list will
 * be empty after this call returns.
 */
public void clear() {
    modCount++;

    // clear to let GC do its work
    for (int i = 0; i < size; i++)
        elementData[i] = null;

    size = 0;
}
// 清空List時順序遍歷值置爲null

public E get(int index) {
  rangeCheck(index);

  return elementData(index);
}

E elementData(int index) {
  return (E) elementData[index];
}

複製代碼

其中 remove方法 :

複製代碼
  public E remove(int index) { // 按索引刪除對象
         rangeCheck(index); // 校驗輸入索引id是否越界,若越界則拋出運行時異常 IndexOutOfBoundsException
 
         modCount++;
         E oldValue = elementData(index);
 
         int numMoved = size - index - 1; // 定位到索引的下一位
         if (numMoved > 0)
             System.arraycopy(elementData, index+1, elementData, index,
                              numMoved); //調用native方法實現數組位置左移
         elementData[--size] = null; // clear to let GC do its work
      // 末尾元素置空
         return oldValue;
     }


 public boolean remove(Object o) {// 按對象刪除
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) { // 識別對象相等使用equals方法,使用時注意重寫equals方法
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

    /*
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
    private void fastRemove(int index) {
        modCount++; // 刪除元素時,modCount值變更
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }
複製代碼

可以看到ArrayList中對數組進行,操作時常用到System.arraycopy

java.lang.System下
public
static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);

 還有在 java.util.Arrays下數組copy方法,最終也是調用System.arraycopy方法

複製代碼
 1 public static <T> T[] copyOf(T[] original, int newLength) {
 2         return (T[]) copyOf(original, newLength, original.getClass());
 3     }
 4 
 5 public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
 6         @SuppressWarnings("unchecked")
 7         T[] copy = ((Object)newType == (Object)Object[].class)
 8             ? (T[]) new Object[newLength]
 9             : (T[]) Array.newInstance(newType.getComponentType(), newLength);
10         System.arraycopy(original, 0, copy, 0,
11                          Math.min(original.length, newLength));
12         return copy;
13     }
複製代碼

示例:

 View Code

 

可以看到出現異常:ConcurrentModificationException,出現該異常原因是: 

“快速失敗”也就是fail-fast,它是Java集合的一種錯誤檢測機制。當創建Iterator後,在Iterator使用還沒有結束時,改變(刪除或增添新項)集合元素就會出現上面的錯誤

看看ArrayList的排序方法:sort(Comparator<? super E> c)

複製代碼
 1  public void sort(Comparator<? super E> c) {
 2         final int expectedModCount = modCount;
 3         Arrays.sort((E[]) elementData, 0, size, c);
 4         if (modCount != expectedModCount) {
 5             throw new ConcurrentModificationException();
 6         }
 7         modCount++;
 8     }
 9 
10 public static <T> void sort(T[] a, int fromIndex, int toIndex,
11                                 Comparator<? super T> c) {
12         if (c == null) {
13             sort(a, fromIndex, toIndex);
14         } else {
15             rangeCheck(a.length, fromIndex, toIndex);
16             if (LegacyMergeSort.userRequested)
17                 legacyMergeSort(a, fromIndex, toIndex, c);
18             else
19                 TimSort.sort(a, fromIndex, toIndex, c, null, 0, 0);
20         }
21     }
複製代碼

 示例:

 View Code

  (2)LinkedList

複製代碼
 1 public class LinkedList<E>
 2     extends AbstractSequentialList<E>
 3     implements List<E>, Deque<E>, Cloneable, java.io.Serializable
 4   // 實現了Deque接口,可以做隊列使用
 5 
 6  /**
 7      * Pointer to first node.
 8      * Invariant: (first == null && last == null) ||
 9      *            (first.prev == null && first.item != null)
10      */
11     transient Node<E> first;
12 
13     /**
14      * Pointer to last node.
15      * Invariant: (first == null && last == null) ||
16      *            (last.next == null && last.item != null)
17      */
18     transient Node<E> last;
19 
20     /**
21      * Constructs an empty list.
22      */
23     public LinkedList() {
24     }
25  
26     // 集合對象存儲結構,通過當前節點的前後節點,維護順序集合(雙向鏈表結構)
27     private static class Node<E> {
28         E item;
29         Node<E> next;
30         Node<E> prev;
31 
32         Node(Node<E> prev, E element, Node<E> next) {
33             this.item = element;
34             this.next = next;
35             this.prev = prev;
36         }
37     }
複製代碼

以上爲 LinkedList的內部存儲結構,以Node存儲。

在看下集合元素插入、刪除及獲取方法實現:

複製代碼
 1     public boolean add(E e) {
 2         linkLast(e);
 3         return true;
 4     }
 5 
 6     /**
 7      * Links e as last element.
 8      */
 9     void linkLast(E e) {
10         final Node<E> l = last; // 保存最後個節點
11         final Node<E> newNode = new Node<>(l, e, null);// 新增節點
12         last = newNode; // 將新節點置爲最後節點
13         if (l == null)
14             first = newNode;
15         else
16             l.next = newNode;
17         size++;
18         modCount++;
19     }
20 
21      public E remove() {
22         return removeFirst(); // 去掉首節點
23     }
24 
25     public E removeFirst() {
26         final Node<E> f = first;
27         if (f == null)
28             throw new NoSuchElementException();
29         return unlinkFirst(f);
30     }
31 
32     private E unlinkFirst(Node<E> f) {
33         // assert f == first && f != null;
34         final E element = f.item;
35         final Node<E> next = f.next;
36         f.item = null;
37         f.next = null; // help GC
38         first = next;
39         if (next == null)
40             last = null;
41         else
42             next.prev = null;
43         size--;
44         modCount++;
45         return element;
46     }
47 
48     // 入棧方法
49     public void push(E e) {
50         addFirst(e);
51     }
52     // 出棧方法
53     public E pop() {
54         return removeFirst();
55     }
56 
57     // 入隊
58     public boolean offer(E e) {
59         return add(e);
60     }
61     public boolean add(E e) {
62         linkLast(e);
63         return true;
64     }
65     // 出隊
66     public E poll() {
67         final Node<E> f = first;
68         return (f == null) ? null : unlinkFirst(f);
69     }
70 
71     //隨機訪問集合對象
72      public E get(int index) {
73         checkElementIndex(index);
74         return node(index).item;
75     }
76     
77     /**
78      * Returns the (non-null) Node at the specified element index.
79      */
80     Node<E> node(int index) {
81         // assert isElementIndex(index);
82         // 識別 index id離首節點近還是尾節點近,減少遍歷  
83         if (index < (size >> 1)) {
84             Node<E> x = first;
85             for (int i = 0; i < index; i++) // 0(i)
86                 x = x.next;
87             return x;
88         } else {
89             Node<E> x = last;
90             for (int i = size - 1; i > index; i--) // 0(i)
91                 x = x.prev;
92             return x;
93         }
94     }
複製代碼

通過以上源碼理解 ArrayList 和 LinkedList 區別類似數據結構中 數組及鏈表結構區別 ,新增、刪除 和 隨機訪問存在 效率上的差別:

ArrayList是最常用的集合,其內部實現是一個數組,ArrayList的大小是可以動態擴充的。對於元素的隨機訪問效率高,其訪問的時間複雜度爲O(1),對於數據的插入與刪除,從尾部操作效率高,時間複雜度和隨機訪問一樣是O(1),若是從頭部操作則效率會比較低,因爲從頭部插入或刪除時需要移動後面所有元素,其時間複雜度爲O(n-i)(n表示元素個數,i表示元素位置)

LinkList對於隨機訪問效率是比較低的,因爲它需要從頭開始索引,所以其時間複雜度爲O(i)。但是對於元素的增刪,LinkList效率高,因爲只需要修改前後指針即可,其時間複雜度爲O(1)

  (3)Vector

    與ArrayList類型,內部也是使用數據來存儲對象,但是線程安全的,因爲實現方法重寫的時候,全部加上了同步關鍵字:synchronized;(一般不建議使用,性能消耗)

  (4)Stack

複製代碼
public
class Stack<E> extends Vector<E> {
    /**
     * Creates an empty Stack.
     */
    public Stack() {
    }
複製代碼

  2、Queue

  遵循FIFO(先入先出規則),內部出棧入棧方法,主要區別在於是否是阻塞入隊或出隊

  3、Set 

複製代碼
public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable

/**
     * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
     * default initial capacity (16) and load factor (0.75).
     */
    public HashSet() {
        map = new HashMap<>();
    }

// Set 集合對象存儲在 Map的 key中
 public boolean contains(Object o) {
        return map.containsKey(o);
    }

// 添加對象到Set集合中
 public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
// 刪除Set集合中對象
 public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }
複製代碼

實際Set集合的實現依賴於Map的實現,通過Map的 key值唯一性來實現

二、Map

  1、HashMap:基於Map接口實現、允許null 鍵值、無序、非同步

  一起看下HashMap的實現

複製代碼
// map 內部對象鏈表存儲結構
static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next; // 下一節點

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }
        // 重寫hashCode方法
        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }
        // 重寫 equals方法
        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }

transient Node<K,V>[] table; // 用數組保存多條鏈表的首節點

// 獲取 key所對應的存儲JNode的 value值
public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
// 識別是否存在key所對應的 Node
 public boolean containsKey(Object key) {
        return getNode(hash(key), key) != null;
    }
複製代碼
複製代碼
// map 內部對象鏈表存儲結構
static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next; // 下一節點

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }
        // 重寫hashCode方法
        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }
        // 重寫 equals方法
        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }

transient Node<K,V>[] table; // 用數組保存多條鏈表的首節點

// 獲取 key所對應的存儲JNode的 value值
public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
// 識別是否存在key所對應的 Node
 public boolean containsKey(Object key) {
        return getNode(hash(key), key) != null;
    }

複製代碼
複製代碼
// 插入 對象
public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

// 調用的內部方法,
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        // tab 爲map內首節點集合
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        // 先識別table是否爲空,爲空則初始化,hashmap內存存儲延遲加載在這裏體現
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        // 通過hash值以長度做按位與,識別讀取元素的存儲在tab中的位置
        if ((p = tab[i = (n - 1) & hash]) == null)
            // 若tab所在鏈表首節點爲空,則直接構造新節點
            tab[i] = newNode(hash, key, value, null);
        else {
            // tab所在鏈表首節點不爲空,則遍歷p所在鏈表或紅黑樹,找到可以存儲的位置
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }                
複製代碼

可以看到,在HashMap中存儲的結構下, Node類型的數組保存頭部節點(單鏈表)或根節點(紅黑樹),先以 Node的key的hash值與數組長度做位與運算(hash碰撞),初始

時使用單鏈表存儲新插入對象(newNode),當鏈表長度超過8時,會將鏈表結構轉爲紅黑樹結構存儲(treeifyBin方法)

複製代碼
final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        else if ((e = tab[index = (n - 1) & hash]) != null) {
        // 找到需要轉換的 單鏈表 e,遍歷單鏈表,轉換爲TreeNode,保存前後節點關係
        TreeNode<K,V> hd = null, tl = null;
            do {
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;
                else {
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            //讓桶的第一個元素指向新建的紅黑樹頭結點,以後這個桶裏的元素就是紅黑樹而不是鏈表了
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }
    }    
複製代碼

先將單鏈錶轉換爲 treenode,在調用 treeify方法構造紅黑樹

  2、LinkedHashMap

   繼承HashMap,HashMap是無序集合,而LinkedHashMap爲有序集合

public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>

 構造LinkedHashMap.EntreyNode<K,V> 繼承 HashMap.Node<K,V> 實現雙向鏈表

複製代碼
static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after; // before 保存前置節點,after保存後置節點
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }

 /**
     * The head (eldest) of the doubly linked list.
     */
    transient LinkedHashMap.Entry<K,V> head; // 頭節點

    /**
     * The tail (youngest) of the doubly linked list.
     */
    transient LinkedHashMap.Entry<K,V> tail;  // 尾部節點


// 重寫HashMap的 創建新節點方法
 Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
        LinkedHashMap.Entry<K,V> p =
            new LinkedHashMap.Entry<K,V>(hash, key, value, e);
        linkNodeLast(p); // 將新節點放到尾部節點,從而保證順序
        return p;
    }

// link at the end of list
    private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
        LinkedHashMap.Entry<K,V> last = tail;
        tail = p;
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
    }
複製代碼

  3、TreeMap

  TreeMap直接使用紅黑樹結構存儲集合元素,根據鍵 做排序,排序規則按內部 comparator 對象的實例對象的排序規則,若comparator爲空,則按自然排序

複製代碼
 1 public class TreeMap<K,V>
 2     extends AbstractMap<K,V>
 3     implements NavigableMap<K,V>, Cloneable, java.io.Serializable
 4 {
 5     /**
 6      * The comparator used to maintain order in this tree map, or
 7      * null if it uses the natural ordering of its keys.
 8      *
 9      * @serial
10      */
11     private final Comparator<? super K> comparator; // 對象比較接口
12 
13     private transient Entry<K,V> root;  // 根節點
複製代碼

root的實現邏輯爲 TreeMap.Entrey<K,V> 繼承 Map.Entrey<K,V> 實現 ,與HashMap.TreeNode<K,V>實現類似

複製代碼
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;
複製代碼

所以TreeMap put和get方法就是以鍵先進行紅黑樹的查找後操作

  4、HashTable

  HashTable和HashMap數據結構類似,主要區別爲HashTable中操作集合元素對象的方法都加上了 同步關鍵字(synchronized), 所以說線程安全的及集合

 

分類
發佈了130 篇原創文章 · 獲贊 138 · 訪問量 31萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章