java 數據結構大全(Map、List、Set、BlockingQueue、BlockingDueue)

目錄

1、集合框架

2、Iterable 接口

3、 Collection接口

3.1、 List接口

3.1.1、 ArrayList類、LinkedList類、Vector類

3.2、Set接口

3.2.1、HashSet類、TreeSet類、LinkedHashSet類

3.3、Map接口

3.3.1、HashMap類

3.3.2、 HashTable類

3.3.3、TreeMap類

3.3.4、 WeakHashMap類

4、Concurrent包下的阻塞隊列(都是線程安全的,因爲使用了Lock)

4.1、ArrayBlockingQueue 類

4.2、LinkedBlockingQueue 類

4.3、SynchronousQueue 類

4.4、PriorityBlockingQueue 類

4.5、 DeleyQueue 類

4.5.1 講講隊列中元素需要滿足什麼要求

4.5.2、 看看DelayQueue的源碼(這個源碼不難,耐心點看吧)

4.6、LinkedTransferQueue 類

5、併發性集合

5.1、ConcurrentHashMap類

5.2、ConcurrentSkipListMap 類

5.3、ConcurrentSkipListSet 類


1、集合框架

下面這個圖基本包括了java中核心的集合結構,個人覺得還可以。

2、Iterable 接口

這個接口是集合系列的根接口,都要實現它,因爲 Iterable 接口裏面有個iterator()方法,用於遍歷集合裏的每一個元素,所以記着:凡是集合類,都可以通過iterator來遍歷。

3、 Collection接口

下面是Collection接口的源碼,定義了集合最基本的使用規範。不僅是java,其它的開源代碼也是這樣層層繼承和實現,我們可能會比較疑惑,爲什麼要搞得這麼複雜,把代碼寫在一起不好嗎,幹嘛弄出這麼多的接口文件。層層分開的目的是讓框架解耦,這樣可以更加靈活地擴展和使用,你把代碼都寫到一堆,一旦需要更改或者擴展,會讓你改得頭疼。

public interface Collection<E> extends Iterable<E> {
    // 返回集合的元素個數
    int size();
    // 集合是否爲空
    boolean isEmpty();
    // 集合是否包含該元素
    boolean contains(Object o);
    // 返回迭代器,用於遍歷集合
    Iterator<E> iterator();
    // 返回一個包含集合所有元素的數組
    Object[] toArray();
    // 同上,只是能夠運行指定類型
    <T> T[] toArray(T[] a);
    // 添加一個元素,成功返回true,失敗返回false
    boolean add(E e);
    // 刪除一個元素,成功返回true,失敗返回false
    boolean remove(Object o);
    // 集合是否包含該集合c(c是否是集合的子集)
    boolean containsAll(Collection<?> c);
    // 添加一個集合,成功返回true,失敗返回false   
    boolean addAll(Collection<? extends E> c);
    // 刪除一個集合,成功返回true,失敗返回false
    boolean removeAll(Collection<?> c);
    // 刪除滿足指定條件的元素
    default boolean removeIf(Predicate<? super E> filter) {
        Objects.requireNonNull(filter);
        boolean removed = false;
        final Iterator<E> each = iterator();
        while (each.hasNext()) {
            if (filter.test(each.next())) {
                each.remove();
                removed = true;
            }
        }
        return removed;
    }
    // 僅保留包含在指定集合c中的元素
    boolean retainAll(Collection<?> c);
    // 刪除所有元素
    void clear();
    // 判等
    boolean equals(Object o);
    // 返回哈希碼
    int hashCode();
    // 返回集合的Spliterator
    @Override
    default Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, 0);
    }
    // 返回以此集合作爲源的Stream
    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }
    // 返回可能並行的以此集合作爲源的Stream
    default Stream<E> parallelStream() {
        return StreamSupport.stream(spliterator(), true);
    }
}

 上面是Collection接口的源碼,重點說一下:removeIf()。

removeIf(Predicate<? super E> filter):刪除滿足指定條件的元素,那怎麼去設置條件呢?下面舉個例子,刪除集合中大於24的元素,其實就是對集合的每個元素進行遍歷,然後判斷每個元素是否滿足條件,如果滿足,就刪除。很多地方建議removeIf()裏面用lambda表達式,確實,如果條件比較簡單的話,用lambda表達式更簡潔,但是對於複雜的條件,還是用java方式吧。

        ArrayList<Integer> collection = new ArrayList<>();
        collection.add(22);
        collection.add(40);
        collection.removeIf(new Predicate<Integer>() {
            @Override
            public boolean test(Integer integer) {
                return integer > 24;
            }
        });

3.1、 List接口

下面是List接口的源碼,除去了從Collection接口繼承的方法,只簡單介紹了擴展的方法,熟悉這些方法的目的是讓我們知道List集合框架下的結構可以有哪些屬性和操作。

public interface List<E> extends Collection<E> {
    // 在指定位置處添加集合
    boolean addAll(int index, Collection<? extends E> c);
    // 將所有元素替換,具體的替換操作代碼就寫在參數位置即可
    default void replaceAll(UnaryOperator<E> operator) {
        Objects.requireNonNull(operator);
        final ListIterator<E> li = this.listIterator();
        while (li.hasNext()) {
            li.set(operator.apply(li.next()));
        }
    }
    // 給list排序,前提是元素能夠比較(實現了Comparator接口)
    @SuppressWarnings({"unchecked", "rawtypes"})
    default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }
    // 返回index處的元素 
    E get(int index);
    // 設置index處的元素爲element
    E set(int index, E element);
    // 在index處添加元素element
    void add(int index, E element);
    // 刪除index處的元素
    E remove(int index);
    // 返回指定元素的位置
    int indexOf(Object o);
    // 返回指定元素在list中最後一個的位置
    int lastIndexOf(Object o);
    // 迭代器
    ListIterator<E> listIterator();
    // 迭代器,從index處開始迭代
    ListIterator<E> listIterator(int index);
    // 截取list的一段出來
    List<E> subList(int fromIndex, int toIndex);
}

3.1.1、 ArrayList類、LinkedList類、Vector類

ArrayList類:線程不安全。底層是一個數組,new ArrayList(); 創建時默認爲空數組(長度爲0),當真正要用的時候,再擴展容量爲10,其實也可以理解爲ArrayList 的默認容量爲10。之後的擴容機制:擴容後的大小= 原始大小*1.5。

優點:能夠隨機訪問,相對於數組而言,能夠擴容。

缺點:容易造成空間浪費,擴展容量的話,也是創建一個新的更大容量的數組,讓後將舊數組的數據複製到新數組,麻煩;另外,不方便插入、刪除操作。

LinkedList類:線程不安全。底層是一個雙向鏈表。

優點:插入,刪除操作非常方便。

缺點:遍歷,查詢不方便。

Vector類:線程安全。和ArrayList差不多,區別在於:Vector是線程安全的,因爲用了synchronized關鍵字;Vector的擴容機制是擴展1倍,而ArrayList是擴展0.5倍。

