喫透Java集合

原文鏈接:(7條消息) 喫透Java集合_喫透Java-CSDN博客

 

喫透Java集合系列一:Iterable和Iterator

 

 

 

 

喫透Java集合系列二:Collection與其子接口

一:Collection接口

Collection接口是集合層次的根接口,定義了集合的通用操作,所有集合都是在Collection基礎上進行擴展的。
先看一下Collection的源碼:

  1 /**
  2  * 集合層次結構中的根接口。一個集合代表一組對象,被稱爲元素。
  3  * 一些集合允許重複的元素,而有些則不允許。一些是有序的,另一些是無序的。
  4  * JDK沒有提供這個接口的任何直接的實現:它提供更具體的子接口像 Set和 List實現。
  5  * 
  6  * 所有通用的Collection實現類(通常通過其子接口間接實現Collection)應該提供兩個“標準”構造函數:
  7  * void(無參數) 構造函數,它創建一個空集合,以及一個帶有Collection類型的單個參數的構造函數,
  8  * 它創建一個與它的參數具有相同元素的新集合。實際上,後者的構造函數允許用戶複製任何集合,生成所需實現類型的等效集合。
  9  * 沒有辦法強制執行這個約定(因爲接口不能包含構造函數),但是Java平臺庫中的所有通用Collection實現都符合。
 10  * 
 11  * 如果這個集合不支持某個操作的話,調用這些方法可能(但不是必需)拋出 UnsupportedOperationException 
 12  */
 13 
 14 public interface Collection<E> extends Iterable<E> {
 15 
 16     /**
 17     * 此方法成功完成後,可確保對象包含在集合中。
 18     * 如果集合被修改,則返回true,如果沒有更改,則返回false。
 19     */
 20     boolean add(E e)
 21 
 22     /**
 23     * 將指定集合中的所有元素添加到這個集合
 24     */
 25     boolean addAll(Collection<? extends E> c)
 26 
 27     /**
 28     * 從這個集合中移除指定元素的一個實例
 29     */
 30     boolean remove(Object o)
 31 
 32     /**
 33     * 刪除此集合中包含的所有元素的所有元素。
 34     */
 35     boolean removeAll(Collection<?> c)
 36     
 37     /**
 38     * 刪除滿足給定謂詞的這個集合的所有元素。
 39     */
 40     default boolean removeIf(Predicate<? super E> filter) {
 41         Objects.requireNonNull(filter);
 42         boolean removed = false;
 43         final Iterator<E> each = iterator();
 44         while (each.hasNext()) {
 45             if (filter.test(each.next())) {
 46                 each.remove();
 47                 removed = true;
 48             }
 49         }
 50         return removed;
 51     }
 52 
 53     /**
 54     * 從這個集合中移除所有不包含在指定集合中的元素的所有元素。
 55     */
 56     boolean retainAll(Collection<?> c)
 57     
 58     /**
 59     * 從這個集合中移除所有的元素
 60     */
 61     void clear()
 62 
 63     /**
 64     * 返回此集合中的元素的數目。如果這個集合包含多 Integer.MAX_VALUE元素,返回 Integer.MAX_VALUE。
 65     */
 66     int size();
 67     
 68     /**
 69     * 如果此Collection不包含元素,則返回true
 70     */
 71     boolean isEmpty()
 72    
 73     /**
 74     * 如果集合包含指定元素,返回 true。
 75     */
 76     boolean contains(Object o)
 77 
 78     /**
 79     * 返回 true如果這個集合包含指定集合的所有元素。
 80     */
 81     boolean containsAll(Collection<?> c)
 82     
 83     /**
 84     * 返回一個包含此集合中包含的所有元素的新數組。
 85     * 如果實現已經排序了元素,它將以與迭代器返回的順序相同的順序返回元素數組。
 86     * 返回的數組不反映集合的任何更改。 即使底層數據結構已經是一個數組,也創建一個新數組。
 87     */
 88     Object[] toArray()
 89     
 90     /**
 91     * 返回包含此集合中包含的所有元素的數組。 如果指定的數組足夠大以容納元素,則使用指定的數組
 92     * 否則將創建相同類型的數組。 如果使用指定的數組並且大於此集合,則Collection元素之後的數組元素將設置爲null。
 93     */
 94     <T> T[] toArray(T[] a)
 95    
 96     /**
 97     * 重寫Object中的equals方法
 98     */
 99     boolean equals(Object o)
100     
101     /**
102     * 重寫Object中的hashCode方法
103     */
104     int hashCode()
105     
106     /**
107     * 返回可用於訪問此Collection所包含的對象的Iterator實例。 沒有定義迭代器返回元素的順序。
108     * 只有當Collection的實例具有定義的順序時,才能按照該順序返回元素。
109     */
110     Iterator<E> iterator()
111     
112     /**
113     * 返回一個並行迭代器類Spliterator
114     */
115     default Spliterator<E> spliterator() {
116         return Spliterators.spliterator(this, 0);
117     }
118     
119     /**
120     * 這個集合中的元素的順序 Stream
121     */
122     default Stream<E> stream() {
123         return StreamSupport.stream(spliterator(), false);
124     }
125     
126     /**
127     * 返回一個可能並行 Stream與集合的來源。
128     */
129     default Stream<E> parallelStream() {
130         return StreamSupport.stream(spliterator(), true);
131     }
View Code

Collection接口是Java語言中最基本的集合接口,在JDK中沒有直接提供Collection接口的具體實現類,Collection的功能實現類主要是對它的三個更具體的子接口List、Set和Queue的具體實現類。但是在Collection接口中定義了一套通用操作的實現方法和命名規則。
List、Set、Queue接口都繼承自Collection並定義了各自不同的方法對其擴展。
二:List接口
List接口擴展了Collection,可以根據下標index對元素進行操作,每個元素都有唯一一個下標對應。
添加了功能更強大的ListIterator迭代器,可以沿任一方向遍歷List,並且在遍歷期間還可以修改List

 1 /** 
 2  * List是維護其元素的排序的集合。 List中的每個元素都有一個索引。 因此,每個元素可以被其索引訪問,第一個索引爲零。 
 3  * 通常,與集合相比,List允許重複元素,其中元素必須是唯一的。
 4  * 有序集合(也稱爲<i>序列</ i>)。 該接口的用戶可以精確控制每個元素插入到列表中的哪個位置。 
 5  * 用戶可以通過整數索引(列表中的位置)訪問元素,並搜索列表中的元素。
 6  * 
 7  * 列表允許重複元素,也允許null元素插入
 8  */  
 9 public interface List<E> extends Collection<E> {  
10     /**
11      * List作爲Collection的子接口提供了Collection接口定義的方法 
12      * 這些方法在Collection源碼中已經分析過了,就不在說明了 
13      */  
14     //
15     boolean add(E e); 
16     boolean addAll(Collection<? extends E> c); 
17     //
18     boolean remove(Object o); 
19     boolean removeAll(Collection<?> c);
20     boolean retainAll(Collection<?> c);
21     default boolean removeIf(Predicate<? super E> filter)  
22     void clear(); 
23     //
24     int size();  
25     boolean isEmpty();
26     boolean contains(Object o);
27     boolean containsAll(Collection<?> c);
28     //轉數組
29     Object[] toArray();  
30     <T> T[] toArray(T[] a);  
31     //重寫Object方法
32     boolean equals(Object o);  
33     int hashCode();
34     //迭代器 
35     Iterator<E> iterator();  
36     default Spliterator<E> spliterator()
37     
38     //同時List接口定義了一些自己的方法來實現“有序”這一功能特點 
39     /** 
40      *返回列表中指定索引的元素
41      */  
42     E get(int index);  
43     /** 
44      *設定某個列表索引的值  
45      */  
46     E set(int index, E element);  
47     /** 
48      *在指定位置插入元素,當前元素和後續元素後移 
49      *這是可選操作,類似於順序表的插入操作 
50      */  
51     void add(int index, E element); 
52     /**
53     * 在指定位置插入指定集合中的元素,當前元素和後續元素後移
54     */ 
55     boolean addAll(int index, Collection<? extends E> c);  
56     /** 
57      * 刪除指定位置的元素(可選操作)
58      */  
59     E remove(int index);  
60     /** 
61      * 獲得指定對象的最小索引位置,沒有則返回-1
62      */  
63     int indexOf(Object o);  
64     /**
65      * 獲得指定對象的最大索引位置,可以知道的是若集合中無給定元素的重複元素下 
66      * 其和indexOf返回值是一樣的
67      */  
68     int lastIndexOf(Object o);  
69     /**
70      * 一種更加強大的迭代器,支持向前遍歷,向後遍歷 
71      * 插入刪除操作,此處不解釋
72      */  
73     ListIterator<E> listIterator();  
74     ListIterator<E> listIterator(int index);  
75     /** 
76      * 返回索引fromIndex(包括)和toIndex(不包括)之間的視圖。
77      */  
78     List<E> subList(int fromIndex, int toIndex);  
79 }  
View Code

List集合特點

  • 內部元素是有序的 ,集合中每個元素都有其對應的順序索引。
  • 元素是可以重複的 ,因爲它可以通過索引來訪問指定位置的集合元素。
  • List接口有3個常用的實現類,分別是ArrayList、LinkedList、Vector。

三:Set接口

Set接口完全繼承了Collection,在Collection基礎上並沒有什麼改動,但是增加了一個約定:不包含重複元素的集合!
接下來我們看一下源碼:

/**
 * Set是不允許重複元素的數據結構。
 */
public interface Set<E> extends Collection<E> {
    /**
     * Set作爲Collection的子接口提供了Collection接口定義的方法 
     * 這些方法在Collection源碼學習中已經分析過了,就不在說明了 
     */  
   //
    boolean add(E e); 
    boolean addAll(Collection<? extends E> c); 
    //
    boolean remove(Object o); 
    boolean removeAll(Collection<?> c);
    boolean retainAll(Collection<?> c);
    default boolean removeIf(Predicate<? super E> filter)  
    void clear(); 
    //
    int size();  
    boolean isEmpty();
    boolean contains(Object o);
    boolean containsAll(Collection<?> c);
    //轉數組
    Object[] toArray();  
    <T> T[] toArray(T[] a);  
    //重寫Object方法
    boolean equals(Object o);  
    int hashCode();
    //迭代器 
    Iterator<E> iterator();  
    default Spliterator<E> spliterator()
}
View Code

Set內部元素是無序的,元素是不可以重複的。

四:Queue接口
1、Queue是在處理之前保存元素的集合。除了基本的Collection操作外,Queue還提供額外的插入、刪除和檢查操作
2、每個Queue方法都有兩種形式:(1)如果操作失敗則拋出異常,(2)如果操作失敗,則返回特殊值(null或false,具體取決於操作),接口的常規結構如下表所示。

 

 

 

/**
 * Queue是在處理之前保存元素的集合。除了基本的Collection操作外,Queue還提供額外的插入、刪除和檢查操作
 * 每個Queue方法都有兩種形式:(1)如果操作失敗則拋出異常,
 * (2)如果操作失敗,則返回特殊值(null或false,具體取決於操作),接口的常規結構如下表所示。
 * 插入操作的後一種形式是專爲使用有容量限制 Queue實現;在大多數實現中,插入操作不會失敗。
 * 
 * |            | 拋出異常         | 返回特殊值             |
 * |插入      | add(e)          | offer(e)              |
 * |刪除      | remove()        | poll()                |
 * |檢查      | element()       | peek()                |
 * 
 * 隊列通常(但不一定)以FIFO(先進先出)方式對元素進行排序,優先級隊列除外,
 * 它們根據元素的值對元素進行排序 — 有關詳細信息,請參閱“對象排序”部分。
 * 無論使用什麼排序,隊列的頭部都是通過調用remove或poll移除的元素。
 * 在FIFO隊列中,所有新元素都插入隊列的尾部,其他類型的隊列可能使用不同的放置規則,
 * 每個Queue實現都必須指定其排序屬性。
 *
 * Queue實現可以限制它所擁有的元素數量,這樣的隊列被稱爲有界,java.util.concurrent中的某些Queue實現是有界的,但java.util中的實現不是。
 * 可不只有拋出unchecked異常添加元素。的offer方法設計時使用的失敗是正常的,
 * 而不是特殊的情況,例如,固定容量(或“有界”)隊列。的remove()和poll()方法移除並返回隊列的頭部。
 * 從隊列中刪除的是隊列的排序策略的函數,它不同於從“實現”到“實現”的功能。的remove()和poll()方法
 * 只是他們的行爲當隊列爲空的不同的remove()方法拋出一個異常,而poll()方法返回null。
 *
 * Queue從Collection繼承的add方法插入一個元素,除非它違反了隊列的容量限制,
 * 在這種情況下它會拋出IllegalStateException。offer方法,僅用於有界隊列,
 * 與add不同之處僅在於它通過返回false來表示插入元素失敗。
 *
 * remove和poll方法都移除並返回隊列的頭部,確切地移除哪個元素是隊列的排序策略的函數,
 * 僅當隊列爲空時,remove和poll方法的行爲纔有所不同,在這些情況下,
 * remove拋出NoSuchElementException,而poll返回null。
 *
 *element和peek方法返回但不移除隊列的頭部,它們之間的差異與remove和poll的方式完全相同:如果隊列爲空,則element拋出NoSuchElementException,而peek返回null。
 * 隊列實現通常不允許插入null元素,爲實現Queue而進行了改進的LinkedList實現是一個例外,
 * 由於歷史原因,它允許null元素,但是你應該避免利用它,因爲null被poll和peek方法用作特殊的返回值。
 *隊列實現通常不定義equals和hashCode方法的基於元素的版本,而是從Object繼承基於標識的版本。
 * Queue接口不定義阻塞隊列方法,這在併發編程中很常見,這些等待元素出現或空間可用的方法在java.util.concurrent.BlockingQueue接口中定義,該接口擴展了Queue。
 *
 */
public interface Queue<E> extends Collection<E> {
    /**
     * 將指定的元素插入到此隊列中,如果不違反容量限制立即執行此操作 
     * 成功後返回true,如果當前沒有可用空間,則拋出IllegalStateException。
     */
    boolean add(E e);

    /**
     * 如果在不違反容量限制的情況下立即執行,則將指定的元素插入到此隊列中。 
     * 當使用容量限制隊列時,此方法通常優於add(E) ,這可能無法僅通過拋出異常來插入元素。 
     * 成功返回true,失敗返回false
     */
    boolean offer(E e);

    /**
     * 檢索並刪除此隊列的頭。 此方法與poll不同之處在於,如果此隊列爲空,它將拋出異常。
     */
    E remove();

    /**
     * 檢索並刪除此隊列的頭,如果此隊列爲空,則返回 null 。
     */
    E poll();

    /**
     * 檢索,但不刪除,這個隊列的頭。 此方法與peek的不同之處在於,如果此隊列爲空,它將拋出異常。
     */
    E element();

    /**
     * 檢索但不刪除此隊列的頭部,如果此隊列爲空,則返回 null 。
     */
    E peek();
}
View Code

Queue接口特點:

  先進先出的數據結構,即從容器的一端放入對象,從另一端取出,並且對象放入容器的順序與取出的順序是相同的。
  雖然Queue接口繼承Collection接口,但是Queue接口中的方法都是獨立的,在創建具有Queue功能的實現時,不需要使用Collection方法。

喫透Java集合系列三:ArrayList

前言
本篇作爲喫透Java集合系列第三篇,我們來看一下ArrayList,通過本篇我們要明白如下問題:
1、ArrayList擴容機制
2、ArrayList迭代器實現
3、fail-fast機制
4、ArrayList序列化反序列化機制
5、ArrayList clone實現

ArrayList內部是使用動態數組實現的,換句話說,ArrayList封裝了對內部數組的操作,比如向數組中添加、刪除、插入新的元素或者數據的擴展和重定向。

繼承了AbstractList,此類提供 List 接口的骨幹實現,以最大限度地減少實現”隨機訪問”數據存儲(如數組)支持的該接口所需的工作.對於連續的訪問數據(如鏈表),應優先使用 AbstractSequentialList,而不是此類。
實現了List接口,意味着ArrayList元素是有序的,可以重複的,可以有null元素的集合.
實現了RandomAccess接口標識着其支持隨機快速訪問,實際上,我們查看RandomAccess源碼可以看到,其實裏面什麼都沒有定義.因爲ArrayList底層是數組,那麼隨機快速訪問是理所當然的,訪問速度O(1)。
實現了Cloneable接口,標識着可以它可以被複制.注意,ArrayList裏面的clone()複製其實是淺複製。
實現了Serializable 標識着集合可被序列化

一:ArrayList擴容機制
初始化:
ArrayList提供了三個構造函數來對elementData數組初始化:
無參構造函數:初始化一個空的數組,添加元素時再對數組elementData擴容。
指定容量的構造函數:直接初始化數組爲指定的大小。
帶有一個集合參數的構造函數:把指定集合中的數據通過Arrays.copyOf拷貝到elementData中,容量和指定集合容量相同。

private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
//無參構造函數直接賦值一個空的數組
 public ArrayList() {
        super();
        this.elementData = EMPTY_ELEMENTDATA;
    }
//指定大小的構造函數
public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
    }
