Java集合概述(上)

Java集合概述(上)

前言

先說說,爲什麼要寫這麼一篇博客(我總是喜歡寫原因)。因爲最近到年底了,正好又要準備面試,所以在做各方面的技術總結。而Java集合是Java非常重要的一部分,自己前前後後也花了不少時間學習,但是一直比較零散。所以,打算趁着這個機會,來寫一個總結。

由於能力有限,這方面沒有足夠積累,如果有什麼問題,還請指出。謝謝。

集合分類,主要分爲:

  • Collection(繼承Iterable接口):按照單個元素存儲的集合
    • List:一種線性數據結構的主要體現。有序,可重複
    • Set:一種不允許出現重複元素的集合。無序(插入順序與輸出順序不一致),不可重複
    • Queue:一種先進先出(FIFO)的數據結構。有序,可重複,先進先出
  • Map(無繼承接口):按照K-V存儲的Map
    • keySet:可以查看所有的Key。底層實現各不相同。ConcurrentHashMap則是採用的自定義實現的KeySetView內部靜態類(實現了Set接口),而HashMap這樣的AbstractMap子類,則是是Set接口
    • values:同上,ConcurrentHashMap採用ValueSetView,HashMap採用Set接口
    • entrySet:同上,ConcurrentHashMap採用EntrySetView,HashMap採用Set接口

原本Map是打算按照 AbstractMap;SortedMap;ConcurrentMap;來分類,但是發現這個分類屬於理論價值,大於使用價值,也可能是我現在層次不夠吧。最後還是學着孤盡大佬在《碼處高效》中那樣,通過三個視圖,來觀察Map。具體後面闡述,我也只是闡述其中部分的Map。

論述方面,我主要會從數據組織方式(底層數據存儲方式),數據處理方式(如HashMap的put操作等),特點小結結三個方面進行闡述。但是由於內容量的問題,這裏並不會非常細緻地闡述代碼實現。

最後,由於內容量的緣故,這部分內容,我將分爲兩個部分。這篇博客主要論述List與Map,而Set與Queue放在另外一篇博客。

一,List

ArrayList

數據組織方式


	transient Object[] elementData; // non-private to simplify nested class access

ArrayList的底層是一個Object類型的數組。那麼ArrayList就有着和數組一樣的特點:隨機查詢快,但數據的插入,刪除慢(因爲很可能需要移動其他元素)。

數據處理方式

add

    public void add(int index, E element) {
		// 校驗index是否在0-size範圍內,如果不是,拋出異常IndexOutOfBoundsException
        rangeCheckForAdd(index);

		// 這個操作後面有多個操作,總結一下,就是校驗,判斷是否需要擴容,擴容。
        ensureCapacityInternal(size + 1);  // Increments modCount!!
		// 通過System.arraycopy操作,爲新添加的元素element,在elementData數組的對應index位置,騰出空間
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
		// 緊跟着上面的操作elementData數組的index位置,賦值爲element
        elementData[index] = element;
		// 數組元素數量+1
        size++;
    }

grow

	// 簡單來說, 就是根據所給的minCapacity,計算對應容量(2的冪次方),然後校驗容量,最後擴容
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

小結

根據其數據組織方式,與數據處理方式,可以明確:

  • ArrayList隨機查詢快(直接通過index定位數據中具體元素)
  • ArrayList插入與刪除操作慢(涉及數組元素移動操作System.arraycopy,還可能涉及擴容操作)
  • ArrayList是容量可變的(自帶擴容操作,初始化,默認爲DEFAULT_CAPACITY=10)
  • ArrayList是非線程安全的(沒有線程安全措施)

補充:

  • ArrayList的默認容量爲10(即無參構造時)
  • 出於性能考慮,避免多次擴容,最好在初始化時設置對應size(即使後面不夠了,它也可以自動擴容)

LinkedList

數據組織方式


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

LinkedList的底層是自定義的Node雙向鏈表。那麼LinkedList就有着和鏈表一樣的特點:數據的插入與刪除快,但是隨機訪問慢。

數據處理方式

add

    public void add(int index, E element) {
		// 數據校驗,index是否超出0-size範圍
        checkPositionIndex(index);

        if (index == size)
			// 如果插入的元素是放在最後一個,那就執行尾插入操作(因爲LinkedList是有保存first與last兩個Node的,所以可以直接操作)
            linkLast(element);
        else
			// 首先通過node(index)方法,獲取到當前index位置的Node元素(內部實現,依舊是遍歷。不過會根據index與列表中值的比較結果,判斷是從first開始遍歷,還是從last開始遍歷),再通過linkBefore方法,進行插入操作
            linkBefore(element, node(index));
    }