3.2、Set接口

Set接口的源碼沒有什麼可說的,因爲都是繼承自Collection接口的。

3.2.1、HashSet類、TreeSet類、LinkedHashSet類

HashSet類:線程不安全。底層是HashMap的,元素就是HashMap的key,value固定是PRESENT。此處暫不做詳細介紹,看下面的HashMap即可。

TreeSet類:線程不安全。底層是TreeMap實現的。

LinkedHashSet類:線程不安全。有兩個空間,一個是HashSet ,另一個是雙鏈表,既有HashSet的性質,又有鏈表的性質,用空間換功能。

都是用Map來實現的,那Set系列存在的意義呢?其實Set系列都用的少,替我們封裝了一層,讓我們用起來更方便;另外,Set裏的元素必須不重複,這也是Set的優點。

3.3、Map接口

3.3.1、HashMap類

下面的圖畫得很好理解,所以引用一下,請原作者別噴。

線程不安全。HashMap是由數組+鏈表實現的,添加一個元素A時,首先要確定插入的位置:先計算其hashCode值,再根據hashCode計算出位置,如果該位置已經有元素了,那就比較兩個元素是否是同一個,如果是,則替換,如果不是,即出現哈希衝突,將兩個元素形成鏈表,然後表頭插入插入數組的該位置處,新來的元素A放入表頭,就形成了鏈表。當鏈表的長度超過8時,就自動將鏈表轉爲紅黑樹結構。

默認情況下,數組的初始長度爲16。默認的負載因子是0.75。當元素的個數超過 數組長度 * 負載因子 時,就擴容,數組長度翻倍。

負載因子:比如一個哈希數組長度爲16時,裏面存的元素越多,那麼下一次存入元素時發生哈希衝突的可能性就越大,如果數組都滿了,再存入一個元素,那哈希衝突概率就是100%。一旦衝突了,就會用鏈表的方法去解決,你要知道哈希的查詢是O(1),但是鏈表的查詢是要從表頭挨個挨個地去找的,所以鏈表的大量存在會影響HashMap的性能。因此,不能等到數組都存滿了再擴容,所以設置了一個負載因子,負載因子表明一個數組最多存儲的元素比例,比如,因子 = 0.75時,長度爲16的HashMap最多存入12個元素,然後就得擴容爲32。

擴容:擴容比較麻煩,大小從16變爲了32,之前元素的哈希位置需要重新計算;另外還得重新創建數組,將元素複製過去。因此,如果能提前預料到HashMap的最大容量是最好的,就不會出現擴容了。

負載因子的選擇需要選好,大了,哈希衝突多,鏈表多,影響性能;小了,浪費空間。

如果要自己設置hashMap 的大小,最好設置爲2的冪,爲了讓哈希計算出來的索引位置均勻地分佈在數組裏。如果不是2的冪,那麼在二進制取模運算時,有些index結果的出現機率會更大,而有些index可能永遠不會出現。

常用方法:

// 添加鍵值對
public V put(K key, V value);
// 添加很多鍵值對
public void putAll(Map<? extends K, ? extends V> m);
// 獲取key對於的值
public V get(Object key);
// 是否包含該key
public boolean containsKey(Object key);
// 是否包含該value
public boolean containsValue(Object value);
// 刪除key對應的鍵值對
public V remove(Object key);
// 獲取所有value的集合
public Collection<V> values();
// 判斷是否爲空
public boolean isEmpty();
// 返回一個由所有的key組成的一個Set
public Set<K> keySet();
// 返回一個由所有鍵值對組成的一個Set
public Set<Map.Entry<K,V>> entrySet();
// 清空
public void clear();
// 獲取大小
public int size();

3.3.2、 HashTable類

線程安全。雖然HashTable已經被棄用了,還是介紹一下吧,HashTable的結構與HashMap的一樣,兩者的主要區別在於:

1、HashTable是線程安全的(sychronized);

2、HashTable不允許key,value爲null;

3、HashTable的默認大小爲11,擴容時,變爲原來大小的2倍+1。

HashTable被棄用了,肯定是有新的類替代它,下文會介紹ConcurrentHashMap。

3.3.3、TreeMap類

線程不安全。TreeMap類的底層結構是一顆紅黑樹,即自平衡二叉排序樹,因此,查詢操作的時間複雜度爲O(log n)。如果說僅僅查詢,添加,刪除操作,那TreeMap的性能是不如HashMap的,但是TreeMap是能夠自動排序的,這也是TreeMap存在的意義。

每添加/刪除一個元素時,TreeMap都會自動調整自己的結構以再次讓自己成爲平衡二叉排序樹,當我們要使用TreeMap的自動排序特性時,需要將所有元素/所有key導出爲一個集合,然後這個集合裏的內容是有序的(默認按照key升序),當然我們可以指定排序的規則,使得獲得的集合按照我們自定義的規則來排序,因此,就需要讓key實現Comparable接口。

常用方法:

1、public TreeMap():默認的構造方法,按key自然排序。

2、public TreeMap(Map<? extends K, ? extends V> m):將Map裏的鍵值對直接構造成紅黑樹,key按照自然排序。

3、public TreeMap(Comparator<? super K> comparator):指定比較器,key按照比較器排序。

4、public V get(Object key):根據key獲取value。

5、public V put(K key, V value):添加key-value。

6、Map.Entry<K,V> ceilingEntry(K key):返回大於等於此key 的元素。

7、K ceilingKey(K key):返回大於等於此key的key。

8、void clear():清空。

9、Object clone():返回此 TreeMap實例的淺拷貝。

10、Comparator<? super K> comparator():返回比較器,如果是自然排序,即返回null。

11、boolean containsKey(Object key):是否包含指定key。

12、boolean containsValue(Object value):是否包含指定value。

13、NavigableSet<K> descendingKeySet():以反序的方式返回所有的key

14、NavigableMap<K,V> descendingMap():以反序的方式返回所有鍵值對。

15、Set<Map.Entry<K,V>> entrySet():返回包含所有鍵值對的集合。

16、Map.Entry<K,V> firstEntry():返回最小鍵相關聯的鍵值對 。

17、K firstKey():返回第一個(最低)鍵。

18、Map.Entry<K,V> floorEntry(K key):返回小於等於此key的鍵值對。

19、K floorKey(K key):返回小於等於此key 的key。

20、Set<K> keySet():返回包含所有key的集合。

21、Map.Entry<K,V> lastEntry():返回最大key對應的鍵值對。

22、K lastKey():返回最大key。

23、void putAll(Map<? extends K,? extends V> map):將map添加進TreeMap中。

24、V remove(Object key):刪除key。

25、int size():返回鍵值對的總數量。

26、Collection<V> values():返回包含所有值的集合。

3.3.4、 WeakHashMap類