//構造一個包含指定*集合的元素的列表。
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);
    }
View Code

擴容:
添加元素時使用 ensureCapacityInternal() 方法來保證容量足夠,如果不夠時,需要使用 grow() 方法進行擴容,新容量的大小爲 oldCapacity + (oldCapacity >> 1),也就是舊容量的 1.5 倍。

擴容操作需要調用 Arrays.copyOf() 把原數組整個複製到新數組中,這個操作代價很高,因此最好在創建 ArrayList 對象時就指定大概的容量大小,減少擴容操作的次數。

二:ArrayList迭代器實現
如果對Iterable和Iterator接口不是很清楚的,請先移步到第一篇文章:
喫透Java集合系列一:Iterable和Iterator
ArrayList通過內部類實現Iterator接口來實例化迭代器類,通過Iterator我們可以實現對elementData中的元素迭代遍歷。而ArrayList又實現了一種功能更爲強大的ListIterator來實現迭代遍歷。ListIterator繼承於Iterator接口,對Iterator接口做了擴展,支持向前向後遍歷、迭代過程中去修改集合等

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;

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

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

private class ListItr extends Itr implements ListIterator<E> {
        ListItr(int index) {
            super();
            cursor = index;
        }

        public boolean hasPrevious() {
            return cursor != 0;
        }