peek
	
	// LinkedList實現了Deque接口,所以需要實現其中的peek方法。獲取當前數組的第一個元素,但不進行刪除操作
    public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }

小結

根據其數據組織方式,與數據處理方式,可以明確:

  • LinkedList隨機查詢慢(需要進行遍歷查詢,雖然通過列表中值,降低了一半的遍歷範圍,但其數據組織方式決定了它的速度慢):

    測試表明,10W條數據,LinkedList的隨即提取速度與ArrayList相比,存在數百倍的差距(引自《碼出高效》)

  • LinkedList插入與刪除操作快(依舊需要靠遍歷來定位目標元素,但只需要修改鏈表節點的前後節點引用)

  • LinkedList是容量可變的(鏈表可以隨意鏈接)

  • LinkedList是非線程安全的(沒有線程安全措施)

補充:

  • 通過鏈表,可以有效地將零散的內存單元通過引用的方式串聯起來,形成按鏈路順序查找的線性結構,內存利用率較高(引用自《碼出高效》)

Vector

Vector本質與ArrayList沒太大區別,底層同樣是Object數組,默認大小依舊爲10(不過Vector採用的是不推薦的魔法數字)。

唯一的區別,就是Vector在關鍵方法上添加了Sychronized關鍵字,來確保線程安全。

但是,由於處理得較爲粗糙,以及其特點,所以性能很差,基本已經被拋棄。

這裏就不再贅述了。

CopyOnWriteArrayList

CopyOnWriteArrayList,作爲COW容器的一員,其思想就是空間換時間,主要針對讀多寫少的場景。當有元素寫入時,會新建一個數組,將原有數組的元素複製過來,然後進行寫操作(此時數組的讀操作,還是針對原數組)。在寫操作完成後,會將讀操作針對的數組引用,從原數組指向新數組。這樣就可以在寫操作進行時,不影響讀操作的進行。

數據組織方式


    /** The array, accessed only via getArray/setArray. */
	// 一方面通過transient避免序列化,另一方面通過volatile確保可見性,從而確保單個屬性(這裏是引用變量)的線程安全
    private transient volatile Object[] array;

數據處理方式

add

    public void add(int index, E element) {
        final ReentrantLock lock = this.lock;
		// 進行加鎖,同時只能有一個寫操作
		// 另外,加鎖操作放在try塊外,一方面是try規範(lock操作並不會發生異常,並且可以減少try塊大小),另一方面是避免加鎖失敗,finally的釋放鎖出現IllegalMonitorStateException異常
        lock.lock();
        try {
			// 獲取原有數組,並賦值給elements(引用變量)
            Object[] elements = getArray();
            int len = elements.length;
			// 數據校驗
            if (index > len || index < 0)
                throw new IndexOutOfBoundsException("Index: "+index+
                                                    ", Size: "+len);
			// 下面的操作,就是對原有數組進行復制,並賦值給newElements(並且留出index位置)
            Object[] newElements;
            int numMoved = len - index;
            if (numMoved == 0)
                newElements = Arrays.copyOf(elements, len + 1);
            else {
                newElements = new Object[len + 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index, newElements, index + 1,
                                 numMoved);
            }
			// 設置新數組index位置的值爲element,完成賦值操作
            newElements[index] = element;
			// 將數組引用(讀操作正在讀的數組引用)改爲newElements
            setArray(newElements);
        } finally {
			// 無論是否異常,都需要釋放鎖,
            lock.unlock();
        }
    }

最大的特色,就是這部分了。至於remove操作,都是類似的。故不再贅述。

小結

由於CopyOnWriteArrayList的數據組織方式與ArrayList一致,也是採用的數組,故:

  • CopyOnWriteArrayList隨機查詢快
  • CopyOnWriteArrayList插入與讀寫慢
  • CopyOnWriteArrayList是容量可變的(每次進行增刪的寫操作,都會新建一個數組,進而進行替換)

補充:

  • CopyOnWriteArrayList是線程安全的(讀寫操作隔離,寫操作通過ReentrantLock確保線程安全)
  • CopyOnWriteArrayList的寫操作不直接影響讀操作(兩者在內存上針對的不是同一個數組)
  • CopyOnWriteArrayList只適用於讀多寫少場景(畢竟寫操作是需要複製數組)
  • CopyOnWriteArrayList佔據雙倍內存(因爲寫操作的時候需要複製數組)
  • CopyOnWriteArrayList的性能會隨着寫入頻次與數組大小上升,而快速下降(寫入頻次m x 數組大小n)