這是一個和HashMap類似的哈希結構類,常用的操作方法就是map那套,因爲平時也沒有用過,所以此處暫不作詳細介紹,只是這個類是一個弱引用的哈希映射,在垃圾回收時,不管當前內存是否夠用,都會回收掉弱引用指向的內存空間的。

4、Concurrent包下的阻塞隊列(都是線程安全的,因爲使用了Lock)

何爲阻塞隊列?當一個阻塞隊列爲空時,其它線程獲取元素時,會阻塞,直到隊列不爲空。當一個阻塞隊列滿了,其它線程插入元素,會阻塞,直到隊列有空位。

Queue接口繼承了Collection接口,所以有時候會看到,一個隊列類裏面,既可以使用Collection裏面的某些方法,又可以使用Queue接口裏面的方法,而效果是一樣的,比如add()方法和put()方法。個人覺得,還是按照隊列這套接口去使用吧。

先看看Queue接口定義的操作規範:

public interface Queue<E> extends Collection<E> {
    // 往隊列尾部添加元素,如果隊列已滿,拋出異常
    boolean add(E e);
    // 往隊列尾部添加元素,如果隊列已滿,返回false即可,不會拋出異常
    boolean offer(E e);
    // 刪除隊列頭,如果隊列爲空,拋出異常
    E remove();
    // 彈出隊列第一個元素,如果隊列爲空,返回null
    E poll();
    // 獲取隊列第一個元素,但是元素仍然在隊列中,如果隊列爲空,拋出異常
    E element();
    // 獲取隊列第一個元素,但是元素仍然在隊列中,如果隊列爲空,返回null
    E peek();
}

4.1、ArrayBlockingQueue 類

個人覺得命名是一項非常厲害的本事,光從命名就可以看出一個人的技術水平。“Array”表示底層是數組,“Blocking”表示阻塞,“Queue”表示隊列,因此,ArrayBlockingQueue就是底層是一個有界數組,擁有阻塞特性的隊列。

構造方法:

1、public ArrayBlockingQueue​(int capacity);指定隊列大小,默認爲非公平方式。

2、public ArrayBlockingQueue​(int capacity, boolean fair):指定隊列大小,指定是公平方式還是非公平方式。

3、public ArrayBlockingQueue​(int capacity, boolean fair, Collection <? extends E> c):將c集合作爲隊列的初始值。

常用方法,實現Queue接口中的基本方法就不說了:

1、public void put​(E e):添加元素至隊尾。

2、public boolean offer​(E e, long timeout, TimeUnit unit):在指定的時間裏還不能添加元素,那就返回false。

3、public E take​():刪除隊列頭,如果沒有的話,阻塞。

4、public E poll​(long timeout, TimeUnit unit):在指定時間內還不能獲得隊列頭,就返回null。

5、public int size​():返回隊列大小。

6、public int remainingCapacity​():隊列剩餘大小。

7、public void clear​():清空隊列。

8、public boolean retainAll​(Collection <?> c):保留隊列中集合c包含的元素。

9、public boolean removeAll​(Collection <?> c):刪除隊列中集合c包含的元素。

10、public boolean removeIf​(Predicate <? super E> filter):刪除隊列中滿足指定條件的元素。

11、public void forEach​(Consumer <? super E> action):對每個元素進行操作,參數就是操作。

12、public int drainTo​(Collection <? super E> c):將隊列中的元素提取出來放入集合c,此時隊列就空了。

13、public int drainTo​(Collection <? super E> c, int maxElements):儘量提取maxElements個元素放入集合c,此時隊列就空了。

4.2、LinkedBlockingQueue 類

線程安全。LinkedBlockingQueue的底層是基於單鏈表實現的,有兩個指針 head 和 last ,分別指向隊列頭和隊列尾,因此可以很方便地在隊列尾添加元素,在隊列頭提取元素。LinkedBlockingQueue 默認是一個無界隊列,最大長度可達到Integer.MAX_VLAUE,所以如果隊列會有大量元素的話,會造成內存不足,因此,最好還是設置一個最大長度。

另外,LinkedBlockingQueue使用了兩種鎖 putLock 和 takeLock,因此,添加 和 刪除 操作是不互斥的,略微地提高了併發量(相對於ArrayBlockingQueue來說)。

LinkedBlockingQueue類的api幾乎和ArrayBlockingQueue一模一樣,所以此處就不作介紹了。

4.3、SynchronousQueue 類

關於它的原理,這篇文章講得比較可以:https://www.cnblogs.com/dwlsxj/p/synchronousqueue-unfair-pattern.html

線程安全。SynchronousQueue隊列是不存儲數據的,當有生產者線程A準備向隊列裏添加數據時,如果此時沒有線程取數據,那麼線程A就要阻塞,直到有消費線程出現;如果有消費者線程B要獲取隊列裏的數據,但是沒有生產者線程添加數據,那麼線程B就要阻塞,直到有線程往隊列裏添加數據。可以看出,生成者線程和消費者線程的數據是直接交互的,相互喚醒,數據不用經過隊列,因此才說SynchronousQueue隊列是沒有數據緩存的。

但是SynchronousQueue什麼都不存嗎?不是的,它會存儲阻塞的線程,如果是公平模式的話,就用一個隊列來存儲阻塞線程,如果是非公平模式的話,就用一個棧來存儲阻塞隊列。

那麼問題來了,如果有2個線程向隊列添加元素,一起阻塞了,此時來了一個消費線程,那到底是取哪個線程的數據呢?

因此,SynchronousQueue隊列是有公平模式和非公平模式之分。

public SynchronousQueue();默認是非公平模式。

public SynchronousQueue(boolean fair); 可以設置模式(設置爲公平模式)。

我們再講講在公平模式和非公平模式下,那些阻塞線程被喚醒的順序。對於公平模式,SynchronousQueue用一個隊列來存儲阻塞線程,FIFO先進先出,即先到的,待會兒先被喚醒。對於非公平模式,用一個棧來存儲阻塞線程,先進後出,即先到的,反而後被喚醒。

用圖來給大家說明一下原理(假設是公平模式下):

1、如果此時僅有一個生產者線程P1,由於沒有消費者線程,所以將P1添加到阻塞隊列中。

2、此時又有一個生產者線程P2,由於仍然沒有消費者線程,所以將P2添加到阻塞隊列中。

3、此時出現了一個消費者線程C1,就應該匹配到阻塞隊列中的隊列頭線程P1,線程P1將數據傳遞給線程C1,並將P1從阻塞隊列中刪除。

至此,基本原理明白了吧,如果是隻有消費者線程,而沒有生成者線程的話,那阻塞隊列就應該存儲消費者線程了。

舉個例子來詳細說明(兩個條件數據的線程ThreadPut_One,ThreadPut_Two,一個讀取數據的線程ThreadTake):

public class ThreadPut_One extends Thread {
    private SynchronousQueue<String> synchronousQueue;
    public ThreadPut_One(SynchronousQueue<String> synchronousQueue){
        this.synchronousQueue = synchronousQueue;
    }