        public int nextIndex() {
            return cursor;
        }

        public int previousIndex() {
            return cursor - 1;
        }

        @SuppressWarnings("unchecked")
        public E previous() {
            checkForComodification();
            int i = cursor - 1;
            if (i < 0)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i;
            return (E) elementData[lastRet = i];
        }

        public void set(E e) {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.set(lastRet, e);
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        public void add(E e) {
            checkForComodification();

            try {
                int i = cursor;
                ArrayList.this.add(i, e);
                cursor = i + 1;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    }
View Code

三: Fail-Fast機制
modCount 用來記錄 ArrayList 結構發生變化的次數。結構發生變化是指添加或者刪除至少一個元素的所有操作,或者是調整內部數組的大小,僅僅只是設置元素的值不算結構發生變化。

在進行序列化或者迭代等操作時,需要比較操作前後 modCount 是否改變,如果改變了需要拋出 ConcurrentModificationException(也就是 Fail-Fast機制來保證不允許出現在序列化或者迭代的時候進行增刪修改操作)。如下例子

public static void main(String[] args) {
        List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
        Iterator<Integer> iterator = list.listIterator();
        while (iterator.hasNext()) {
            Integer i = iterator.next();
            if (i == 1) {
                list.remove(i);
            }
        }
    }

運行後會拋出異常:

Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:886)
at java.util.ArrayList$Itr.next(ArrayList.java:836)
at MyTest.main(MyTest.java:12)


當我們調用list.remove的方法來刪除元素後,此時modCount會+1,導致modCount和迭代器裏面的expectedModCount 不相等,當遍歷下一個元素調用next方法時,會先調用checkForComodification()方法,當expectedModCount!=modCount時會拋出ConcurrentModificationException,這就是Fail-Fast機制。

那我們要如何避免此問題呢?Iterator已經爲我們提供了remove方法,所以我們只需要調用迭代器裏面的remove方法就可以了,Iterator中的remove方法移除元素後會把modCount重寫賦值給expectedModCount,下一個循環時expectedModCount與modCount相等就避免此問題。如下例子:

public static void main(String[] args) {
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
Iterator<Integer> iterator = list.listIterator();
while (iterator.hasNext()) {
Integer i = iterator.next();
if (i == 1) {
iterator.remove();
}
}
}
View Code

四: ArrayList序列化機制

我們看到ArrayList實現了Serializable接口,那麼證明可以是被序列化的,但是elementData數組又被transient關鍵字修飾,我們知道被transient修飾的成員屬性變量不被序列化。

。ArrayList實際上是動態數組,每次在放滿以後自動增長設定的長度值,如果數組自動增長長度設爲100,而實際只放了一個元素,那就會序列化99個null元素。爲了保證在序列化的時候不會將這麼多null同時進行序列化,ArrayList把元素數組設置爲transient

五: ArrayList clone機制

ArrayList的clone實現,其實是通過數組元素拷貝來實現的淺拷貝(地址指向同一塊內存),很簡單,我們看一下源碼就行了:

public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }

喫透Java集合系列四:LinkedList

一:LinkedList
LinkedList類是List接口的實現類,它是一個集合,可以根據索引來隨機的訪問集合中的元素,還實現了Deque接口,它還是一個隊列,可以當成雙端隊列來使用。
雖然LinkedList是一個List集合,但是它的實現方式和ArrayList是完全不同的,ArrayList的底層是通過一個動態的Object[]數組實現的,而LinkedList的底層是通過雙向鏈表來實現的,因此它的隨機訪問速度是比較差的,但是它的刪除,插入操作很快。

  • LinkedList是基於雙向循環鏈表實現的,除了可以當作鏈表操作外,它還可以當作棧、隊列和雙端隊列來使用。
  • LinkedList同樣是非線程安全的,只在單線程下適合使用。
  • LinkedList允許null插入。
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable

實現List接口,具有List集合的特性。
實現Deque接口,具有隊列的特性。
實現Cloneable接口,可以通過clone來實現淺拷貝
實現Serializable接口,可以序列化,通過writeObject和readObject自定義序列化。
LinkedList的底層結構如下圖所示

LinkedList的底層結構如下圖所示
在這裏插入圖片描述
F表示頭結點引用,L表示尾結點引用,鏈表的每個結點都有三個元素,分別是前繼結點引用§,結點元素的值(E),後繼結點的引用(N)。結點由內部類Node表示,我們看看它的內部結構。

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

Node這個內部類其實很簡單,只有三個成員變量和一個構造器,item表示結點的值,next爲下一個結點的引用,prev爲上一個結點的引用,通過構造器傳入這三個值。接下來再看看LinkedList的成員變量和構造器。

    /**
    * 集合元素個數
    */
    transient int size = 0;

    /**
     * 頭結點
     */
    transient Node<E> first;

    /**
     * 尾節點
     */
    transient Node<E> last;

    /**
     * 無參構造器
     */
    public LinkedList() {
    }

    /**
     * 傳入外部集合的構造器
     */
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }
View Code

LinkedList持有頭結點的引用和尾結點的引用,它有兩個構造器,一個是無參構造器,一個是傳入外部集合的構造器。與ArrayList不同的是LinkedList沒有指定初始大小的構造器。

二:LinkedList的List特性

    //
   /**
    * 將指定的元素追加到此列表的末尾
    */
    public boolean add(E e) {
        linkLast(e);
        return true;
    }
   /**
    * 在此列表中的指定位置插入指定的元素。
    */
    public void add(int index, E element) {
        checkPositionIndex(index);

        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }
   /**
    * 將指定集合中的所有元素追加到此列表的末尾。
    */
    public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }
   /**
    * 將指定集合中的所有元素插入到此列表中,從指定的位置開始。
    */
    public boolean addAll(int index, Collection<? extends E> c) {
        checkPositionIndex(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        if (numNew == 0)
            return false;

        Node<E> pred, succ;
        if (index == size) {
            succ = null;
            pred = last;
        } else {
            succ = node(index);
            pred = succ.prev;
        }

        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            Node<E> newNode = new Node<>(pred, e, null);
            if (pred == null)
                first = newNode;
            else
                pred.next = newNode;
            pred = newNode;
        }

        if (succ == null) {
            last = pred;
        } else {
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;
        modCount++;
        return true;
    }
   /**
    * 頭插入
    */
    public void addFirst(E e) {
        linkFirst(e);
    }
   /**
    * 尾插入
    */
    public void addLast(E e) {
        linkLast(e);
    }

    //
   /**
    * 從列表中刪除指定元素的第一個出現(如果存在)。
    */
    public boolean remove(Object o) {
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }
   /**
    * 刪除該列表中指定位置的元素。
    */
    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }
   /**
    * 從此列表中刪除並返回第一個元素。
    */
    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);
    }

    //
   /**
    * 用指定的元素替換此列表中指定位置的元素。
    */
    public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }

    //
   /**
    * 返回此列表中指定位置的元素。
    */
    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }
View Code

LinkedList的添加元素的方法主要是調用linkLast和linkBefore兩個方法,linkLast方法是在鏈表後面鏈接一個元素,linkBefore方法是在鏈表中間插入一個元素。LinkedList的刪除方法通過調用unlink方法將某個元素從鏈表中移除。下面我們看看鏈表的插入和刪除操作的核心代碼。

    /**
     * 返回指定位置的節點
     */
    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;
        }
    }
    /**
     * 連接到第一個元素
     */
    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 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++;
    }

    /**
     * 在succ前插入元素e
     */
    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++;
    }

    /**
     * 去掉頭結點
     */
    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;
    }

    /**
     * 去掉指定的節點
     */
    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;
    }
View Code

三:LinkedList的Queue特性

通過對雙向鏈表的操作還可以實現單項隊列,雙向隊列和棧的功能。
單向隊列操作:

//獲取頭結點
public E peek() {
   final Node<E> f = first;
   return (f == null) ? null : f.item;
}

//獲取頭結點
public E element() {
   return getFirst();
}

//彈出頭結點
public E poll() {
   final Node<E> f = first;
   return (f == null) ? null : unlinkFirst(f);
}

//移除頭結點
public E remove() {
   return removeFirst();
}

//在隊列尾部添加結點
public boolean offer(E e) {
   return add(e);
}

 

雙向隊列操作:

//在頭部添加
public boolean offerFirst(E e) {
   addFirst(e);
   return true;
}

//在尾部添加
public boolean offerLast(E e) {
   addLast(e);
   return true;
}

//獲取頭結點
public E peekFirst() {
   final Node<E> f = first;
   return (f == null) ? null : f.item;
}