推薦:高併發請求下,可以攢一下要進行的寫操作(如添加,或刪除,可以分開保存),然後進行addAll或removeAll操作。這樣可以有效減低資源消耗。但是這個攢的度需要好好把握,就和請求合併一樣,需要好好權衡。

二,Map

###TreeMap

數據組織方式

數據處理方式

小結

HashMap

HashMap一方面是工作中用的非常多的集合,另一方面是面試的高頻(我每次面試幾乎都會被人問這個)。

而HashMap,與ConcurrentHashMap一樣,都存在Jdk8之前與Jdk8之後的區別。不過,我應該會以Jdk8之後爲重點,畢竟現在SpringBoot2.x都要求Jdk8了。

數據組織方式

Jdk8之前

	// jdk8之前,其底層是數組+鏈表
	// 鏈表底層Entry是Map的內部接口
	transient Entry<K, V>[] table;

Jdk8之後

	transient Node<K, V>[] table;


	static class Node<K, V> implements Map.Entry<K, V> {
		final int hash;
		final K key;
		V value;
		Node<K, V> next;
	}

數據處理方式

Jdk8之前的put方法(註釋並不多,因爲我沒有源碼,我是按照筆記圖片,手擼的這段)

	public V put (K key, V value) {
		// HashMap採用延遲創建。判斷當前table是否爲空。如果爲空,就根據默認值15,創建一個數組,並賦值給table
		if (table == EMPTY_TABLE) {
			inflateTable(threshold);
		}
		// 數據校驗
		if ( key == null) 
			return putForNullKey(value);
		// 根據key,計算哈希值
		int hash = hash(key);
		// 通過indexFor(內部貌似採用位運算),根據key的哈希值與數組長度,計算該K-V鍵值對在數組中的下標i
		int i = indexFor(hash, table.length);
		for (Entry<K, V> e = table[i]; e != null; e = e.next) {
			Object k;
			if (e.hash = hash && ((k = e.key) || key.equals(k))) {
				V oldValue = e.value;
				e.value = value;
				e.recordAccess(this);
				return oldValue;
			}
		}

			// 記錄修改次數+1,類似版本號
		modCount++;
		addEntry(hash, key, value, i);
		return null;
	}