    @Override
    public void run() {
        super.run();
        try {
            this.synchronousQueue.put("DataOne");
            System.out.println("DataOne 已經插入完畢");
        } catch (InterruptedException e) {
            System.out.println("線程ID(" + Thread.currentThread().getId() + "),線程名(" + Thread.currentThread().getName() + ") 拋出異常。");
            System.out.println("異常原因:插入數據線程在阻塞時被中斷了。");
            e.printStackTrace();
        }
    }
}
public class ThreadPut_Two extends Thread {
    private SynchronousQueue<String> synchronousQueue;

    public ThreadPut_Two(SynchronousQueue<String> synchronousQueue){
        this.synchronousQueue = synchronousQueue;
    }

    @Override
    public void run() {
        super.run();
        try {
            this.synchronousQueue.put("DataTwo");
            System.out.println("DataTwo 已經插入完畢");
        } catch (InterruptedException e) {
            System.out.println("線程ID(" + Thread.currentThread().getId() + "),線程名(" + Thread.currentThread().getName() + ") 拋出異常。");
            System.out.println("異常原因:插入數據線程在阻塞時被中斷了。");
            e.printStackTrace();
        }
    }
}
public class ThreadTake extends Thread{
    private SynchronousQueue<String> synchronousQueue;
    public ThreadTake(SynchronousQueue<String> synchronousQueue){
        this.synchronousQueue = synchronousQueue;
    }

    @Override
    public void run() {
        super.run();
        String entity = getEntity();
        System.out.println("ThreadTake拿到數據了:" + entity);
    }

    /**
     * 從同步隊列synchronousQueue中獲取數據,如果此時有往隊列插入數據的線程,那麼返回該將要插入的數據,否則阻塞
     * @return 如果不阻塞,返回取到的數據
     */
    public String getEntity(){
        try {
            return this.synchronousQueue.take();
        } catch (InterruptedException e) {
            System.out.println("線程ID(" + Thread.currentThread().getId() + "),線程名(" + Thread.currentThread().getName() + ") 拋出異常。");
            System.out.println("異常原因:取數據線程在阻塞時被中斷了。");
            e.printStackTrace();
            return null;
        }
    }
}

測試代碼:

public class SynchronousQueueTest {
    public static SynchronousQueue<String> synchronousQueue = new SynchronousQueue<>(false);
    public static void main(String[] args) throws InterruptedException {
        ThreadPut_One threadPut_One = new ThreadPut_One(SynchronousQueueTest.synchronousQueue);
        ThreadPut_Two threadPut_two = new 
ThreadPut_Two(SynchronousQueueTest.synchronousQueue);
        threadPut_One.setPriority(3);
        threadPut_two.setPriority(8);
        threadPut_One.start();
        Thread.sleep(2000);
        threadPut_two.start();
        Thread.sleep(1000);
        ThreadTake threadTake = new ThreadTake(SynchronousQueueTest.synchronousQueue);
        threadTake.start();
    }
}

運行結果:

分析:首先說一下,代碼的運行結果肯定是符合SynchronousQueue的特性的,即便是我自己給線程設置了高低優先級,仍然沒有改變SynchronousQueue的特性。有些網友在測試的時候,明明是公平模式,先到先執行,但是測試結果卻不對,需要將這兩個線程的啓動間隔一定的時間,確保第一個啓動的線程能夠在第二個線程啓動之前被操作系統運行,因爲如果沒有間隔時間的話,兩個線程start()之後,就處於就緒狀態,等待操作系統調度,但是先調度誰不好說。

4.4、PriorityBlockingQueue 類

線程安全。這是一個自動維護有序性的阻塞隊列,一看到Priority就知道是按照優先級的,這裏的優先級可不是指線程的優先級,而是說存儲在隊列的元素必須是可以比較的(必須實現comparable接口,並重寫compareTo方法),經過比較後,可以確定誰大誰小,然後按照順序排好序。

PriorityBlockingQueue實際上是一個小頂堆(完全二叉樹形式),用數組去實現的小頂堆,具體怎麼用數組去實現的呢?如下,順便還可以學習一下堆排序:

因爲完全二叉樹有此特性:對於一個節點的編號N(或者叫數組裏的索引),它的父節點的編號 = (N - 1) / 2,它的左子節點的編號 = 2 x N + 1,它的右子節點的編號 = (N + 1) x 2。在編程語言裏這種計算公式是正確的,比如 7 和 2 都是int類型, 7 / 2 = 3。

如果添加一個元素呢,如果自動維護小頂堆的特性呢?比如添加一個元素10,會將10添加到末尾,再進行一次堆排序即可。

    

如果從隊列中取走頂元素(7),過程是怎麼樣的呢?將最後一個元素26直接放到頂端,然後進行一次堆排序即可,每次要換位置的時候,比如26 和 9 換位置,而不是和 13 換位置,因爲總是和左右子節點中最小的換位置。

           

如果刪除一個元素呢?同理,將最後一個元素替換掉刪除元素,然後進行一次堆排序。

自己的感悟:爲什麼總是用最後元素呢,爲什麼添加元素時,總是添加到最後位置呢?因爲數組最忌諱的就是大量的移動數據,所以利用堆的最後一個位置或者最後一個元素可以避免大量數據的移動,頂多在堆排序的時候,多次替換2個位置的值。

另外,PriorityBlockingQueue的數組默認大小爲11,動態擴展(創建更長的數組,將舊數組數據複製到新數組),因此PriorityBlockingQueue默認一個無界隊列。當然也可以設定數組的大小。

構造方法:

1、public PriorityBlockingQueue(); //默認數組初始大小爲11.

2、public PriorityBlockingQueue(int initialCapacity); //設定數組大小。

3、public PriorityBlockingQueue(int initialCapacity, Comparator<? super E> comparator); //指定數組大小,指定比較器

4、public PriorityBlockingQueue(Collection<? extends E> c); //指定隊列裏的初始化數據。

4.5、 DeleyQueue 類

DelayQueue的作用:是一個延遲隊列,隊列裏的元素都必須設置一個過期時間(比如10秒後過期),然後根據當前系統時刻 + 過期時間 算出過期時刻,元素就按照過期時刻進行升序排序,因此,越早過期的元素越排在隊列前面,當有線程來獲取元素時,先看看隊列頭元素是否已經過期,如果沒有,就返回null 或者 線程阻塞直到頭元素過期再獲取它。

DelayQueue本質上是在PriorityBlockingQueue上封裝的,因爲這樣就可以按照過期時刻的大小來排序了,每次獲取的元素都是最先過期的元素。

DelayQueue實現了BlockingQueue接口,說明具備阻塞接口的特性,同時隊列中元素類型規定了必須實現Delayed接口。

4.5.1 講講隊列中元素需要滿足什麼要求

首先看一下Delayed接口:

public interface Delayed extends Comparable<Delayed> {
    //獲得當前元素剩餘時間,以unit爲單位換算,並返回
    long getDelay(TimeUnit unit);
}

我們的隊列元素必須實現Delayed接口,並且要重點實現getDelay()方法 和 compareTo()方法,因爲getDelay()方法被用於判斷當前元素是否過期,compareTo()方法被用於排序。舉個例子Message元素類:

delayNanoTime 存儲的是過期時刻,一般人爲設置過期時間都是按秒,所以構造方法中轉換的時候是按秒轉換的,如果你要按照其它的換算,也行。

public class Message implements Delayed {
    private String content;
    private long delayNanoTime;

    public Message(String content, long delayTime){
        this.content = content;
        this.delayNanoTime = System.nanoTime() + TimeUnit.SECONDS.toNanos(delayTime);
    }

    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(this.delayNanoTime - System.nanoTime(), TimeUnit.NANOSECONDS);
    }

    @Override
    public int compareTo(Delayed o) {
        long v = this.delayNanoTime - ((Message)o).delayNanoTime;
        if(v < 0){
            return -1;
        }else if(v > 0){
            return 1;
        }else {
            return 0;
        }
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public long getDelayNanoTime() {
        return delayNanoTime;
    }

    public void setDelayNanoTime(long delayNanoTime) {
        this.delayNanoTime = delayNanoTime;
    }
}

4.5.2、 看看DelayQueue的源碼(這個源碼不難,耐心點看吧)

在看看DelayQueue源碼:

值得說一下的是,1、一般這種源碼裏面講什麼隊列是無界的,實際上是有界的,只不過最大允許的容量是Integer的最大值,基本可以看作是無界的;2、正如本章標題所示,線程安全是由Lock實現的,線程的阻塞和喚醒也是Lock機制實現的;3、當判斷一個元素是否過期時,使用的是getDelay()方法,即查看剩餘時間是否大於0。

至於使用例子就不寫了。

public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
    implements BlockingQueue<E> {

    private final transient ReentrantLock lock = new ReentrantLock();
    private final PriorityQueue<E> q = new PriorityQueue<E>();
    private Thread leader = null;
    private final Condition available = lock.newCondition();
    //隊列初始化,空
    public DelayQueue() {}
    //隊列初始化,將集合中的元素添加到隊列中作爲初始化數據
    public DelayQueue(Collection<? extends E> c) {
        this.addAll(c);
    }
    //添加元素,其實也是調用的offer()方法
    public boolean add(E e) {
        return offer(e);
    }

    //添加元素,元素不能爲null
    public boolean offer(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            q.offer(e);
            if (q.peek() == e) {
                leader = null;
                available.signal();
            }
            return true;
        } finally {
            lock.unlock();
        }
    }

    //添加元素,其實還是用的offer()方法
    public void put(E e) {
        offer(e);
    }

    //添加元素,超時參數沒有意義,因爲添加元素從來不阻塞,所以無所謂超時。
    public boolean offer(E e, long timeout, TimeUnit unit) {
        return offer(e);
    }

    //獲取隊列頭元素並刪除(前提是過期時間到了),否則返回null
    //如果判斷元素是否過期呢?通過判斷元素的剩餘時間是否小於等於0
    public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            E first = q.peek();
            if (first == null || first.getDelay(NANOSECONDS) > 0)
                return null;
            else
                return q.poll();
        } finally {
            lock.unlock();
        }
    }

    //功能和poll一樣,但是如果沒有一個過期時間到了的頭元素,就會等待
    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            for (;;) {
                E first = q.peek();
                if (first == null)
                    available.await();
                else {
                    long delay = first.getDelay(NANOSECONDS);
                    if (delay <= 0)
                        return q.poll();
                    first = null; // don't retain ref while waiting
                    if (leader != null)
                        available.await();
                    else {
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                            available.awaitNanos(delay);
                        } finally {
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
            if (leader == null && q.peek() != null)
                available.signal();
            lock.unlock();
        }
    }

  //獲取並刪除隊列頭元素(前提是過期了),如果在指定時間內,還無法獲得過期的隊列頭元素,返回null
    public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            for (;;) {
                E first = q.peek();
                if (first == null) {
                    if (nanos <= 0)
                        return null;
                    else
                        nanos = available.awaitNanos(nanos);
                } else {
                    long delay = first.getDelay(NANOSECONDS);
                    if (delay <= 0)
                        return q.poll();
                    if (nanos <= 0)
                        return null;
                    first = null; // don't retain ref while waiting
                    if (nanos < delay || leader != null)
                        nanos = available.awaitNanos(nanos);
                    else {
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                            long timeLeft = available.awaitNanos(delay);
                            nanos -= delay - timeLeft;
                        } finally {
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
            if (leader == null && q.peek() != null)
                available.signal();
            lock.unlock();
        }
    }

    //返回隊列頭元素,如果隊列爲空,就返回bull
    public E peek() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return q.peek();
        } finally {
            lock.unlock();
        }
    }
    //返回隊列實際的大小
    public int size() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return q.size();
        } finally {
            lock.unlock();
        }
    }

    //返回隊列頭的元素(前提是過期了),否則返回null
    private E peekExpired() {
        // assert lock.isHeldByCurrentThread();
        E first = q.peek();
        return (first == null || first.getDelay(NANOSECONDS) > 0) ?
            null : first;
    }

    public int drainTo(Collection<? super E> c) {
        if (c == null)
            throw new NullPointerException();
        if (c == this)
            throw new IllegalArgumentException();
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            int n = 0;
            for (E e; (e = peekExpired()) != null;) {
                c.add(e);       // In this order, in case add() throws.
                q.poll();
                ++n;
            }
            return n;
        } finally {
            lock.unlock();
        }
    }

    //這個方法上文將過,去看看吧
    public int drainTo(Collection<? super E> c, int maxElements) {
        if (c == null)
            throw new NullPointerException();
        if (c == this)
            throw new IllegalArgumentException();
        if (maxElements <= 0)
            return 0;
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            int n = 0;
            for (E e; n < maxElements && (e = peekExpired()) != null;) {
                c.add(e);       // In this order, in case add() throws.
                q.poll();
                ++n;
            }
            return n;
        } finally {
            lock.unlock();
        }
    }

    // 清空隊列
    public void clear() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            q.clear();
        } finally {
            lock.unlock();
        }
    }

    //返回隊列剩餘容量,但是沒什麼意義,反正隊列是無界的,就返回Integer最大值吧
    public int remainingCapacity() {
        return Integer.MAX_VALUE;
    }

    //將隊列轉換爲數組
    public Object[] toArray() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return q.toArray();
        } finally {
            lock.unlock();
        }
    }

    //將隊列轉換爲數組並存在數組a裏    
    public <T> T[] toArray(T[] a) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return q.toArray(a);
        } finally {
            lock.unlock();
        }
    }

    //刪除一個元素,不管它是否過期,只要存在於隊列中,就刪除
    public boolean remove(Object o) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return q.remove(o);
        } finally {
            lock.unlock();
        }
    }

    //這個方法不用管,它是供Irt迭代器調用的
    void removeEQ(Object o) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            for (Iterator<E> it = q.iterator(); it.hasNext(); ) {
                if (o == it.next()) {
                    it.remove();
                    break;
                }
            }
        } finally {
            lock.unlock();
        }
    }

    //返回此隊列的迭代器
    public Iterator<E> iterator() {
        return new Itr(toArray());
    }

    /**
     * 定義了一個針對DelayQueue隊列的迭代器,是在q數組的副本上進行迭代的.
     */
    private class Itr implements Iterator<E> {
        final Object[] array; // Array of all elements
        int cursor;           // index of next element to return
        int lastRet;          // index of last element, or -1 if no such

        Itr(Object[] array) {
            lastRet = -1;
            this.array = array;
        }

        public boolean hasNext() {
            return cursor < array.length;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            if (cursor >= array.length)
                throw new NoSuchElementException();
            lastRet = cursor;
            return (E)array[cursor++];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            removeEQ(array[lastRet]);
            lastRet = -1;
        }
    }

}