//獲取尾結點
public E peekLast() {
   final Node<E> l = last;
   return (l == null) ? null : l.item;
}

棧操作:

//入棧
public void push(E e) {
   addFirst(e);
}

//出棧
public E pop() {
   return removeFirst();
}
View Code

不管是單向隊列還是雙向隊列還是棧,其實都是對鏈表的頭結點和尾結點進行操作,它們的實現都是基於addFirst(),addLast(),removeFirst(),removeLast()這四個方法。

LinkedList是基於雙向鏈表實現的,不論是增刪改查方法還是隊列和棧的實現,都可通過操作結點實現
LinkedList無需提前指定容量,因爲基於鏈表操作,集合的容量隨着元素的加入自動增加
LinkedList刪除元素後集合佔用的內存自動縮小,無需像ArrayList一樣調用trimToSize()方法
LinkedList的所有方法沒有進行同步,因此它也不是線程安全的,應該避免在多線程環境下使用

喫透Java集合系列五:Vector和Stack

 

一:Vector分析
Vector 是線程安全的動態數組同 ArrayList 一樣繼承自 AbstractList 且實現了 List、RandomAccess、Cloneable、Serializable 接口。
內部實現依然基於數組,Vector 與 ArrayList 基本是一致的,唯一不同的是 Vector 是線程安全的,會在可能出現線程安全的方法前面加上 synchronized 關鍵字。
其和 ArrayList 類似,隨機訪問速度快,插入和移除性能較差(數組原因),支持 null 元素,有順序,元素可以重複,線程安全。

擴容
Vector 在 capacityIncrement 大於 0 時擴容 capacityIncrement 大小,否則擴容爲原始容量的 2 倍,而ArrayList 在默認數組容量不夠時默認擴展是 1.5 倍。
增刪改查
Vector是JDK1.0時已經有的,而List框架是1.2時纔出現的,所以Vector在List接口定義的增刪改查以外還有他自己定義的增刪改查方法

迭代器實現
Vector中迭代器實現和ArrayList中一樣的,只不過Vector中爲了保證線程安全,在方法體裏面加了synchronized關鍵字。

克隆實現
Vector克隆實現和ArrayList的實現一致,都是通過數組元素拷貝來實現的淺拷貝

爲什麼現在都不提倡使用 Vector 了?

因爲 Vector 實現併發安全的原理是在每個操作方法上加鎖,這些鎖並不是必須要的,在實際開發中一般都是通過鎖一系列的操作來實現線程安全,也就是說將需要同步的資源放一起加鎖來保證線程安全,如果多個 Thread 併發執行一個已經加鎖的方法,但是在該方法中又有 Vector 的存在,Vector 本身實現中已經加鎖了,雙重鎖會造成額外的開銷,即 Vector 同 ArrayList 一樣有 fail-fast 問題(即無法保證遍歷安全),所以在遍歷 Vector 操作時又得額外加鎖保證安全,還不如直接用 ArrayList 加鎖性能好,所以在 JDK 1.5 之後推薦使用 java.util.concurrent 包下的併發類。此外 Vector 是一個從 JDK1.0 就有的古老集合,那時候 Java 還沒有提供系統的集合框架,所以在 Vector 裏提供了一些方法名很長的方法(如 addElement(Object obj),實際上這個方法和 add(Object obj) 沒什麼區別),從 JDK1.2 以後 Java 提供了集合框架,然後就將 Vector 改爲實現 List 接口,從而導致 Vector 裏有一些重複的方法。

二、Stack分析
通過繼承Vector類,Stack類可以很容易的實現他本身的功能。因爲大部分的功能在Vector裏面已經提供支持了。
在Java中Stack類表示後進先出(LIFO)的對象堆棧。棧是一種非常常見的數據結構,它採用典型的先進後出的操作方式完成的。
Stack通過五個操作對Vector進行擴展,允許將向量視爲堆棧。這個五個操作如下:

  • empty() 測試堆棧是否爲空。
  • peek() 查看堆棧頂部的對象,但不從堆棧中移除它。
  • pop() 移除堆棧頂部的對象,並作爲此函數的值返回該對象。
  • push(E item) 把項壓入堆棧頂部。
  • search(Object o) 返回對象在堆棧中的位置,以 1 爲基數。

喫透Java集合系列六:HashSet、LinkedHashSet和TreeSet

一:HashSet

HashSet 繼承於AbstractSet 該類提供了Set 接口的骨架實現,以最大限度地減少實現此接口所需的工作量。
實現Set接口,標誌着內部元素是無序的,元素是不可以重複的。
實現Cloneable接口,標識着可以它可以被複制。
實現Serializable接口,標識着可被序列化
HashSet內部是以HashMap的key來保存元素的

    //使用HashMap的Key來保存HashSet中所有元素。
    private transient HashMap<E,Object> map;

    //定義一個Object對象作爲HashMap的value
    private static final Object PRESENT = new Object();
View Code

構造函數

   /**
    * 構造一個空的hashSet,實際底層會初始化一個空的HashMap,並使用默認初始容量爲16和加載因子0.75。
    */
    public HashSet() {
        map = new HashMap<>();
    }

    /**
     * 構造一個包含指定集合中元素的新集合。HashMap是使用默認加載因子(0.75)和足以包含指定
     * 集合中元素的初始容量創建的。
     */
    public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }

    /**
     * 以指定的initialCapacity和loadFactor構造一個空的HashSet
     * 實際底層以相應的參數構造一個空的HashMap。
     */
    public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }

    /**
     * 以指定的initialCapacity構造一個空的HashSet。
     * 實際底層以相應的參數及加載因子loadFactor爲0.75構造一個空的HashMap。
     */
    public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }

    /**
     * 以指定的initialCapacity和loadFactor構造一個新的空鏈接哈希集合。
     * 此構造函數爲包訪問權限,不對外公開,實際只是是對LinkedHashSet的支持。
     * 實際底層會以指定的參數構造一個空LinkedHashMap實例來實現。
     */
    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }
View Code
    /**
     * 返回此set中的元素的數量(set的容量)。
     * 底層實際調用HashMap的size()方法返回Entry的數量,就得到該Set中元素的個數。
     */
    public int size() {
        return map.size();
    }

    /**
     * 如果此set不包含任何元素,則返回true。 
     * 底層實際調用HashMap的isEmpty()判斷該HashSet是否爲空。
     */
    public boolean isEmpty() {
        return map.isEmpty();
    }

    /**
     * 如果此set包含指定元素,則返回true。
     * 底層實際調用HashMap的containsKey判斷是否包含指定key。
     */
    public boolean contains(Object o) {
        return map.containsKey(o);
    }

    /**
     * 如果此set中尚未包含指定元素,則添加指定元素。
     * 底層實際將將該元素作爲key放入HashMap。
     */
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

    /**
     * 如果指定元素存在於此set中,則將其移除。 
     * 底層實際調用HashMap的remove方法刪除指定Entry。
     */
    public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }

    /**
     * 從此set中移除所有元素。
     * 底層實際調用HashMap的clear方法清空Entry中所有元素。
     */
    public void clear() {
        map.clear();
    }

    /**
     * 返回此HashSet實例的淺表副本:並沒有複製這些元素本身。 
     * 底層實際調用HashMap的clone()方法,獲取HashMap的淺表副本,並設置到HashSet中。
     */
    @SuppressWarnings("unchecked")
    public Object clone() {
        try {
            HashSet<E> newSet = (HashSet<E>) super.clone();
            newSet.map = (HashMap<E, Object>) map.clone();
            return newSet;
        } catch (CloneNotSupportedException e) {
            throw new InternalError(e);
        }
    }

    /**
     * 自定義序列化實現
     */
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        // Write out any hidden serialization magic
        s.defaultWriteObject();

        // Write out HashMap capacity and load factor
        s.writeInt(map.capacity());
        s.writeFloat(map.loadFactor());

        // Write out size
        s.writeInt(map.size());

        // Write out all elements in the proper order.
        for (E e : map.keySet())
            s.writeObject(e);
    }
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // Read in any hidden serialization magic
        s.defaultReadObject();

        // Read capacity and verify non-negative.
        int capacity = s.readInt();
        if (capacity < 0) {
            throw new InvalidObjectException("Illegal capacity: " +
                                             capacity);
        }

        // Read load factor and verify positive and non NaN.
        float loadFactor = s.readFloat();
        if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
            throw new InvalidObjectException("Illegal load factor: " +
                                             loadFactor);
        }

        // Read size and verify non-negative.
        int size = s.readInt();
        if (size < 0) {
            throw new InvalidObjectException("Illegal size: " +
                                             size);
        }
        capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
                HashMap.MAXIMUM_CAPACITY);
        SharedSecrets.getJavaOISAccess()
                     .checkArray(s, Map.Entry[].class, HashMap.tableSizeFor(capacity));

        // Create backing HashMap
        map = (((HashSet<?>)this) instanceof LinkedHashSet ?
               new LinkedHashMap<E,Object>(capacity, loadFactor) :
               new HashMap<E,Object>(capacity, loadFactor));

        // Read in all elements in the proper order.
        for (int i=0; i<size; i++) {
            @SuppressWarnings("unchecked")
                E e = (E) s.readObject();
            map.put(e, PRESENT);
        }
    }

    public Spliterator<E> spliterator() {
        return new HashMap.KeySpliterator<E,Object>(map, 0, -1, 0, 0);
    }
