Java容器深入研究

xyyujnnaruz 8年前發布 | 20K 次閱讀 Java 鏈表 紅黑樹 Java開發

基本功能

Arrays & Collections

常用的方法

//Arrays.java
public static <T> List<T> asList(T... a) {
   return new ArrayList<>(a);
}

注意這里返回的ArrayList不同于我們平時使用的ArrayList,根據該 ArrayLsit 的源碼

private static class ArrayList<E> extends AbstractList<E>

知道其繼承至 AbstractList ,但是沒有實現它的 add() 和 delete() 方法,因此若調用會拋出 UnsupportedOperationException 的提示,這是由于該 List 的底層就是一個數組,而且不會擴容,所以不支持添加等操作,在使用的時候要特別注意。

List list = ...;
List lists1 = new ArrayList(list);
List lists2 = Collections.addAll(list);

上面代碼,相比 lists1 , lists2 更為高效。

集合類基本介紹

  • List 以特定順序保存的一組元素
  • Set 以特定順序保存的不重復的一組元素
  • Queue 同數據結構隊列
  • Map 使用KV保存兩組值

具體介紹

集合類圖

List

相比 Collection ,多 了一些方法,如 listIterator() 等.

ArrayList

ArrayList包含函數

概述

根據類圖可以知道 ArrayList 的繼承結構, RandomAccess 是一個說明性接口,沒有任何的方法實現. ArrayList 的底層實現任然是數組,當容量達到一定時,會新建一個數組,再把原來的數據拷貝過去,所以性能并不是太好.下面詳細的看看.

準備

由于 ArrayList 的底層是由數組實現的,并且 ArrayList 是動態大小,因此修改擴容,這里用到 Arrays.copyOf(...) 方法

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    @SuppressWarnings("unchecked")
    T[] copy = ((Object)newType == (Object)Object[].class)
          ? (T[]) new Object[newLength]
          : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    System.arraycopy(original, 0, copy, 0,
                   Math.min(original.length, newLength));  //native
    return copy;
}

源碼閱讀

transient Object[] elementData; // 為什么是Object而不是泛型E?
private int size; //實際大小 size()函數就是返回該值

它的三個構造函數的作用都是初始化上面兩個參數的值.都是非常的簡單,不多說,下面看看最常用的 add() 函數.

public boolean add(E e) {
   ensureCapacityInternal(size + 1);  // 判斷數組容量是否夠,不夠就擴容
   elementData[size++] = e;
   return true;
}

public void add(int index, E element) { rangeCheckForAdd(index); //index > size || index < 0拋異常 ensureCapacityInternal(size + 1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index);//index后的往后移一位 elementData[index] = element; size++; }

public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; }

public boolean addAll(int index, Collection<? extends E> c) { rangeCheckForAdd(index); Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount int numMoved = size - index; if (numMoved > 0) System.arraycopy(elementData, index, elementData, index + numNew, numMoved); System.arraycopy(a, 0, elementData, index, numNew); size += numNew; return numNew != 0; }</code></pre>

本身這段代碼是非常容易理解的,下面看看它擴容的實現.

private void ensureCapacityInternal(int minCapacity) {
      if (elementData == EMPTY_ELEMENTDATA) { //還沒有數據時
          minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
      }
      ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code //注意elementData.length只是表示現有的容量,不是size if (minCapacity - elementData.length > 0) grow(minCapacity); }

private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1);//增加1.5倍容量,位操作效率遠遠高于做除法 if (newCapacity - minCapacity < 0) //容量還沒有達到申請的量 newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) //Integer.MAX_VALUE - 8 newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity);//把原來的數據移動到新數組中

private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // int 溢出變為負數了 throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }</code></pre>

通過上面的代碼,可以看到 ArrayList 的最大容量為 Integer.MAX_VALUE ,即 65535 .接下來就看看同等常用的 get() 函數.

public E get(int index) {
     rangeCheck(index);  //檢查index是否在[0,size)范圍內,具體實現就這一個條件判斷
     return elementData(index); //取得元素elementData[index]
}