4.6、LinkedTransferQueue 類

這個隊列有點牛掰,優勢比較明顯,相當於SynchronousQueue 和 LinkedBlockingQueue 的綜合,隊列既有實際的存儲空間,又是線程安全(沒有使用鎖,因此更高效),建議學一學。

先看一個LinkedTransferQueue 類內部的一個靜態內部類(隊列的節點類Node):

static final class Node {
        // 當前節點是否是數據節點,true代表是,false代表是請求獲取數據節點
        final boolean isData;   
        // 節點的內容,如果是數據節點,則不爲null,如果是請求獲取數據節點,爲null
        volatile Object item;   
        // 下一個節點引用
        volatile Node next;
        // 如果操作此節點的線程是阻塞的,那麼waiter就記錄該線程,否則爲null
        volatile Thread waiter; 

        /**
         * 還有其他的方法和成員,此處省略吧,因爲都是系統底層級別的,本人一時無法弄明白
         */ 
    }

看完Node類之後,我們就明白了爲什麼很多資料裏都說 LinkedTransferQueue 比 SynchronousQueue 多了存儲數據的隊列。這種說法非常膚淺,可能這樣說的人也沒有弄明白這兩者的原理吧。真相是這樣的:SynchronousQueue中也是有隊列(公平模式下,如果是非公平模式,那就是棧了),只不過該隊列只用於存儲阻塞線程,然後讓消費者線程和生產者線程去匹配,至於兩者之間的數據傳輸,不會經過SynchronousQueue隊列的,因此才說SynchronousQueue隊列是沒有數據緩衝的;而LinkedTransferQueue隊列中的節點所包含的信息更加豐富,不僅可以包含線程,還可以包含數據。 而且LinkedTransferQueue隊列的優點還遠不止於此,稍後介紹。 

再看看LinkedTransferQueue源碼:

其實原理上的理解可以參照SynchronousQueue的原理圖,只不過,LinkedTransferQueue隊列節點包含了數據,而其操作隊列的方法不僅僅是阻塞的,還可以是非阻塞的,限時的(NOW,ASYNC,SYNC,TIMED)。

NOW(立刻去獲取數據或者傳輸數據,能做到就OK,做不到也要立即返回,不阻塞),針對tryTransfer()方法和poll()方法。

ASYNC(異步,不阻塞,因爲是添加數據,自己是生產者線程,如果有消費者線程了,那就直接給它,不會阻塞,如果沒有消費者線程,那就將自己作爲數據節點添加到隊列中,添加完就返回,也不阻塞),針對put、offer、add方法。

SYNC(同步,阻塞的,take方法是獲取數據,如果當前沒有生產者線程(數據節點),就將自己作爲請求數據節點放入隊列中阻塞等待吧;transfer方法是傳遞數據給消費者線程,如果此時有消費者線程,就給它,如果沒有,就將自己作爲數據節點放入隊列中阻塞等待),針對take,transfer方法。

TIMED(限時的,如果在規定時間內沒有完成操作,該阻塞就阻塞,該返回null就返回null)。

public class LinkedTransferQueue<E> extends AbstractQueue<E>
    implements TransferQueue<E>, java.io.Serializable {
    // 如果當前系統是多處理器的,那麼MP是true,否則是false。這個有點高級哦~~
    private static final boolean MP = Runtime.getRuntime().availableProcessors() > 1;
    
    private static final int NOW   = 0; // 立即執行,不阻塞,大不了就返回null
    private static final int ASYNC = 1; // 異步執行,用於添加元素,因爲添加元素不會阻塞
    private static final int SYNC  = 2; // 同步執行,該阻塞就阻塞
    private static final int TIMED = 3; // 立即執行,如果在一定時間內還不行,就返回null
    
    // 此方法是核心方法,其它操作方法幾乎都是調用的此方法
    private E xfer(E e, boolean haveData, int how, long nanos){...}
    
    // 添加元素,不阻塞的,實際還是調用的xfer()方法
    public void put(E e) {
        xfer(e, true, ASYNC, 0);
    }
    
    // 添加元素,不阻塞,和put一樣,只不過添加後,會返回一個true而已
    // 這個超時時間一點用都沒有,因爲添加元素不阻塞,因此不會超時
    public boolean offer(E e, long timeout, TimeUnit unit) {
        xfer(e, true, ASYNC, 0);
        return true;
    }

    // 和上面這個offer方法一模一樣
    public boolean offer(E e) {
        xfer(e, true, ASYNC, 0);
        return true;
    }
    
    // 添加元素,和offer方法一模一樣
    public boolean add(E e) {
        xfer(e, true, ASYNC, 0);
        return true;
    }
    
    // 嘗試將數據立刻傳輸給一個消費者線程,如果消費者線程不存在,不會將該數據放入隊列,
    //並且立即返回false,如果存在,則立即將數據傳給等待線程,並返回true
    public boolean tryTransfer(E e) {
        return xfer(e, true, NOW, 0) == null;
    }
    
    // 將數據傳輸給一個消費者線程,如果隊列頭就是一個消費者線程,那麼就立即給它,否則,就將自己
    // 排到隊列尾,阻塞等待消費者線程出現
    public void transfer(E e) throws InterruptedException {
        if (xfer(e, true, SYNC, 0) != null) {
            Thread.interrupted(); // failure possible only due to interrupt
            throw new InterruptedException();
        }
    }

    // 這個同理,只不過多了一個超時時間,如果在給定時間內,還沒有完成數據傳輸,就返回false
    public boolean tryTransfer(E e, long timeout, TimeUnit unit)
        throws InterruptedException {
        if (xfer(e, true, TIMED, unit.toNanos(timeout)) == null)
            return true;
        if (!Thread.interrupted())
            return false;
        throw new InterruptedException();
    }
    
    // 從隊列中獲取一個數據,如果沒有數據,那就將自己作爲請求數據的節點添加到隊列中,阻塞等待
    public E take() throws InterruptedException {
        E e = xfer(null, false, SYNC, 0);
        if (e != null)
            return e;
        Thread.interrupted();
        throw new InterruptedException();
    }

    // 在指定時間內如果沒有獲取到數據,返回null即可
    public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        E e = xfer(null, false, TIMED, unit.toNanos(timeout));
        if (e != null || !Thread.interrupted())
            return e;
        throw new InterruptedException();
    }

    // 立即去獲取數據,如果沒有,立刻返回null
    public E poll() {
        return xfer(null, false, NOW, 0);
    }

    // 刪除某個元素,得先找,只有確定在隊列中,才能刪除
    public boolean remove(Object o) {
        return findAndRemove(o);
    }
        
    // 是否包含該元素
    public boolean contains(Object o) {
        if (o == null) return false;
        for (Node p = head; p != null; p = succ(p)) {
            Object item = p.item;
            if (p.isData) {
                if (item != null && item != p && o.equals(item))
                    return true;
            }
            else if (item == null)
                break;
        }
        return false;
    }
    
    // 返回隊列的剩餘容量,沒什麼意義,返回的是Integer的最大值
    public int remainingCapacity() {
        return Integer.MAX_VALUE;
    }

    // 將隊列的每個元素都序列化,添加到輸出流中
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        s.defaultWriteObject();
        for (E e : this)
            s.writeObject(e);
        // Use trailing null as sentinel
        s.writeObject(null);
    }
    
    // 從對象的序列化流中讀取對象,然後把對象添加到隊列中,相當於反序列化
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        for (;;) {
            @SuppressWarnings("unchecked")
            E item = (E) s.readObject();
            if (item == null)
                break;
            else
                offer(item);
        }
    }