Jdk8之後的put方法

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }


	// 計算key的哈希值(數據校驗,key的哈希值,即其hashCode)
    static final int hash(Object key) {
        int h;
		// 通過其hashCode的高16位與其低16位的異或運算,既降低系統性能開銷,又避免高位不參加下標運算造成的碰撞
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }


	// 執行主要put操作
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
		// 從下面這個代碼塊,可以看出Java8後的HashMap等,代碼晦澀不少
        if ((tab = table) == null || (n = tab.length) == 0)
			// 如果table爲null,或table.length爲0(其中混雜了賦值語句),就進行進行初始化操作(通過resize()操作,這點與Spring的refresh()應用是一致的),並將其長度賦值給n(注意這裏,都賦值給了局部變量,而非全局變量)
            n = (tab = resize()).length;
		// 根據key的hash值,計算其下標,並判斷數組中對應下標位置是否爲null
        if ((p = tab[i = (n - 1) & hash]) == null)
			// 如果對應位置爲null,直接通過newNode方法(生成Node),設置數組對應i位置爲對應新Node
            tab[i] = newNode(hash, key, value, null);
        else {
			// 如果對應位置不爲null,那就需要進行鏈表操作,進而判斷是否樹化(紅黑樹),是否擴容等
            Node<K,V> e; K k;
			// 通過hash與equals等,判斷新添加值的key與已存在值的key是否真正相等
			// 這裏擴展兩點:第一,判斷對象是否相等,必須hashcode與equals都判斷相等。前者避免兩個對象只是值,但不是同一個對象(兩位都是p9大佬,不代表兩位就是同一個人)。後者避免哈希碰撞問題(即使是兩個不同的對象的內存地址,也可能哈希值相等)
			// 第二,我看到這裏的時候,比較擔心,會不會出現value相等,但是hashCode不同,導致這裏判斷爲false。然後我發現包裝類型,早就重寫了hashCode方法,如Integer的hashCode就直接返回value
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
				// 如果相等,就直接更新對應Node即可
                e = p;
			// 如果上面判斷失敗,則判斷原有的數組元素,是不是已經樹化(不再是Node類型,而是TreeNode,當然TreeNode依舊是由Node構成的)
            else if (p instanceof TreeNode)
				// 如果原有數組元素已經樹化,那麼就進行調用putTreeVal方法,將當前元素,置入目標紅黑樹中(其中涉及紅黑樹的旋轉等操作)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
			// 如果不是空,也不是相同元素,更不是紅黑樹,那說明那已經是一個鏈表(已經由多個元素),或即將成爲鏈表(已經有一個元素,並即將添加一個新的元素)
            else {
				// 遍歷對應鏈表元素,並通過binCount記錄鏈表已存在的元素數
                for (int binCount = 0; ; ++binCount) {
					// 如果e=p.next()爲null,說明達到了鏈表的最後(e的前一個值爲當前鏈表的最後一個元素)
                    if ((e = p.next) == null) {
						// 通過newNode獲得對應p的Node,並將其設置爲鏈表的最後一個元素
                        p.next = newNode(hash, key, value, null);
						// 通過binCount,判斷鏈表的長度是否達到了樹化的閾值
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
							// 達到閾值,則通過當前table數組與hash值,以及treefyBin方法,將當前數組位置的鏈表樹化
                            treeifyBin(tab, hash);
                        break;
                    }
					// 在遍歷過程中,找到了相同的元素,即跳過(因爲內容相同)
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
					// 該賦值操作,屬於鏈表的操作,從而繼續鏈表遍歷
                    p = e;
                }
            }
			// 下面這段代碼,就涉及到HashMap的putIfAbsent(也是調用putVal,只是第四個參數onlyIfAbsent不同)
			// 簡單來說,就是遇到key相同的元素,怎麼處理。put操作是直接賦值,而putIfAbsent則是判斷對應key的value是否爲null,如果是null,纔會賦值。否則就不變(類似Redis)
			// 只不過,這個過程通過新增的第四個參數控制,從而確保同一套代碼(putVal方法),實現兩種不同功能(put與putIfAbsent)
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
		// 版本號
        ++modCount;
		// 一方面size前綴自增,另一方面,判斷自增後的size是否超過閾值(默認16*0.75=12,數組容量*負載因子)
        if (++size > threshold)
			// 擴容(擴容2倍後,重排)
            resize();
		// 空方法,爲子類保留的,如LinkedHashMap
        afterNodeInsertion(evict);
        return null;
    }

這個方法可以算是HashMap的核心,畢竟通過這個方法,也算是摸到了HashMap的運行機制了。

流程簡述:

  1. 如果HashMap的底層數組沒有初始化,則通過resize()方法進行構建
  2. 對key計算hash值,然後再計算下標
  3. 如果數組對應下標位置爲null(這裏我認爲不該用哈希碰撞),則直接放入對應位置
  4. 如果數組對應下標位置爲TreeNode(即對應位置已經樹化),則通過putTreeVal方法,將對應Node置入樹中
  5. 否則遍歷數組對應下標位置的鏈表,將對應Node置入
  6. 如果鏈表的長度超過閾值,則進行樹化操作
  7. 如果節點存在舊值,直接替換
  8. 如果數組的元素數量超過閾值(數組容量*負載因子),則進行擴容(擴容2倍,重排)
Jdk8之後的get方法

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


	// 這裏我覺得沒什麼說的。根據不同情況,分別從數組,紅黑樹,數組來獲取目標元素
    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

小結

就使用場景而言,《碼出高效》給出這樣一句話:

除局部方法或絕對線程安全的情形外,優先推薦ConcurrentHashMap。兩者雖然性能相差無幾,但後者解決了高併發下的線程安全問題。HashMap的死鏈問題及擴容數據丟失問題是慎用HashMap的兩個主要原因。

這裏,我忍不住站在Java工程師的角度,推薦《碼出高效》以及配套的《阿里Java開發手冊》。作爲一名也算看過不少技術書籍的開發者,這兩本書在我這兒,也算得上是優秀書籍了。

不過,文中也提到,這種情形,在Jdk8之後有所修復,改善。具體的,可以看看書籍(主要內容有點多)。

ConcurrentHashMap

ConcurrentHashMap部分,我將只描述Jdk8之後的版本。