根據他的名稱我們很容易的了解他的功能,并且對于數組的隨機存取,這個實現太簡單了,就不必多說.下面看看 set() 方法.

public E set(int index, E element) {
    rangeCheck(index);
    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

臥槽,我都不想多說什么了,就是簡單的判斷 index 的范圍,然后就是對數組操作.函數 indexOf()/contains() 和 lastIndexOf() 都是簡單的對數組的遍歷過程,也跳過.下面看看 remove() 相關的方法.

public E remove(int index) {
    rangeCheck(index);
    modCount++;
    E oldValue = elementData(index);
    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
    return oldValue;
}

/**

  • 先遍歷查找到index,在移除 */ 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])) {
             fastRemove(index);
             return true;
         }
    
    } return false; }

private void fastRemove(int index) { 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 }</code></pre>

下面看看 retainAll() 和 removeAll() 的實現函數 batchRemove() .

private boolean batchRemove(Collection<?> c, boolean complement) {
    final Object[] elementData = this.elementData;
    int r = 0, w = 0;
    boolean modified = false;
    try {
        for (; r < size; r++)
           if (c.contains(elementData[r]) == complement)
                elementData[w++] = elementData[r]; //保留相等/或者不等的部分
    } finally {
       // Preserve behavioral compatibility with AbstractCollection,
       // even if c.contains() throws.
       if (r != size) {
           System.arraycopy(elementData, r,
                            elementData, w,
                             size - r);
            w += size - r;
        }
        if (w != size) {
            // clear to let GC do its work
            for (int i = w; i < size; i++)
                elementData[i] = null;
            modCount += size - w;
            size = w;
            modified = true;
        }
    }
     return modified;
}

下面看看排序函數 sort()

public void sort(Comparator<? super E> c) {
    final int expectedModCount = modCount;
    Arrays.sort((E[]) elementData, 0, size, c);
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
    modCount++;
}

由上面的代碼可以看出 sort() 在排序過程重中是不允許執行修改/添加等等操作的. subList() 返回一個 List ,但是這個 List 是依附在原本的 ArrayList 的,也就是說 subList() 得到的 List 其實是 ArrayList 的鏡像,當 ArrayList 修改后,取得的 subList 也會顯示出修改后的狀態.這里可以看看它的一部分實現

//構造函數
SubList(AbstractList<E> parent,
        int offset, int fromIndex, int toIndex) {
   this.parent = parent;
   this.parentOffset = fromIndex;
   this.offset = offset + fromIndex;
   this.size = toIndex - fromIndex;
   this.modCount = ArrayList.this.modCount;
}

public E get(int index) { rangeCheck(index); checkForComodification(); //從這里可以看出,它的數據是外部類ArrayList的. return ArrayList.this.elementData(offset + index); }</code></pre>

最后看看函數 listIterator() 和函數 iterator() ,他們分別返回一個雙向迭代器和單向迭代器.本質他們的遍歷過程還是數組的遍歷,想要了解詳情可以去看看具體的源碼,這里就不介紹了.

LinkedList

LinkedList包含函數

概述

inkedList 實現了 List 、 Deque 、 Cloneable 以及 Serializable 接口。其中 Deque 是雙端隊列接口,所以 LinkedList 可以當作是棧、隊列或者雙端隊隊列。在使用它的時候,通常可以把它向上轉型為 List , Queue 已達到縮小她的接口的功能(限制了不需要的方法).

源碼閱讀

由于是由鏈表實現,首先需要查看的就是結點了.

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;
    }

}</code></pre>

在 LinkedList 的內部,保存著 first 和 last 結點的引用,這樣就方便了兩端的插入刪除等操作.

transient int size = 0;
    transient Node<E> first;
    transient Node<E> last;

下面看看它的關鍵實現函數,添加結點相關函數.

//尾部添加
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++;
}

//頭部添加 private void linkFirst(E e) { final Node<E> f = first; final Node<E> newNode = new Node<>(null, e, f); first = newNode; if (f == null) last = newNode; else f.prev = newNode; size++; modCount++; }