總結一下,LinkedTransferQueue的節點類型更加豐富,節點的內容也更加多(包含數據),隊列的操作方法的方式更加豐富。你想想,一個隊列中每個節點都包含了數據,是不是和LinkedBlockingQueue有點像呀,另外,消費數據操作和生成數據操作是相互匹配的,如果僅有消費者,沒有生產者,那消費者就要阻塞了,和SynchronousQueue的原理基本類似,。另外,LinkedTransferQueue隊列的操作方法更加豐富,分爲NOW,同步,異步,限時,這樣效率更加,比如我就總是用NOW方法或者異步方法,就可以減少阻塞了唄。

5、併發性集合

5.1、ConcurrentHashMap類

作用:和HashMap的作用基本類似,就是一個散列映射集合,只不過ConcurrentHashMap是線程安全的,並且支持多線程併發訪問。

結構:HashMap是數組 + 鏈表(8節點以上是紅黑樹),而ConcurrentHashMap的結構也是數組 + 鏈表(8節點以上是紅黑樹),只不過數據的元素類型是Node,Node是它內部的一個內部類,在多線程中,不是以整個ConcurrentHashMap對象作爲鎖,而是以Node節點作爲鎖,只要是多個線程訪問的Node不一樣,就不會相互阻塞,因此,ConcurrentHashMap具有高併發訪問的特性。

先看看內部類Node的源碼,基本瞭解一下Node是什麼樣的:

    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;  //哈希值,用於確定自己所存儲的位置
        final K key;  //key 鍵
        volatile V val; //value 值
        volatile Node<K,V> next; //下一個節點,用於在形成鏈表時用的

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

        public final K getKey()       { return key; }
        public final V getValue()     { return val; }
        public final int hashCode()   { return key.hashCode() ^ val.hashCode(); }
        public final String toString(){ return key + "=" + val; }
        public final V setValue(V value) {
            throw new UnsupportedOperationException();
        }
        // 判等方法
        public final boolean equals(Object o) {
            Object k, v, u; Map.Entry<?,?> e;
            return ((o instanceof Map.Entry) &&
                    (k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
                    (v = e.getValue()) != null &&
                    (k == key || k.equals(key)) &&
                    (v == (u = val) || v.equals(u)));
        }

        // 查找指定指定key值的Node節點
        Node<K,V> find(int h, Object k) {
            Node<K,V> e = this;
            if (k != null) {
                do {
                    K ek;
                    if (e.hash == h &&
                        ((ek = e.key) == k || (ek != null && k.equals(ek))))
                        return e;
                } while ((e = e.next) != null);
            }
            return null;
        }
    }

再看看ConcurrentHashMap的部分基本屬性, 對於ConcurrentHashMap的工作原理,個人的理解是:創建的時候,可以設置最大容量,因此最多隻能存儲指定容量的鍵值對,而且不會擴容,比如設置最大容量爲16,然而Node數組的長度不會是16,而是32,爲什麼實際的數組大小比最大容量大2倍呢?因爲要避免哈希衝突呀;另外,可以使用默認構造方法,數組大小默認爲16,負載因子爲0.75,存入數據時,如果發生哈希衝突,就以鏈表法解決,當某個鏈表的長度超過8時,就轉換爲紅黑樹,當紅黑樹的節點小於6的時候,就轉換爲鏈表;當添加數據後,數據的數量大於 16 * 0.75 時,說明要擴容了,就會創建一個更大容量(2倍)的數據,將原數組的節點複製到新數組去,節點的位置也會發生改變(通過相應的計算)。

連續幾次擴容,數組會很大,但是當map裏的元素變得很少時,會不會縮小容量呢?會。

public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
    implements ConcurrentMap<K,V>, Serializable {
    // 默認的數組大小爲16
    private static final int DEFAULT_CAPACITY = 16;
    // 數組最大容量,幾乎可以認爲是無界的
    static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    // 默認的併發級別
    private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
    // 默認的負載因子
    private static final float LOAD_FACTOR = 0.75f;
    // 鏈表長度超過多少就變爲紅黑樹,默認爲8
    static final int TREEIFY_THRESHOLD = 8;
    // 紅黑樹的節點數小於多少就變爲鏈表形式,默認爲6
    static final int UNTREEIFY_THRESHOLD = 6;
    // ConcurrentHashMap 默認能存儲的元素的最小容量
    static final int MIN_TREEIFY_CAPACITY = 64;
    
    // Node數組,懶加載模式,僅在第一個元素存入時才創建空間
    transient volatile Node<K,V>[] table;
    // 下一個Node數組,在擴容的時候用
    private transient volatile Node<K,V>[] nextTable;
}

常用方法:

1、public V put(K key, V value); //添加鍵值對

2、public void clear(); //清空所有內容

3、public int size(); //返回map中元素個數

4、public Collection<V> values(); //返回包含所有value的集合

5、public boolean contains(Object value); //是否包含某個鍵值對

6、 public boolean containsKey(Object key)l //是否包含某個key