方法
  • HashSet就是限制了功能的HashMap,所以瞭解HashMap的實現原理,這個HashSet自然就通
  • 對於HashSet中保存的對象,主要要正確重寫equals方法和hashCode方法,以保證放入Set對象的唯一性
  • 雖說時Set是對於重複的元素不放入,倒不如直接說是底層的Map直接把原值替代了(這個Set的put方法的返回值真有意思)
  • HashSet沒有提供get()方法,願意是同HashMap一樣,Set內部是無序的,只能通過迭代的方式獲得

二:LinkedHashSet
LinkedHashSet是HashSet的一個“擴展版本”,HashSet並不管什麼順序,不同的是LinkedHashSet會維護“插入順序”。HashSet內部使用HashMap對象來存儲它的元素,而LinkedHashSet內部使用LinkedHashMap對象來存儲和處理它的元素。
源碼如下

public class LinkedHashSet<E>
    extends HashSet<E>
    implements Set<E>, Cloneable, java.io.Serializable {

    private static final long serialVersionUID = -2851667679971038690L;

    public LinkedHashSet(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor, true);
    }
    
    public LinkedHashSet(int initialCapacity) {
        super(initialCapacity, .75f, true);
    }

    public LinkedHashSet() {
        super(16, .75f, true);
    }

    public LinkedHashSet(Collection<? extends E> c) {
        super(Math.max(2*c.size(), 11), .75f, true);
        addAll(c);
    }

    @Override
    public Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, Spliterator.DISTINCT | Spliterator.ORDERED);
    }
}
View Code

從源碼中我們可以注意到,LinkedHashSet繼承於HashSet,只包含4個構造函數,這4個構造函數調用的是同一個父類的構造函數。我們來看一下父類中的這個構造函數:

HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
View Code


這個構造函數需要初始容量,負載因子和一個boolean類型的啞值(沒有什麼用處的參數,作爲標記,譯者注)等參數。這個啞參數只是用來區別這個構造函數與HashSet的其他擁有初始容量和負載因子參數的構造函數。
這個構造函數內部初始化了一個LinkedHashMap對象,這個對象恰好被LinkedHashSet用來存儲它的元素。
LinkedHashSet並沒有自己的方法,所有的方法都繼承自它的父類HashSet,因此,對LinkedHashSet的所有操作方式就好像對HashSet操作一樣。
唯一的不同是內部使用不同的對象去存儲元素。在HashSet中,插入的元素是被當做HashMap的鍵來保存的,而在LinkedHashSet中被看作是LinkedHashMap的鍵。

三:TreeSet
我們知道TreeMap是一個有序的二叉樹,那麼同理TreeSet同樣也是一個有序的,它的作用是提供有序的Set集合TreeSet中的元素支持2種排序方式:自然排序 或者 根據創建TreeSet 時提供的 Comparator 進行排序。這取決於使用的構造方法。
通過源碼我們知道TreeSet基礎AbstractSet,實現NavigableSet、Cloneable、Serializable接口。
其中AbstractSet提供 Set 接口的骨幹實現,從而最大限度地減少了實現此接口所需的工作。
NavigableSet是擴展的 SortedSet,具有了爲給定搜索目標報告最接近匹配項的導航方法,這就意味着它支持一系列的導航方法。比如查找與指定目標最匹配項。Cloneable支持克隆,Serializable支持序列化。

public class TreeSet<E> extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, java.io.Serializable
{
    //使用NavigableMap來保存TreeSet元素
    private transient NavigableMap<E,Object> m;

    // 與NavigableMap中的對象關聯的虛擬值
    private static final Object PRESENT = new Object();

    /**
     * 構造由指定的NavigableMap的集合。
     */
    TreeSet(NavigableMap<E,Object> m) {
        this.m = m;
    }

    /**
     * 構造一個新的空TreeSet,根據元素的自然排序進行排序。 插入到集合中的所有元素都必須實現Comparable接口。 
     * 此外,所有這些元素必須可以相互比較 如果用戶嘗試向違反此約束的集合添加元素,那麼add調用將拋出一個
     * ClassClassException。
     */
    public TreeSet() {
        this(new TreeMap<E,Object>());
    }

    /**
     * 構造一個新的空TreeSet,根據指定的比較器進行排序。 插入到集合中的所有元素必須與指定的比較器可相互比較
     * 如果用戶嘗試向違反此約束的集合添加元素,那麼add調用將拋出ClassCastException。
     */
    public TreeSet(Comparator<? super E> comparator) {
        this(new TreeMap<>(comparator));
    }

    /**
     *構造一個新的TreeSet,其中包含指定集合中的元素,並根據元素的 自然排序 進行排序。 
     *插入到集合中的所有元素都必須實現 Comparable接口。 此外,所有這些元素必須可以相互比較
     */
    public TreeSet(Collection<? extends E> c) {
        this();
        addAll(c);
    }

    /**
     * 構造一個包含相同元素並使用與指定的排序集相同順序的TreeSet。
     */
    public TreeSet(SortedSet<E> s) {
        this(s.comparator());
        addAll(s);
    }

    /**
     * 以升序返回此集合中元素的迭代器。
     */
    public Iterator<E> iterator() {
        return m.navigableKeySet().iterator();
    }

    /**
     * 以降序返回此集合中元素的迭代器。
     */
    public Iterator<E> descendingIterator() {
        return m.descendingKeySet().iterator();
    }

    /**
     * @since 1.6
     */
    public NavigableSet<E> descendingSet() {
        return new TreeSet<>(m.descendingMap());
    }

    /**
     * 返回此集合中元素的數量(基數)。返回此集合中元素的數量。
     */
    public int size() {
        return m.size();
    }

    /**
     * 返回TreeSet是否爲空
     */
    public boolean isEmpty() {
        return m.isEmpty();
    }

    /**
     * 返回TreeSet是否包含對象(o)
     */
    public boolean contains(Object o) {
        return m.containsKey(o);
    }

    /**
     * 添加e到TreeSet中
     */
    public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    }

    /**
     * 刪除TreeSet中的對象o
     */
    public boolean remove(Object o) {
        return m.remove(o)==PRESENT;
    }

    /**
     * 清空TreeSet
     */
    public void clear() {
        m.clear();
    }

    /**
     * 將集合c中的全部元素添加到TreeSet中
     */
    public  boolean addAll(Collection<? extends E> c) {
        // Use linear-time version if applicable
        if (m.size()==0 && c.size() > 0 &&
            c instanceof SortedSet &&
            m instanceof TreeMap) {
            SortedSet<? extends E> set = (SortedSet<? extends E>) c;
            TreeMap<E,Object> map = (TreeMap<E, Object>) m;
            Comparator<?> cc = set.comparator();
            Comparator<? super E> mc = map.comparator();
            if (cc==mc || (cc != null && cc.equals(mc))) {
                map.addAllForTreeSet(set, PRESENT);
                return true;
            }
        }
        return super.addAll(c);
    }

    /**
     *  返回子Set,實際上是通過TreeMap的subMap()實現的。
     */
    public NavigableSet<E> subSet(E fromElement, boolean fromInclusive,
                                  E toElement,   boolean toInclusive) {
        return new TreeSet<>(m.subMap(fromElement, fromInclusive,
                                       toElement,   toInclusive));
    }

    /**
     * 返回Set的頭部,範圍是:從頭部到toElement。
     * inclusive是是否包含toElement的標誌
     */
    public NavigableSet<E> headSet(E toElement, boolean inclusive) {
        return new TreeSet<>(m.headMap(toElement, inclusive));
    }

    /**
     * 返回Set的尾部,範圍是:從fromElement到結尾。
     * inclusive是是否包含fromElement的標誌
     */
    public NavigableSet<E> tailSet(E fromElement, boolean inclusive) {
        return new TreeSet<>(m.tailMap(fromElement, inclusive));
    }

    /**
     * 返回子Set。範圍是:從fromElement(包括)到toElement(不包括)。
     */
    public SortedSet<E> subSet(E fromElement, E toElement) {
        return subSet(fromElement, true, toElement, false);
    }

    /**
     * 返回Set的頭部,範圍是:從頭部到toElement(不包括)。
     */
    public SortedSet<E> headSet(E toElement) {
        return headSet(toElement, false);
    }

    /**
     * 返回Set的尾部,範圍是:從fromElement到結尾(不包括)。
     */
    public SortedSet<E> tailSet(E fromElement) {
        return tailSet(fromElement, true);
    }

    //返回Set的比較器
    public Comparator<? super E> comparator() {
        return m.comparator();
    }

    /**
     * 返回Set的第一個元素
     */
    public E first() {
        return m.firstKey();
    }

    /**
     * 返回Set的最後一個元素
     */
    public E last() {
        return m.lastKey();
    }

    // NavigableSet API methods

    /**
     * 返回Set中小於e的最大元素
     */
    public E lower(E e) {
        return m.lowerKey(e);
    }

    /**
     *返回Set中小於/等於e的最大元素
     */
    public E floor(E e) {
        return m.floorKey(e);
    }

    /**
     *返回Set中大於/等於e的最小元素
     */
    public E ceiling(E e) {
        return m.ceilingKey(e);
    }

    /**
     * 返回Set中大於e的最小元素
     */
    public E higher(E e) {
        return m.higherKey(e);
    }

    /**
     * 獲取第一個元素,並將該元素從TreeMap中刪除。
     */
    public E pollFirst() {
        Map.Entry<E,?> e = m.pollFirstEntry();
        return (e == null) ? null : e.getKey();
    }

    /**
     * 獲取最後一個元素,並將該元素從TreeMap中刪除。
     */
    public E pollLast() {
        Map.Entry<E,?> e = m.pollLastEntry();
        return (e == null) ? null : e.getKey();
    }

    /**
     *克隆一個TreeSet,並返回Object對象
     */
    @SuppressWarnings("unchecked")
    public Object clone() {
        TreeSet<E> clone;
        try {
            clone = (TreeSet<E>) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new InternalError(e);
        }

        clone.m = new TreeMap<>(m);
        return clone;
    }

    /**
     * java.io.Serializable的寫入函數
     *將TreeSet的“比較器、容量,所有的元素值”都寫入到輸出流中
     */
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        // Write out any hidden stuff
        s.defaultWriteObject();

        // Write out Comparator
        s.writeObject(m.comparator());

        // Write out size
        s.writeInt(m.size());

        // Write out all elements in the proper order.
        for (E e : m.keySet())
            s.writeObject(e);
    }

    /**
     *  java.io.Serializable的讀取函數:根據寫入方式讀出
     *  先將TreeSet的“比較器、容量、所有的元素值”依次讀出
     */
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // Read in any hidden stuff
        s.defaultReadObject();

        // Read in Comparator
        @SuppressWarnings("unchecked")
            Comparator<? super E> c = (Comparator<? super E>) s.readObject();

        // Create backing TreeMap
        TreeMap<E,Object> tm = new TreeMap<>(c);
        m = tm;

        // Read in size
        int size = s.readInt();

        tm.readTreeSet(size, s, PRESENT);
    }
    public Spliterator<E> spliterator() {
        return TreeMap.keySpliteratorFor(m);
    }

    private static final long serialVersionUID = -2479143000061671589L;
}
View Code