//在某個結點前添加 void linkBefore(E e, Node<E> succ) { // assert succ != null; final Node<E> pred = succ.prev; final Node<E> newNode = new Node<>(pred, e, succ); succ.prev = newNode; if (pred == null) first = newNode; else pred.next = newNode; size++; modCount++; }</code></pre>

刪除結點相關函數.

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;

}

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; }

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; }</code></pre>

以上兩組函數實現都是非常的簡單,和數據結構書中講的都幾乎一樣,想必大家也可以看懂,就不多廢話了,而該類的其他方法多依賴于以上方法的實現.還有一個其他函數的依賴函數是 node()

//獲取第index個結點
Node<E> node(int index) {
        // assert isElementIndex(index);
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
}

可以看出他是遍歷鏈表的操作,只是因為有 first 和 last 的存在,可以稍微優化一下.

Stack

根據上面 LinkedList 的實現,其實使用 LinkedList 實現一個 Stack 是非常的容易的,可以看看實現方式.

public class Stack<T> {
    private LinkedList<T> storage = new LinkedList<T>();

 /** 入棧 */
public void push(T v) {
   storage.addFirst(v);
}

 /** 出棧,但不刪除 */
public T peek() {
   return storage.getFirst();
}

 /** 出棧 */
public T pop() {
   return storage.removeFirst();
}

 /** 棧是否為空 */
public boolean empty() {
   return storage.isEmpty();
}

 /** 打印棧元素 */
public String toString() {
   return storage.toString();
}

}</code></pre>

但是 Java 中任然提供了 Stack 類,而且實現方式與上面的完全不同,它的內部存儲結構是數組,基本的實現其實還是和 ArrayList 差不多,而且在 <<Think In Java>> 中并不建議使用它,因此這里不講了.

Map

HashMap

HasgMap包含函數

本文的代碼均來至于JDK1.8, HashMap 與前面版本的變化比較大,Android SDK V23中的是舊版本的

源碼閱讀

transient Node<K,V>[] table;
    transient Set<Map.Entry<K,V>> entrySet;
    // HashMap的閾值,用于判斷是否需要調整HashMap的容量(threshold = 容量*加載因子)   
    int threshold;
    final float loadFactor; //加載因子,注意Android SDK寫死的0.75

在這里可以看看構造函數的參數

public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        //tableSizeFor(n) Returns the smallest power of two >= its argument
        this.threshold = tableSizeFor(initialCapacity);
}

可以看出 threshold 在沒到達最大值之前是$2^n$.下面再看看常用的方法.

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) { Node<K,V>[] tab; Node<K,V> p; int n, i; //還沒有初始化數組 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; //找到要添加的位置 if ((p = tab[i = (n - 1) & hash]) == null) //如果還沒有元素,就放入 tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) //已經存在了一個相同KEY的元素 e = p; //紅黑樹,JDK1.8的優化點,當鏈表的長度大于8時,不再使用鏈表,轉為紅黑樹 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); //鏈表長度為8了,轉紅黑樹 break; } //在鏈表中找到了同KEY值得元素 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key ,已經存在的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; }

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) { 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); //形成從該節點連接的節點的樹。實現有點復雜 } }</code></pre>

關于紅黑樹的操作本身是非常復雜的,可以參考 Wiki ,接下來看看 get() 操作.

public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
}