而Jdk8之前的版本,其實底層就是類似HashTable的Segament組成的數組。通過分段鎖,達成線程安全。算是HashTable與HashMap的折中方案。複雜度並不是很高,不過Jdk8之後的版本,就較爲複雜。首先,引入紅黑樹,優化存儲結構。其次,取消原有的分段鎖設計,採用了更高效的線程安全設計方案(利用了無鎖操作CAS與頭節點同步鎖等)。最後,使用了更優化的方式統計集合內的元素數量(引用自《碼出高效》,我還真沒注意到這點)。

數據組織方式


	transient volatile Node<K,V>[] table;


	static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        volatile V val;
        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;
        }

		// 此處省略其內部方法,感興趣的,可以自行查看
	}

從上述來看,ConcurrentHashMap的底層數據組織爲數組+鏈表。依據Jdk8後的HashMap,可以推測,在對應條件下,鏈表會轉爲紅黑樹結構。事實也是如此,請看下代碼。


	static final class TreeNode<K,V> extends Node<K,V> {
        TreeNode<K,V> parent;  // red-black tree links
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;    // needed to unlink next upon deletion
        boolean red;

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

		// 此處省略其內部方法,感興趣的,可以自行查看
	}

ConcurrentHashMap,與HashMap一樣,其內部也有專門爲紅黑樹服務的TreeNode。

所以,從數據組織方面來看,其實ConcurrentHashMap與同版本的HashMap,可以說就是一個模子刻出來的(畢竟都是Doug Lea帶着擼的)。

兩者的區別,或者說ConcurrentHashMap的精妙之處,就在於ConcurrentHashMap對多線程的考慮與處理。

其中的細節挺多的,我只闡述我對其中一些大頭的理解(因爲很多細節,我也不知道,也是看了大佬的總結,才發現)。

數據處理方式

put

    public V put(K key, V value) {
        return putVal(key, value, false);
    }

    /** Implementation for put and putIfAbsent */
    final V putVal(K key, V value, boolean onlyIfAbsent) {
		// 數據校驗,如果key或value爲Null,直接NPE
        if (key == null || value == null) throw new NullPointerException();
		// 通過spread方法,計算hash值(本質還是與HashMap一樣,針對hashCode進行高低16位異或計算等)
        int hash = spread(key.hashCode());
		// 記錄鏈表長度
        int binCount = 0;
		// 這裏的循環操作是爲了之後的CAS操作(就是CAS的自旋操作)
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            if (tab == null || (n = tab.length) == 0)
				// 同HashMap一樣,如果數組爲空或長度爲0,則進行數組初始化操作(循環頭中已經完成賦值操作)
                tab = initTable();
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
				// 如果數組對應位置爲null,則通過CAS操作,進行值的插入操作
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
			// 如果對應節點的Node.hash值爲MOVED=-1
            else if ((fh = f.hash) == MOVED)
				// 進行resize協助操作(具體協助方式,還沒研究)
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
						// 如果數組對應位置(即首節點)的哈希值大於等於零(樹化後等情況下,對應位置哈希值小於零)
						// 	static final int MOVED     = -1; // hash for forwarding nodes
   						//	static final int TREEBIN   = -2; // hash for roots of trees
   						//	static final int RESERVED  = -3; // hash for transient reservations
                        if (fh >= 0) {
							// 說明此情況下,數組對應位置,存儲的是鏈表。進行鏈表插入,遍歷操作(具體參照HashMap的put操作)
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                Node<K,V> pred = e;
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
						// 如果數組對應位置的元素,是樹化節點(即爲TreeBin實例)
                        else if (f instanceof TreeBin) {
                            Node<K,V> p;
                            binCount = 2;
							// 調用putTreeVal方法,進行紅黑樹的值插入操作
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
								// 判斷onlylfAbsent參數,進行val設置。具體參照HashMap的put方法的對應位置解釋
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
				// 前面的各類操作,都會計算binCount(數組當前位置存儲的節點數)
                if (binCount != 0) {
					// 如果對應節點數超過了樹化閾值TREEIFY_THRESHOLD=8
                    if (binCount >= TREEIFY_THRESHOLD)
						// 對數組當前位置,進行樹化操作
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
			// 計數
        addCount(1L, binCount);
        return null;
    }

小結

ConcurrentHashMap的魅力在於其線程安全的實現,有機會好好研究研究,專門寫一個相關的博客。

三,總結

其實,Java集合主要從兩個維度分析。一個是底層數據組織方式,如鏈表與數組(基本就這兩種,或者如HashMap那樣組合兩種)。另一個是線程安全方式,就是線程安全與非線程安全。

最後就是由於一些底層數據組織方式的調整,帶來的循環,有序等特性。

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