喫透Java集合系列七:PriorityQueue

一:PriorityQueue實現方式
Java中PriorityQueue實現了Queue接口,不允許放入null元素其通過堆實現,具體說是通過完全二叉樹(complete binary tree)實現的小頂堆(任意一個非葉子節點的權值,都不大於其左右子節點的權值),也就意味着可以通過數組來作爲PriorityQueue的底層實現。

 

 

 

 

喫透Java集合系列八:Map

 

Map接口分析
關於Map接口,JDK是這樣描述的:

Map是一個有鍵值對映射的對象,map不能包含相同的key,每一個key至多能映射一個value。
Map替代了Dictionary這個類,Dictionary是抽象類而非接口,替代原因:接口總是優於抽象類。
Map接口提供了3個集合視圖,包括:keys的set集合, values的集合;,key-value的set集合,注意:values集合不是set類型,因爲value可相同。
Map返回元素的順序,取決於map對應的某個集合視圖迭代器的順序,一些map的實現,比如TreeMap類,對於map返回元素的順序有特殊的規定,其它的map實現類,比如HashMap類,就沒有特殊的規定。
不要把異變的對象作爲Map的key,如果map中的一個key發生了改變,並且影響了equals()方法的使用,那麼map並不會提示我們。
所有的通用map實現類都應該提供兩個"標準"的構造器函數,一個是無參且返回類型爲void的函數,另一個就是僅含有一個參數且類型爲map類型的的構造方法,這個方法會使用其參數構造一個新的map,且新的map和參數map有相同的key和value,但是,map接口這裏沒辦法強制執行這一建議(因爲接口裏面不能包含構造器函數),不過JDK裏面所有通用的map實現類都是符合這一點要求的。
一些map的實現類在key和value的取值上面會有一些規定,比如,一些map實現類不允許key或者value爲null。
map接口是java集合框架中的一個成員。
集合框架接口定義了很多種和equals()方法相關的實現,比如,containsKey()方法,當前僅當map包含了鍵k的定義是:key = = null ? k == null : key.equals(k),這一規範的寫法,不能被理解爲爲:如果調用方法使用的是一個非null參數的話,然後只是再調用key.equals(k)方法就可以了,具體實現可以通過避免調用equals()方法來實現優化,比如,可以先比較兩個key的哈希值,(哈希值保證了,如果兩個對象的哈希值都不相同,那麼這兩個對象肯定不會相同),更一般的情況是,大量集合框架接口的實現類可以充分利用底層對象(Object)的方法的優勢,只要實現者認爲他們這麼做是合理的。

public interface Map<K,V> {
    //
    /**
     * put方法是將指定的key-value存儲到map裏面的操作.如果map之前包含了一個此key對應的映射,
     * 那麼此key對應的舊value值會被新的value值替換.
     */
    V put(K key, V value);
    /**
     * putAll方法是將一個指定map的映射拷貝到當前map.這一操作類似於將指定map的key-value對通過put方法一個個拷貝過來。
     * 在拷貝過程中,如果指定的這個map被更改了,那麼這時候會出現什麼情況,並不清楚。
     */
    void putAll(Map<? extends K, ? extends V> m);
    
    //
    /**
     * remove方法用於移除map中已有的某個key.更一般的講,如果map包含了一個滿足條件key==null ?  k==null : key.equals(k)的映射,
     * 這一映射就會被移除.(map最多包含一個這樣的映射)
     * 本方法會返回移除的key對應的value值,如果map這個key沒有對應的value值,則返回null。
     * 如果map允許null值,那麼返回null值並不一定表明map不包含對應key的映射值;因爲這也可能是key本身對應的value值就是null.
     */
    V remove(Object key);
    /**
     * 移除map中所有的映射
     */
    void clear();

    //
    /**
     * 返回map中key-value映射的個數.如果map包含的key-value個數超過了Integer.MAX_VALUE這個數
     * 則返回Integer.MAX_VALUE.
     */
    int size();

    /**
     * 如果map沒有存儲任何key-value,則返回true
     */
    boolean isEmpty();
    /**
     * 如果map存儲了指定的key,則返回true.更一般的情況是,當且僅當map包含了一個key的映射
     * 映射情況是:key==null ? k==null : key.equals(k),此時返回true.
     */
    boolean containsKey(Object key);

    /**
     * 如果map中至少有一個key能映射到指定的value,那麼就返回true.更一般的情況是,
     * 當且僅當value==null ? v==null : value.equals(v)條件成立,才返回true.
     * 在所有map接口的實現類中,這一操作都需要map大小的線性時間來完成.
     */
    boolean containsValue(Object value);

    /**
     * 返回指定key映射的value.如果map沒有指定的key,則返回null.
     */
    V get(Object key);

    //三個返回key,value,key-value的集合
    /**
     * 此方法:返回map包含所有的key的一個set集合視圖
     */
    Set<K> keySet();
    /**
     * values方法返回map內存儲的所有值的集合(畢竟值集合中,值可以有重複的,所以此方法和上面的返回的
     * key集合的結果類型不一樣,因爲key肯定都是不同的).
     */
    Collection<V> values();

    /**
     * 此方法返回map裏存儲的所有映射的視圖.
     */
    Set<Map.Entry<K, V>> entrySet();

    //兩個覆蓋object的方法
     /**
     * 用於對比兩個map是否相等.
     * 如果給定的對象是一個map且兩個map的映射一致,則返回true.
     * 一般,兩個map的映射一致,要滿足的條件是:m1.entrySet().equals(m2.entrySet())
     * 這就保證了實現了map接口的不同類對於equals方法的使用纔是正確的.
     */
    boolean equals(Object o);

    /**
     * 返回map的哈希值,map的哈希值被定義爲:這個map的entrySet視圖的每一個條目的哈希值的總和.
     * 這就保證了任意兩個map相等,則他們的哈希值一定相等,這也是Object類對哈希值的普遍要求(哈希值作爲兩個對象相等的必要非充分條件).
     */
    int hashCode();

    /**
     * map條目(key-value對),Map.entrySet方法返回的就是map的集合視圖,map視圖中的元素就是來源於此類.
     * 獲取map條目的唯一方式就是來源於集合視圖的迭代器.只有在迭代的過程中,Map.Entry對象纔是有效的;
     * 通常,如果通過迭代器獲得的map條目,在遍歷過程中,作爲後臺支持的map被修改了,那麼map條目會如何被影響,對此
     * 並沒有做出具體規定(當然此處說的map修改不包括setValue方法的調用).
     */
    interface Entry<K,V> {
        /**
         * 獲取當前map條目對應的key
         */
        K getKey();

        /**
         * 返回map條目對應的value值.
         */
        V getValue();