7、public boolean containsValue(Object value); //是否包含某個value

8、public V get(Object key); //根據key獲取value

9、public boolean replace(K key, V oldValue, V newValue); //對於某個key-value,用新value替換舊value

10、public V replace(K key, V value); //替換value,不用指定舊value。

11、public V remove(Object key); //刪除

12、 public boolean isEmpty(); //是否爲空

13、public void putAll(Map<? extends K, ? extends V> m); //添加一個鍵值對集合

5.2、ConcurrentSkipListMap 類

介紹:按照key有序的映射結合,支持併發。(跳錶結構)

都有一個ConcurrentHashMap了,爲什麼還要搞一個這個呢?原因有2個:

1、有序的(按照key排序)。

2、同等數據量下,併發量越高,ConcurrentSkipListMap的性能體現比ConcurrentHashMap更好。

缺點:存取數據的時間複雜度爲O(log N),而ConcurrentHashMap比這個要小很多,具體多少不確定,因爲鏈表和紅黑樹會影響讀寫速度。

ConcurrentSkipListMap的結構原理是什麼呢?

它是一種跳錶結構實現的,什麼是跳錶結構呢,下面給個圖說明一下,跳錶結構如下圖,是個鏈表網,分爲多層,用空間去換時間,比如獲取數據23,從level 2 的 5 —>17 , 17 垂直下來到最底層,再從 17 —> 23,這樣的速度更快,因爲,對於一個單鏈表的話,就像在最底層要找到 23,需要進行 6 此比較操作 和 5 次索引移動,但是如果使用跳錶的話,從level 2開始,只需要進行 4 次比較操作 和 4 次索引移動。其實這個例子非常簡短,體現不出太大的優勢,如果鏈表很長,元素非常多,我們從高級別的level開始,一下就能精確地跳到鏈表的中間去,省去了很多時間。感覺有點像二分查找哦。

爲什麼要用跳錶結構呢?對於有序數組,能夠以 O(1) 的時間獲取到某個位置的數據,但是插入/刪除數據的時間複雜度比較大;對於鏈表,查找的時間複雜度大,但是方便插入和刪除數據。兩者各有優勢,魚和熊掌不可兼得,於是ConcurrentSkipListMap就做了一個平衡(用空間作爲代價),使用跳錶結構,能夠快速定位到某個區間,然後一層一層地下去,越來越精準,直到找到爲止。

源碼分析:上面的圖只是爲了方便理解,下面的圖纔是ConcurrentSkipListMap 的真實結構,由3種節點組成:Node、Index 和 HeadIndex 。Node是基礎節點,真正用於存儲<key,value>的節點;Index是用於快速定位區間的索引節點,不存儲鍵值對,只是有3個引用,分別指向Node節點,同一層level的右索引 Index,下一層 level 對應的Index;HeadIndex是每一層索引的頭結點,包含一個Index,而且還有一個level字段。

 

Node類的結構。 

    static final class Node<K,V> { //這是典型的單鏈表節點
        final K key;
        volatile Object value;
        volatile Node<K,V> next;
    }

Index類的結構。

    static class Index<K,V> { 
        final Node<K,V> node;  //指向存儲了<k, v>的節點Node
        final Index<K,V> down;  //指向了下一層對應的索引節點
        volatile Index<K,V> right; //指向同一層的右邊索引節點
    }

HeadIndex 類的結構。

    static final class HeadIndex<K,V> extends Index<K,V> {
        final int level;  //所處層級
        HeadIndex(Node<K,V> node, Index<K,V> down, Index<K,V> right, int level) {
            super(node, down, right);
            this.level = level;
        }
    }

至此,ConcurrentSkipListMap的結構原理算是理明白了,但是還有很多細節沒有弄明白,比如爲什麼它是高併發的,在此做個標記,以後補充。

基本使用方法。

由於ConcurrentSkipListMap是key有序的,肯定需要比較器,所以源碼裏面有一個字段:

final Comparator<? super K> comparator;    代表比較器。

1、構造方法

public ConcurrentSkipListMap(); //默認構造方法,比較器爲null,就會按照key進行自然排序,有些情況下排序結果未知。

public ConcurrentSkipListMap(Comparator<? super K> comparator); //指定比較器

public ConcurrentSkipListMap(Map<? extends K, ? extends V> m); //帶有初始化數據,但是比較器爲null

public ConcurrentSkipListMap(SortedMap<K, ? extends V> m); //帶有初始化數據,比較器就是m的比較器

2、常用方法

Map.Entry<K,V> ceilingEntry(K key);  // 返回與大於等於給定鍵的最小鍵關聯的鍵-值映射關係;若沒有,則返回 null

K ceilingKey(K key); // 返回大於等於給定鍵的最小鍵;若沒有,則返回 null

void clear(); // 清空

ConcurrentSkipListMap<K,V> clone(); // 返回此 ConcurrentSkipListMap 實例的淺表副本

Comparator<? super K> comparator(); // 返回key的比較器

boolean containsKey(Object key); // 是否包含指定key

boolean containsValue(Object value); // 是否包含指定value

NavigableSet<K> descendingKeySet(); // 返回包含所有鍵的逆序集合

ConcurrentNavigableMap<K,V> descendingMap(); // 返回包含所有<key, value>的逆序映射

Set<Map.Entry<K,V>> entrySet(); // 返回包含所有<key, value>的逆序集合

boolean equals(Object o); //判斷兩個map是否相等

Map.Entry<K,V> firstEntry();  //返回排在第一個位置的map

K firstKey() ; //返回第一個key

Map.Entry<K,V> floorEntry(K key) // 返回與小於等於給定鍵的最大鍵關聯的鍵-值映射關係;若沒有,則返回 null

K floorKey(K key) //返回小於等於給定鍵的最大鍵;若不存在,則返回 null

V get(Object key)  //返回指定key的value

boolean isEmpty()  //判斷是否爲空

NavigableSet<K> keySet()  //返回包含所有key的集合

Map.Entry<K,V> lastEntry()   //返回最後一個<key, value>

K lastKey()  //返回最後一個key

V put(K key, V value)  //添加一個鍵值對

V remove(Object key)  //刪除指定key的鍵值對

boolean remove(Object key, Object value) //刪除該<key, value>

V replace(K key, V value)   //用value替換掉key對應的值

boolean replace(K key, V oldValue, V newValue)  //用newValue替換掉<key, oldValue>中的oldValue

int size(); //返回<key, value>數量

Collection<V> values()  //返回包含所有value的集合

5.3、ConcurrentSkipListSet 類

ConcurrentSkipListSet 類 是基於ConcurrentSkipListMap實現的,就像TreeSet 是 基於TreeMap實現,一樣一樣的,ConcurrentSkipListSet 裏面的每一個元素都作爲 ConcurrentSkipListMap 的 key。

因此,ConcurrentSkipListSet中的元素不能重複。

 

 

 

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