final Node<K,V> getNode(int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k; if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { //Fiest為鏈表的首節點或紅黑樹的根節點 if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) return first; if ((e = first.next) != null) { if (first instanceof TreeNode) //在紅黑樹中查找 return ((TreeNode<K,V>)first).getTreeNode(hash, key); do { //鏈表中遍歷查找 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); } } return null; }</code></pre>

之理也只是大體說明一下 HashMap 的結構,核心的東西就是紅黑樹,它的其他方法也是大體一致,都是對鏈表和紅黑樹的操作. entrySet() 和 keySet() 也是和 List 中的 iterator 一樣,內部的操作都是由 HashMap 本身來完成.

public boolean containsValue(Object value) {
        Node<K,V>[] tab; V v;
        if ((tab = table) != null && size > 0) {
            for (int i = 0; i < tab.length; ++i) {
                for (Node<K,V> e = tab[i]; e != null; e = e.next) {
                    if ((v = e.value) == value ||
                        (value != null && value.equals(v)))
                        return true;
                }
            }
        }
        return false;
}

這個函數大家可能也會有一點困惑,為什么這里就只討論了鏈表的情況,并根據 next() 遍歷整個鏈表?其實 TreeNode 是繼承至 Node ,并且在生成紅黑樹的時候并沒有修改 next 的指向,所以通過 next() 遍歷就沒問題了.

TreeMap

TreeMap包含函數

TreeMap 的底層實現也是基于紅黑樹的.

源碼閱讀

還是老規矩,先看最常用的方法 put() .

public V put(K key, V value) {
        Entry<K,V> t = root;
        //紅黑樹為空,直接添加一個結點接OK
        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;
        //優先使用主動提供的比較器,
        //在使用該類(KEY)自帶的比較器(繼承Comparable)
        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
                    //找到key值相同的結點,覆蓋該值即可
                    return t.setValue(value);
            } while (t != null);
        }
        else {
            //key不允許為NULL
            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);
        }
        //到此找到了要插入到結點parent的子結點
        Entry<K,V> e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        //插入完成,此時的紅黑樹結構可能已經被破壞,需要重新構建
        //過程和HasmMap的其實是一樣的.了解更多可以看文章后面的參考
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
}
`

接下來看看 get() 函數.

public V get(Object key) {
        Entry<K,V> p = getEntry(key);
        return (p==null ? null : p.value);
}

final Entry<K,V> getEntry(Object key) { // Offload comparator-based version for sake of performance if (comparator != null) //自定義比較器的時候 return getEntryUsingComparator(key); if (key == null) throw new NullPointerException(); @SuppressWarnings("unchecked") Comparable<? super K> k = (Comparable<? super K>) key; Entry<K,V> p = root; //實現就是查找二叉樹查找問題 while (p != null) { int cmp = k.compareTo(p.key); if (cmp < 0) p = p.left; else if (cmp > 0) p = p.right; else return p; } } //這個函數的實現 final Entry<K,V> getEntryUsingComparator(Object key) { @SuppressWarnings("unchecked") K k = (K) key; Comparator<? super K> cpr = comparator; if (cpr != null) { Entry<K,V> p = root; while (p != null) { int cmp = cpr.compare(k, p.key); if (cmp < 0) p = p.left; else if (cmp > 0) p = p.right; else return p; } } return null; }</code></pre>

遍歷的時候調用了一個函數

final Entry<K,V> nextEntry() {
            Entry<K,V> e = next;
            if (e == null)
                throw new NoSuchElementException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            next = successor(e);  //中序遍歷的E的后一節點
            lastReturned = e;
            return e;
}

這樣輸入的數據就是按照紅黑樹中序遍歷的數據,也就是有序數據.

LinkedHashMap

LinkedHashMap函數列表

根據最上面的繼承關系圖我們知道 LinkedHashMap 繼承至 HashMap ,所以重復型的東西我就不說了, LinkedHashMap 的核心功能就是維持了原有的輸入順序或者指定為訪問順序 (LRU) .下面也是主要看看這個功能的實現.

源碼閱讀

LinkedHashMap 中的字段

// 頭部放舊結點(最久沒使用或最久放入)
    transient LinkedHashMap.Entry<K,V> head;
    // 尾部放新節點
    transient LinkedHashMap.Entry<K,V> tail;

通過上面的分析,我們知道 HashMap 的 put() 時調用了函數 newNode() ,而 LinkedHashMap 就重寫了這個方法.

//結點,相比HashMap多了before和after
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);
        }
}

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; } //內部保存了一個鏈表 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; } }</code></pre>

這樣就要求在以后的插入刪除的工作中需要額外的維護這個鏈表.另外,如果開啟了按訪問順序排序的話,在每次 get() 或者 put() 了重復數據是都會要求把訪問的結點放到鏈表尾部.

//把E移動到雙向鏈表的尾部
void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMap.Entry<K,V> last;
        if (accessOrder && (last = tail) != e) {
            LinkedHashMap.Entry<K,V> p =
                (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
            p.after = null;
            if (b == null) //P本身為首節點
                head = a;
            else
                b.after = a;
            if (a != null)
                a.before = b;
            else         //P本身為尾接點
                last = b;
            //到此P被移除了    
            if (last == null) //原本鏈表只有一個元素,移除光了
                head = p;
            else {
               //P放在最后
                p.before = last;
                last.after = p;
            }
            //修改指向末尾結點的指針
            tail = p;
            ++modCount;
        }
}

接下來就看看 LinkedHashMap 的遍歷. entrySet() 返回的是一個 LinkedEntrySet 的實例,而 LinkedEntrySet 的迭代器是 LinkedEntryIterator , LinkedEntryIterator 的 next() 方法調用 nextNode() 函數.

//構造函數
LinkedHashIterator() {
            next = head;
            expectedModCount = modCount;
            current = null;
}

final LinkedHashMap.Entry<K,V> nextNode() { LinkedHashMap.Entry<K,V> e = next; if (modCount != expectedModCount) throw new ConcurrentModificationException(); if (e == null) throw new NoSuchElementException(); current = e; next = e.after; return e; }</code></pre>

可以很明顯的看到,它的實現完全依賴于構建的鏈表,不像 HashMap 對組數和鏈表(紅黑樹)的遍歷.相比 HashMap 其實就是多了一個雙向鏈表而已.

Set

Set 是一個不包含重復元素的 Collection 。更確切地講, Set 不包含滿足 e1.equals(e2) 的元素對 e1 和 e2 ,并且最多包含一個 null 元素.

HashSet

HashSet函數列表

HashSet 的底部是使用一個 HashMap ,把值存在 HashMap 的 KEY , HashMap 的 VALUE 字段為固定的值,根據 HashMap 的 KEY 的唯一性,可以保證 HashSet 的值得唯一性.額外,有一個構造器使用的是 LinkedHashMap ,只有包權限,是后面要講的 LinkedHashSet 的實現.

//dummy 參數只是使用來區別構造函數
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
}

源碼閱讀

其實 HashSet 的源碼并沒有什么東西,都是調用 HashMap 的基本操作.下面隨便看兩個函數.

private transient HashMap<E,Object> map;

public boolean add(E e) { return map.put(e, PRESENT)==null; }

public Iterator<E> iterator() { return map.keySet().iterator(); }

public boolean contains(Object o) { return map.containsKey(o); }</code></pre>

由于這些方法前面都已經說過了,這里就不說了.

TreeSet

TreeSet函數列表

有了上面 HashSet 的介紹,可能你已經猜到 TreeSet 的實現方式是基于 TreeMap 了.將值存放在 TreeMap 的 KEY 中,保證了不會重復并且有序,最后只需要遍歷 TreeSet 的 KEY 就行了.具體的操作可以看看 TreeMap .

LinkedHashSet

它的實現是最簡單的,繼承至 HashSet ,調用前面說的特殊構造器,相當于把 HashSet 的 HashMap 換成了 LinkedHashMap ,并且按照插入順序排序.

Queue

LinkedList

這個上面已經分析過了,這里跳過。

PriorityQueue

PriorityQueue函數

準備

在數據結構的課程中,我們都學過用數組表示完全二叉樹,這里有一些固定的公式

Index(parent) = (Index(Child)-1) >> 1   //索引0開始

而優先級隊列 Priority 就是使用了數組表示最小堆,每次插入刪除都會重新排列內部數據。

最小堆,是一種經過排序的完全二叉樹

源碼閱讀

有用的字段

priavte transient Object[] queue; //內部表示最小堆的數組
private int size = 0; //實際大小

常用方法 add() 的實現

public boolean add(E e) {
    return offer(e); // add方法內部調用offer方法
}

public boolean offer(E e) { if (e == null) // 元素為空的話,拋出NullPointerException異常 throw new NullPointerException(); modCount++; int i = size; if (i >= queue.length) // 如果當前用堆表示的數組已經滿了,調用grow方法擴容 grow(i + 1); // 擴容 size = i + 1; // 元素個數+1 if (i == 0) // 堆還沒有元素的情況 queue[0] = e; // 直接給堆頂賦值元素 else // 堆中已有元素的情況 siftUp(i, e); // 重新調整堆,從下往上調整,因為新增元素是加到最后一個葉子節點 return true; }

private void siftUp(int k, E x) { if (comparator != null) // 比較器存在的情況下 siftUpUsingComparator(k, x); // 使用比較器調整 else // 比較器不存在的情況下 siftUpComparable(k, x); // 使用元素自身的比較器調整 }

private void siftUpUsingComparator(int k, E x) { while (k > 0) { // 一直循環直到父節點還存在 int parent = (k - 1) >>> 1; // 找到父節點索引,依賴完全二叉樹性質 Object e = queue[parent]; // 賦值父節點元素 if (comparator.compare(x, (E) e) >= 0) // 新元素與父元素進行比較,如果滿足比較器結果,直接跳出,否則進行調整 break; queue[k] = e; // 進行調整,新位置的元素變成了父元素 k = parent; // 新位置索引變成父元素索引,進行遞歸操作 } queue[k] = x; // 新添加的元素添加到堆中 } private void siftUpComparable(int k, E x) { ...//同上面類似 }</code></pre>

下面看看函數 remove() 的實現

public boolean remove(Object o) {
        int i = indexOf(o); //按數組索引遍歷
        if (i == -1)
            return false;
        else {
            removeAt(i);
            return true;
        }
}

private E removeAt(int i) { // assert i >= 0 && i < size; modCount++; int s = --size; if (s == i) // removed last element,移除最后的元素,該數組依舊是最小堆 queue[i] = null; else { E moved = (E) queue[s]; queue[s] = null; //數組最后一個位置置空 siftDown(i, moved); if (queue[i] == moved) { siftUp(i, moved); if (queue[i] != moved) return moved; } } return null; }

private void siftDown(int k, E x) { if (comparator != null) siftDownUsingComparator(k, x); else siftDownComparable(k, x); }

@SuppressWarnings("unchecked") private void siftDownComparable(int k, E x) { Comparable<? super E> key = (Comparable<? super E>)x; int half = size >>> 1; // loop while a non-leaf //為什么是一半?? //因為大于half的元素必然是沒有葉子節點的,這是只需要用末節點X替換要刪除的節點index(K),然后重新排序。 //而對于小于half的節點,由于存在(左)/(右)節點,用較小的節點替換要刪除的節點,這樣帶刪除節點的Index = (左)/(右)的索引,然后繼續遞歸執行,直到大于half,在用末節點替換她。 while (k < half) { int child = (k << 1) + 1; // assume left child is least Object c = queue[child]; int right = child + 1; if (right < size && ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0) c = queue[child = right]; if (key.compareTo((E) c) <= 0) break; queue[k] = c; k = child; } queue[k] = key; }</code></pre>

函數 poll() 和 remove() 的實現基本一致。

public E poll() {
        if (size == 0)
            return null;
        int s = --size;
        modCount++;
        E result = (E) queue[0];
        E x = (E) queue[s];
        queue[s] = null;
        if (s != 0)
            siftDown(0, x);
        return result;
}

其他的比如擴容函數和 ArrayList 的原理都是一樣的,這里就不說了。到此,基本的集合類的源碼大體上都說完了。

其他技術點

Java 8 default關鍵字

interface A {
     void doSomeThing();
}
static class B implements A {
     @Override
     public void doSomeThing() {
        System.out.println("B");
     }
 }
 static class C implements A {
     @Override
     public void doSomeThing() {
         System.out.println("C");
     }
 }

以上代碼如果想在 A 中添加一個函數,必然需要修改 B 和 C 的實現,但是在 Java 8 支持為接口添加一個默認的實現,這樣和抽象類就很相似了。

interface A {
     void doSomeThing();
     default void doAction() {
         System.out.println("Default");
     }
}
static class B implements A {
     @Override
     public void doSomeThing() {
        System.out.println("B");
     }
 }
 static class C implements A {
     @Override
     public void doSomeThing() {
         System.out.println("C");
     }
 }

就向上面就OK了。

Integer比較問題

System.out.println(Integer.valueOf(127)==Integer.valueOf(127));
System.out.println(Integer.valueOf(128)==Integer.valueOf(128));
System.out.println(Integer.parseInt("128")==Integer.valueOf("128"));

輸出

true

false

true

為什么會有這問題?通過源代碼

public static Integer valueOf(int i) {
   if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

代碼中的 IntegerCache.low 為固定值 -128 , IntegerCache.high 根據VM系統參數不同會有區別,默認127,因此在[-128,127]范圍內,實例化的時候是返回的同一個對象,必然相等。當 Integer 修改的時候,由于他是 不可變對象(參考String,每次修改都是重新生成對象) ,也不會出現問題。對于第三個例子, parseInt() 的返回是 int ,這時和 Integer 比較, Integer 會拆包為 int ,當然也就相等。

另外補充一點,當我們調用

Integer i = 1;

其實也是執行

Integer i = Integer.valueOf(1);

可以從反編譯中看出

//源碼
public static void main(String[] args){
  Integer i = 1;
  int r = i + 1;
}
//反編譯結果
public static void main(java.lang.String[]);
  Code:
     0: iconst_1      
     1: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     4: astore_1      
     5: aload_1       
     6: invokevirtual #3                  // Method java/lang/Integer.intValue:()I
     9: iconst_1      
    10: iadd          
    11: istore_2      
    12: return

自動拆箱調用 intValue() ,自動裝箱調用 valueOf() 。

List&Set&Map在遍歷過程中刪除添加元素錯誤

for(int i:list){
  if(i == 2){
      list.remove(Integer.valueOf(2));
   }
}

以上這段代碼會拋出 java.util.ConcurrentModificationException 異常。這是因為foreach本質還是調 list.iterator() ,這里用 ArrayList 說明。

public Iterator<E> iterator() {
     return new Itr();
}

這里返回一個迭代器,其內部參數包括

private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount; //修改次數,注意int為值傳遞
}

也就是說保存了修改的次數,在迭代器的 next() 中有檢測這個值是否被篡改(可以修改的地方包括 ArrayList 的 add(...) 和 remove() )。

final void checkForComodification() {
   if (modCount != expectedModCount)
      throw new ConcurrentModificationException();
}

解決方案是使用迭代器的 remove(...)

Iterator iterator = list.iterator();
while (iterator.hasNext()){
   Integer i = (Integer) iterator.next();
   if(i == 2){
      iterator.remove();
   }
}

instanceof 關鍵字

Object obj = null;
if(obj instanceof Object){
   System.out.println("會輸出嗎?還是崩潰");
}

上面的例子不會輸出,也不會崩潰, instanceof 會檢測左邊對象是否為 null ,若是,返回 false .

HashMap的容量為什么為$2^n$

在 put() 函數中,選取數組索引的方式為

tab[i = (n - 1) & hash]

重點關注 (n - 1) & hash ,這里的 n 是容量,若 n= $2^n$, n-1 的二進制形式為 11...11 ,做 & 運算后只有 hash 的后幾位相關,保證足夠散列,而若其他情況,下 n-1 為 01..01 ,運算后只有 hash 的后幾位中的某幾位相關,縮小了散列范圍,如 n-1 最末尾為 0 ,這樣 & 之后始終是一個偶數,導致分布過于集中.

參考

 

來自:http://www.jianshu.com/p/8d14b55fa1fb

 

 本文由用戶 xyyujnnaruz 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!