        /**
         * 用指定值替換當前條目中的value
         */
        V setValue(V value);

        /**
         * 將指定對象和當前條目做比較,如果給定的對象是一個條目並且兩個條目代表同一個映射,則返回true.
         */
        boolean equals(Object o);

        /**
         * 返回map條目的哈希值
         */
        int hashCode();

        /**
         * 返回一個比較map.entry的比較器,按照key的自然順序排序,返回的比較器支持序列化
         * @since 1.8
         */
        public static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K,V>> comparingByKey() {
            return (Comparator<Map.Entry<K, V>> & Serializable)
                (c1, c2) -> c1.getKey().compareTo(c2.getKey());
        }

        /**
         * 返回一個map.enty的比較器,按照value的自然順序排序,返回的比較器支持序列化
         * @since 1.8
         */
        public static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K,V>> comparingByValue() {
            return (Comparator<Map.Entry<K, V>> & Serializable)
                (c1, c2) -> c1.getValue().compareTo(c2.getValue());
        }

        /**
         * 返回一個map.entry的比較器,根據傳入比較器對key排序,如果傳入的比較器支持序列化,則返回的結果比較器也支持序列化.
         * @since 1.8
         */
        public static <K, V> Comparator<Map.Entry<K, V>> comparingByKey(Comparator<? super K> cmp) {
            Objects.requireNonNull(cmp);
            return (Comparator<Map.Entry<K, V>> & Serializable)
                (c1, c2) -> cmp.compare(c1.getKey(), c2.getKey());
        }

        /**
         * 返回一個map.entry的比較器,根據傳入比較器對value排序,如果傳入的比較器支持序列化,則返回的結果比較器也支持序列化
         * @since 1.8
         */
        public static <K, V> Comparator<Map.Entry<K, V>> comparingByValue(Comparator<? super V> cmp) {
            Objects.requireNonNull(cmp);
            return (Comparator<Map.Entry<K, V>> & Serializable)
                (c1, c2) -> cmp.compare(c1.getValue(), c2.getValue());
        }
    }

    
    //1.8新增
    /**
     * 返回指定key對應的value,如果沒有對應的映射,則返回傳入參數中的默認值defaultValue
     * @since 1.8
     */
    default V getOrDefault(Object key, V defaultValue) {
        V v;
        return (((v = get(key)) != null) || containsKey(key))
            ? v
            : defaultValue;
    }

    /**
     * 對map中每一個entry執行action中定義對操作,直到全部entry執行完成or執行中出現異常爲止
     * @since 1.8
     */
    default void forEach(BiConsumer<? super K, ? super V> action) {
        Objects.requireNonNull(action);
        for (Map.Entry<K, V> entry : entrySet()) {
            K k;
            V v;
            try {
                k = entry.getKey();
                v = entry.getValue();
            } catch(IllegalStateException ise) {
                // this usually means the entry is no longer in the map.
                throw new ConcurrentModificationException(ise);
            }
            action.accept(k, v);
        }
    }

    /**
     * 對於map中每一個entry,將其value替換成BiFunction接口返回的值.直到所有entry替換完or出現異常爲止
     * @since 1.8
     */
    default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
        Objects.requireNonNull(function);
        for (Map.Entry<K, V> entry : entrySet()) {
            K k;
            V v;
            try {
                k = entry.getKey();
                v = entry.getValue();
            } catch(IllegalStateException ise) {
                // this usually means the entry is no longer in the map.
                throw new ConcurrentModificationException(ise);
            }

            // ise thrown from function is not a cme.
            v = function.apply(k, v);

            try {
                entry.setValue(v);
            } catch(IllegalStateException ise) {
                // this usually means the entry is no longer in the map.
                throw new ConcurrentModificationException(ise);
            }
        }
    }

    /**
     * 如果指定的鍵尚未與值相關聯(或被映射爲null),則將它與給定的值相關聯並返回null,否則返回當前值。
     * @since 1.8
     */
    default V putIfAbsent(K key, V value) {
        V v = get(key);
        if (v == null) {
            v = put(key, value);
        }

        return v;
    }

    /**
     * 如果給定的參數key和value在map中是一個entry,則刪除這個entry.
     * @since 1.8
     */
    default boolean remove(Object key, Object value) {
        Object curValue = get(key);
        if (!Objects.equals(curValue, value) ||
            (curValue == null && !containsKey(key))) {
            return false;
        }
        remove(key);
        return true;
    }

    /**
     * 如果給定的key和value在map中有entry,則爲指定key的entry,用新value替換舊的value
     * @since 1.8
     */
    default boolean replace(K key, V oldValue, V newValue) {
        Object curValue = get(key);
        if (!Objects.equals(curValue, oldValue) ||
            (curValue == null && !containsKey(key))) {
            return false;
        }
        put(key, newValue);
        return true;
    }

    /**
     *如果指定key在map中有value,則用參數value進行替換
     * @since 1.8
     */
    default V replace(K key, V value) {
        V curValue;
        if (((curValue = get(key)) != null) || containsKey(key)) {
            curValue = put(key, value);
        }
        return curValue;
    }

    /**
     * 如果指定key在map中沒有對應的value,則使用輸入參數,即函數接口mappingfunction爲其計算一個value.
     * 如果計算value不爲null,則將value插入map中,如果計算function返回結果爲null,則不插入任何映射。
     * @since 1.8
     */
    default V computeIfAbsent(K key,
            Function<? super K, ? extends V> mappingFunction) {
        Objects.requireNonNull(mappingFunction);
        V v;
        if ((v = get(key)) == null) {
            V newValue;
            if ((newValue = mappingFunction.apply(key)) != null) {
                put(key, newValue);
                return newValue;
            }
        }

        return v;
    }

    /**
     * 如果map中存在指定key對應的value,且不爲null,則本方法會嘗試使用function,並利用key生成一個新的value
     * 如果function接口返回null,map中原entry則被移除.如果function本身拋出異常,則當前map不會發生改變.
     * @since 1.8
     */
    default V computeIfPresent(K key,
            BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
        Objects.requireNonNull(remappingFunction);
        V oldValue;
        if ((oldValue = get(key)) != null) {
            V newValue = remappingFunction.apply(key, oldValue);
            if (newValue != null) {
                put(key, newValue);
                return newValue;
            } else {
                remove(key);
                return null;
            }
        } else {
            return null;
        }
    }

    /**
     * 利用指定key和value計算一個新映射,比如:向一個value映射中新增或者拼接一個String。
     * 如果function接口返回null,則map中原entry被移除(如果本來就不存在,則不執行移除操作).
     * 如果function本身拋出(非檢查型)異常,異常會被重新拋出,當前map也不會發生改變.
     * @since 1.8
     */
    default V compute(K key,
            BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
        Objects.requireNonNull(remappingFunction);
        V oldValue = get(key);

        V newValue = remappingFunction.apply(key, oldValue);
        if (newValue == null) {
            // delete mapping
            if (oldValue != null || containsKey(key)) {
                // something to remove
                remove(key);
                return null;
            } else {
                // nothing to do. Leave things as they were.
                return null;
            }
        } else {
            // add or replace old mapping
            put(key, newValue);
            return newValue;
        }
    }

    /**
     * 如果指定key沒有value,或者其value爲null,則將其改爲給定的非null的value
     * 否則,用給定的function返回值替換原value.
     * 如果給定參數value和function返回結果都爲null,則刪除map中這個entry.
     * 這一方法常用於:對一個key合併多個映射的value時.
     * 比如:要創建或追加一個String給一個值映射.
     * 如果function返回null,則map中原entry被移除.如果function本身拋出異常,則異常會被重新拋出,且當前map不會發生更改.
     * @since 1.8
     */
    default V merge(K key, V value,
            BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
        Objects.requireNonNull(remappingFunction);
        Objects.requireNonNull(value);
        V oldValue = get(key);
        V newValue = (oldValue == null) ? value :
                   remappingFunction.apply(oldValue, value);
        if(newValue == null) {
            remove(key);
        } else {
            put(key, newValue);
        }
        return newValue;
    }
}
    
}
View Code

 

喫透Java集合系列九:HashMap

詳細內容在下面這一篇博客展開介紹

喫透Java集合系列九:HashMap - 你的雷哥 - 博客園 (cnblogs.com)

 

喫透Java集合系列十:HashTable

一:整體實現
HashTable和HashMap實現大致相同,都是基於哈希表來實現的,數組+鏈表的形式(和HashMap有稍微的區別,HashMap加入了紅黑樹,它存儲的內容是鍵值對(key-value)映射。
Hashtable 繼承於Dictionary,實現了Map、Cloneable、java.io.Serializable接口。Dictionary是一個過時的鍵值對映射的抽象類,jdk已經不建議使用,新的實現應該實現Map接口,而不是擴展這個類。
HashTable方法加了synchronized關鍵字,所以是線程安全的。實現類property

 

 

 

 

三:擴容機制

 

當前鍵值對的數量count >= threshold時會觸發擴容。

 

 

protected void rehash() {
        int oldCapacity = table.length;
        Entry<?,?>[] oldMap = table;

        //新容量=舊容量 * 2 + 1
        int newCapacity = (oldCapacity << 1) + 1;
        if (newCapacity - MAX_ARRAY_SIZE > 0) {
            if (oldCapacity == MAX_ARRAY_SIZE)
                // Keep running with MAX_ARRAY_SIZE buckets
                return;
            newCapacity = MAX_ARRAY_SIZE;
        }
        //新建一個size = newCapacity的數組
        Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];

        modCount++;
        //重新計算閥值
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        table = newMap;
        //將原來的元素拷貝到新的HashTable中,對數組鏈表數據進行重新 hash index 計算,
        //rehash之後會使得最早插入的數據回到鏈表的第一位
        for (int i = oldCapacity ; i-- > 0 ;) {
            for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
                Entry<K,V> e = old;
                old = old.next;

                int index = (e.hash & 0x7FFFFFFF) % newCapacity;
                e.next = (Entry<K,V>)newMap[index];
                newMap[index] = e;
            }
        }
    }
View Code

 

在這個rehash()方法中我們可以看到容量擴大兩倍+1,同時需要將原來HashTable中的元素一一複製到新的HashTable中,並且對每個元素根據hash值從新計算下標,這個過程是比較消耗時間的。

四:put方法
put方法的整個處理流程:計算key的hash值,根據hash值獲得key在table數組中的索引位置,然後迭代該key處的Entry鏈表(我們暫且理解爲鏈表),若該鏈表中存在一個這個的key對象,那麼就直接替換其value值即可,否則在將改key-value節點插入該index索引位置處

 

public synchronized V put(K key, V value) {
        // 值不能爲空
        if (value == null) {
            throw new NullPointerException();
        }

        //計算key的hash值,確認在table[]中的索引位置
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        //迭代index索引位置,如果該位置處的鏈表中存在一個一樣的key,則替換其value,返回舊值
        for(; entry != null ; entry = entry.next) {
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }
        //如果不存在,則創建entry加入hash表中
        addEntry(hash, key, value, index);
        return null;
    }
//在指定索引位置加入key-value鍵值對
private void addEntry(int hash, K key, V value, int index) {
        modCount++;

        Entry<?,?> tab[] = table;
        //如果當前元素的數量大於等於閾值,則觸發擴容
        if (count >= threshold) {
            // Rehash the table if the threshold is exceeded
            rehash();

            tab = table;
            hash = key.hashCode();
            index = (hash & 0x7FFFFFFF) % tab.length;
        }

        // 創建entry並加入到鏈表的頭
        @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>) tab[index];
        tab[index] = new Entry<>(hash, key, value, e);
        count++;
    }
View Code

五:get方法

get方法比較簡單,處理過程就是計算key的hash值,判斷在table數組中的索引位置,然後迭代鏈表,匹配直到找到相對應key的value,若沒有找到返回null。

public synchronized V get(Object key) {
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                return (V)e.value;
            }
        }
        return null;
    }
View Code

六:HashTable和HashMap區別

  • HashMap線程不安全,HashTable是線程安全的。HashMap內部實現沒有任何線程同步相關的代碼,所以相對而言性能要好一點。如果在多線程中使用HashMap需要自己管理線程同步。HashTable大部分對外接口都使用synchronized包裹,所以是線程安全的,但是性能會相對差一些。
  • 二者的基類不一樣。HashMap派生於AbstractMap,HashTable派生於Dictionary。它們都實現Map, Cloneable, Serializable這些接口。AbstractMap中提供的基礎方法更多,並且實現了多個通用的方法,而在Dictionary中只有少量的接口,並且都是abstract類型。
  • key和value的取值範圍不同。HashMap的key和value都可以爲null,但是HashTable key和value都不能爲null。對於HashMap如果get返回null,並不能表明HashMap不存在這個key,如果需要判斷HashMap中是否包含某個key,就需要使用containsKey這個方法來判斷。
  • 算法不一樣。HashMap的initialCapacity爲16,而HashTable的initialCapacity爲11HashMap中初始容量必須是2的冪,如果初始化傳入的initialCapacity不是2的冪,將會自動調整爲大於出入的initialCapacity最小的2的冪。HashMap使用自己的計算hash的方法(會依賴key的hashCode方法),HashTable則使用key的hashCode方法得到。

喫透Java集合系列十一:LinkedHashMap

一:概要
HashMap是Java集合中的重要成員,也是Map族中我們最爲常用的一種,但是HashMap是無序的,也就是說,迭代HashMap所得到的元素順序並不是它們最初放置到HashMap的順序。

HashMap的這一缺點往往會造成諸多不便,因爲在有些場景中,我們確需要用到一個可以保持插入順序的Map。慶幸的是,JDK爲我們解決了這個問題,它爲HashMap提供了一個子類 —— LinkedHashMap。雖然LinkedHashMap增加了時間和空間上的開銷,但是它通過維護一個額外的雙向鏈表保證了迭代順序。

該迭代順序可以是插入順序,也可以是訪問順序。因此,根據鏈表中元素的順序可以將LinkedHashMap分爲:保持插入順序的LinkedHashMap和保持訪問順序的LinkedHashMap,其中LinkedHashMap的默認實現是按插入順序排序的。

其實LinkedHashMap就是 HashMap+雙向鏈表 來實現的,就是將所有Node節點鏈入一個雙向鏈表的HashMap。在LinkedHashMapMap中,所有put進來的Node都保存在哈希表中,但由於它又額外定義了一個以head爲頭結點的雙向鏈表,因此對於每次put進來Node,除了將其保存到哈希表中對應的位置上之外,還會將其插入到雙向鏈表的尾部。

 

 

 

 

喫透Java集合系列十二:TreeMap

 

 

一:TreeMap整體認識
我們知道HashMap,它保證了以O(1)的時間複雜度進行增、刪、改、查,從存儲角度考慮,這兩種數據結構是非常優秀的。但是HashMap還是有自己的侷限性----它不具備統計性能,或者說它的統計性能時間複雜度並不是很好才更準確,所有的統計必須遍歷所有Entry,因此時間複雜度爲O(N)。
比如Map的Key有1、2、3、4、5、6、7,我現在要統計:

 

所有Key比3大的鍵值對有哪些
Key最小的和Key最大的是哪兩個
就類似這些操作,HashMap做得比較差,此時我們可以使用TreeMap。TreeMap的Key按照自然順序進行排序或者根據創建映射時提供的Comparator接口進行排序TreeMap爲增、刪、改、查這些操作提供了log(N)的時間開銷從存儲角度而言,這比HashMap的O(1)時間複雜度要差些;但是在統計性能上,TreeMap同樣可以保證log(N)的時間開銷,這又比HashMap的O(N)時間複雜度好不少。

 

因此:如果只需要存儲功能,使用HashMap是一種更好的選擇;如果還需要保證統計性能或者需要對Key按照一定規則進行排序,那麼使用TreeMap是一種更好的選擇。

 

TreeMap是由紅黑樹來實現的,下面看一下紅黑樹

 

二:紅黑樹
紅黑樹又稱紅-黑二叉樹,它首先是一顆二叉樹,它具體二叉樹所有的特性。同時紅黑樹更是一顆自平衡的排序二叉樹。
我們知道一顆基本的二叉樹他們都需要滿足一個基本性質–即樹中的任何節點的值大於它的左子節點,且小於它的右子節點。按照這個基本性質使得樹的檢索效率大大提高。我們知道在生成二叉樹的過程是非常容易失衡的,最壞的情況就是一邊倒(只有右/左子樹),這樣勢必會導致二叉樹的檢索效率大大降低(O(n)),所以爲了維持二叉樹的平衡,大牛們提出了各種實現的算法,如:AVL,SBT,伸展樹,TREAP ,紅黑樹等等。
平衡二叉樹必須具備如下特性:它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,並且左右兩個子樹都是一棵平衡二叉樹。也就是說該二叉樹的任何一個等等子節點,其左右子樹的高度都相近。

 

紅黑樹顧名思義就是節點是紅色或者黑色的平衡二叉樹,它通過顏色的約束來維持着二叉樹的平衡。對於一棵有效的紅黑樹二叉樹而言我們必須增加如下規則:

 

每個節點都只能是紅色或者黑色
根節點是黑色
每個葉節點(NIL節點,空節點)是黑色的。
如果一個結點是紅的,則它兩個子節點都是黑的。也就是說在一條路徑上不能出現相鄰的兩個紅色結點。
從任一節點到其每個葉子的所有路徑都包含相同數目的黑色節點。
這些約束(通過旋轉和改變顏色來滿足上述兩個條件)強制了紅黑樹的關鍵性質: 從根到葉子的最長的可能路徑不多於最短的可能路徑的兩倍長結果是這棵樹大致上是平衡的。因爲操作比如插入、刪除和查找某個值的最壞情況時間都要求與樹的高度成比例,這個在高度上的理論上限允許紅黑樹在最壞情況下都是高效的,而不同於普通的二叉查找樹。所以紅黑樹它是複雜而高效的,其檢索效率O(log n)。下圖爲一顆典型的紅黑二叉樹。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章