1. 程式人生 > >深入理解Java集合之Map

深入理解Java集合之Map

Map筆錄

    Map 提供了一個更通用的元素儲存方法。 Map 集合類用於儲存元素對(稱作“鍵”和“值”),其中每個鍵對映到一個值。標準的Java類庫中包含了Map的幾種基本實現,包括HashMap、TreeMap、LinkedHashMap、WeakHashMap、ConcurrentHashMap、IdentityHashMap。它們都有相同的介面Map、但是行為特性各不相同,這表現在效率、鍵值對的儲存以及呈現次序、物件的儲存週期、對映表如何在多執行緒程式中工作和判定“鍵”等價的策略等方面。


HashMap

JDK1.8之前HashMap是一個連結串列雜湊的資料結構,也就是採用了鏈地址法,利用連結串列來解決雜湊表的衝突,JDK1.8之後又多加了一個紅黑樹,也就是說是桶位(bucket)+連結串列+紅黑樹來實現HashMap,當連結串列長度超過閥值8時,將連結串列轉化為紅黑樹,這樣大大減少了查詢的時間。

HashMap執行緒不安全,效率高。

    底層實現的原理:

        首先有一個數組,儲存的是連結串列的表頭元素,當新增玉一個元素(key-value)時首先計算key的hash值,以此來確定插入的陣列位置,在計算hash值時,會產生衝突,也就是說可能存在同一hash值的元素已經被放在陣列的同一位置,這時候就要新增到同一hash值的後面,也就是用連結串列的方式儲存,同一連結串列的hash值是相同的,而放連結串列長度太長時,連結串列就會轉換成紅黑樹。以下就是HashMap的原理圖:


        定義:

public class HashMap<k,v> extends AbstractMap<k,v> implements Map<k,v>, Cloneable, Serializable

        重要屬性:

	private static final long serialVersionUID = 362498820763181265L;
	static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16,預設的初始容量,必須是2的冪。
	static final int MAXIMUM_CAPACITY = 1 << 30;// 最大容量(必須是2的冪且小於2的30次方,傳入容量過大將被這個值替換)
	static final float DEFAULT_LOAD_FACTOR = 0.75f;// 填充比
	// 當add一個元素到某個位桶,其連結串列長度達到8時將連結串列轉換為紅黑樹
	static final int TREEIFY_THRESHOLD = 8;
	static final int UNTREEIFY_THRESHOLD = 6;
	static final int MIN_TREEIFY_CAPACITY = 64;
	transient Node<k, v>[] table;// 儲存元素的陣列
	transient Set<map.entry<k, v>> entrySet;
	transient int size;// 存放元素的個數
	transient int modCount;// 被修改的次數fast-fail機制
	int threshold;// 臨界值 當實際大小(容量*填充比)超過臨界值時,會進行擴容
	final float loadFactor;// 填充比(......後面略)

以下是Node<K,V>的原始碼實現

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

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

		public final K getKey() {
			return key;
		}

		public final V getValue() {
			return value;
		}

		public final String toString() {
			return key + "=" + value;
		}

		public final int hashCode() {
			return Objects.hashCode(key) ^ Objects.hashCode(value);
		}

		public final V setValue(V newValue) {
			V oldValue = value;
			value = newValue;
			return oldValue;
		}

		public final boolean equals(Object o) {
			if (o == this)
				return true;
			if (o instanceof Map.Entry) {
				Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
				if (Objects.equals(key, e.getKey())
						&& Objects.equals(value, e.getValue()))
					return true;
			}
			return false;
		}
	}


        構造方法:

	//建構函式1
	//根據給定的初始容量的裝載因子建立一個空的HashMap
    //初始容量小於0或裝載因子小於等於0將報異常 
	public HashMap(int initialCapacity, float loadFactor) {  
	    //指定的初始容量非負  
	    if (initialCapacity < 0)  
	        throw new IllegalArgumentException(Illegal initial capacity:  +  
	                                           initialCapacity);  
	    //如果指定的初始容量大於最大容量,置為最大容量  
	    if (initialCapacity > MAXIMUM_CAPACITY)  
	        initialCapacity = MAXIMUM_CAPACITY;  
	    //填充比為正  
	    if (loadFactor <= 0 || Float.isNaN(loadFactor))  
	        throw new IllegalArgumentException(Illegal load factor:  +  
	                                           loadFactor);  
	    this.loadFactor = loadFactor;  
	    this.threshold = tableSizeFor(initialCapacity);//新的擴容臨界值  
	}  
	   
	//建構函式2
	//根據指定容量建立一個空的HashMap
	public HashMap(int initialCapacity) {  
	    this(initialCapacity, DEFAULT_LOAD_FACTOR);  
	}  
	   
	//建構函式3  
	//使用預設的容量及裝載因子構造一個空的HashMap
	public HashMap() {  
	    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted  
	}  
	   
	//建構函式4用m的元素初始化雜湊對映  
	public HashMap(Map<!--? extends K, ? extends V--> m) {  
	    this.loadFactor = DEFAULT_LOAD_FACTOR;  
	    putMapEntries(m, false);  
	} 

到此處,肯定有很多名次不解,比如裝載因子等,下面就一一解釋:

    負載因子:為了確定合適調整大小,而不是對每個儲存桶中的連結串列的深度做計數,基於雜湊的Map使用一個額外的引數並粗略計算儲存桶的密度。Map在調整大小之前,使用名為“裝載因子”的引數指示Map將承擔的負載量,即負載程度。裝載因子、項數(Map大小)與容量之間的關係是:若裝載因子*容量>map大小,則調整map大小。例如如果預設負載因子為0.75,容量為11,則11*0.75=8.25,則值向下取整為8個元素,因此當第8個項新增到此map,則Map會將自身的大小調整為一個更大的值。HashMap本來就是以空間換時間,所以裝載因子沒必要太大,但是太小又會導致空間的浪費,若關注的是記憶體,則裝載因子可以稍大,若追求的是效能,則應該稍微小一點。

    獲取資料:

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

	/**
	 * Implements Map.get and related methods
	 * 
	 * @param hash
	 *            hash for key
	 * @param key
	 *            the key
	 * @return the node, or null if none
	 */
	final Node<K, V> getNode(int hash, Object key) {
		Node<K, V>[] tab;// Entry物件陣列
		Node<K, V> first, e; // 在tab陣列中經過雜湊的第一個位置
		int n;
		K k;
		/* 找到插入的第一個Node,方法是hash值和n-1相與,tab[(n - 1) & hash] */
		// 也就是說在一條鏈上的hash值相同的
		if ((tab = table) != null && (n = tab.length) > 0
				&& (first = tab[(n - 1) & hash]) != null) {
			/* 檢查第一個Node是不是要找的Node */
			if (first.hash == hash && // always check first node
					((k = first.key) == key || (key != null && key.equals(k))))// 判斷條件是hash值要相同,key值要相同
				return first;
			/* 檢查first後面的node */
			if ((e = first.next) != null) {
				if (first instanceof TreeNode)
					return ((TreeNode<K, V>) first).getTreeNode(hash, key);
				/* 遍歷後面的連結串列,找到key值和hash值都相同的Node */
				do {
					if (e.hash == hash
							&& ((k = e.key) == key || (key != null && key
									.equals(k))))
						return e;
				} while ((e = e.next) != null);
			}
		}
		return null;
	}

使用get(key)方法時獲取key的hash值,計算hash&(n-1)得到連結串列陣列中的位置first=tab[hash&(n-1)],先判斷first的值是否與引數key相等,不等就遍歷後面的連結串列,利用equals()來找到相同的key值返回物件的value值。

    儲存資料:

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

	/**
	 * Implements Map.put and related methods
	 * 
	 * @param hash
	 *            hash for key
	 * @param key
	 *            the key
	 * @param value
	 *            the value to put
	 * @param onlyIfAbsent
	 *            if true, don't change existing value
	 * @param evict
	 *            if false, the table is in creation mode.
	 * @return previous value, or null if none
	 */
	final V putVal(int hash, K key, V value, boolean onlyIfAbsent,  
                   boolean evict) {  
        Node<K,V>[] tab;Node<K,V> p;int n, i;  
        if ((tab = table) == null || (n = tab.length) == 0)  
            n = (tab = resize()).length;  
        /*如果table的在(n-1)&hash的值是空,就新建一個節點插入在該位置*/  
        if ((p = tab[i = (n - 1) & hash]) == null)  
            tab[i] = newNode(hash, key, value, null);  
        /*表示有衝突,開始處理衝突*/  
        else {  
            Node<K,V> e;K k;  
            /*檢查第一個Node,p是不是要找的值*/  
            if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))  
                e = p;  
            else if (p instanceof TreeNode)  
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);  
            else {  
                for (int binCount = 0; ; ++binCount) {  
                	/*指標為空就掛在後面*/  
                    if ((e = p.next) == null) {  
                        p.next = newNode(hash, key, value, null);  
                        //如果衝突的節點數已經達到8個,看是否需要改變衝突節點的儲存結構,               
            		//treeifyBin首先判斷當前hashMap的長度,如果不足64,只進行  
                        //resize,擴容table,如果達到64,那麼將衝突的儲存結構為紅黑樹  
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st  
                            treeifyBin(tab, hash);  
                        break;  
                    }  
                    /*如果有相同的key值就結束遍歷*/  
                    if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))  
                        break;  
                    p = e;  
                }  
            }  
            /*就是連結串列上有相同的key值*/  
            if (e != null) { // existing mapping for key,就是key的Value存在  
                V oldValue = e.value;  
                if (!onlyIfAbsent || oldValue == null)  
                    e.value = value;  
                afterNodeAccess(e);  
                return oldValue;//返回存在的Value值  
            }  
        }  
        ++modCount;  
        /*如果當前大小大於門限,門限原本是初始容量*0.75*/  
        if (++size > threshold)  
            resize();//擴容兩倍  
        afterNodeInsertion(evict);  
        return null;  
    }

put(key,value)的儲存過程:1.判斷鍵值對陣列tab[]是否為空或為null,否則以預設大小resize()。2.根據key值來計算hash值,得到插入陣列的位置,若tab[i]==null,則直接建立節點新增,若不為空,則起衝突。3.判斷當前陣列中處理衝突的方式為連結串列還是紅黑樹,分別處理。

現在來講講涉及到的HashMap擴容機制:構造hash表時,如果不指明初始大小,預設大小為16,如果Node[]陣列達到(裝載因子*Node.length),則重新調整HashMap大小為原來大小的兩倍。

	  /**
     * Initializes or doubles table size.  If null, allocates in
     * accord with initial capacity target held in field threshold.
     * Otherwise, because we are using power-of-two expansion, the
     * elements from each bin must either stay at same index, or move
     * with a power of two offset in the new table.
     *
     * @return the table
     */
    final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        //如果舊錶的長度不是空
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            //把新表的長度設定為舊錶長度的兩倍,newCap=2*oldCap
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
            	//把新表的門限設定為舊錶門限的兩倍,newThr=oldThr*2
                newThr = oldThr << 1; // double threshold
        }
        //如果舊錶的長度的是0,就是說第一次初始化表
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;//新表長度乘以載入因子 
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
        //下面開始構造新表,初始化表中的資料
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;//把新表賦值給table 
        if (oldTab != null) {//原表不是空要把原表中資料移動到新表中 
        	//遍歷原來的舊錶
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)//說明這個node沒有連結串列直接放在新表的e.hash & (newCap - 1)位置  
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    //如果e後邊有連結串列,到這裡表示e後面帶著個單鏈表,需要遍歷單鏈表,將每個結點重
                    else {  // preserve order保證順序
                    	//新計算在新表的位置,並進行搬運 
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;//記錄下一個結點 
                            //新表是舊錶的兩倍容量,例項上就把單鏈表拆分為兩隊,  
                            //e.hash&oldCap為偶數一隊,e.hash&oldCap為奇數一對
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        //lo隊不為null,放在新表原位置  
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        //hi隊不為null,放在新表j+oldCap位置  
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }
	

JDK1.8中處理衝突增加了紅黑樹,當衝突較少時採用連結串列來解決衝突,當較大時(>8個時)採用紅黑樹(從查詢時間O(n)優化到了O(logn))儲存。


而上述的get()以及put()方法都應用到了hashCode()跟equals(),這兩個方法是HashMap中應用廣泛的方法,並且我們在用物件進行作為鍵的時候,必須要重寫hashCode(),equals()。先來看看原始碼:

	static final int hash(Object key) {
		int h;
		return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
	}
        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }
        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }

從這裡看出,若沒有重寫equals()跟hashCode(),Java類庫中預設的是使用Objects的equals()跟hashCode()方法。這樣在實際的使用中很容易出錯。例如:

寫一個user

package crazyHzm;

public class User {
	int id;

	User(int id) {
		this.id = id;
	}

	public String toString() {
		return "key="+id;
	}
}

下面寫一個測試類:

package crazyHzm;

import java.util.HashMap;
import java.util.Map;

public class TestMap {
	public static void main(String[] args) {
		Map map = new HashMap();
		map.put(new User(1), 1);
		map.put(new User(2), 1);
		map.put(new User(3), 1);
		
		System.out.println(map.keySet());
		System.out.println(map.get(new User(1)));
	}
}

檢視結果:


很明顯,第二個輸出是null,再回過去看hashCode()跟equals()的原始碼,很容易就明白:我們傳入的是User預設繼承Object所以equals跟hashCode都是Object的方法,但是我們這裡equals比較的是兩個物件的記憶體地址並非value,而這個new User(1)的地址一定跟之前存放在map裡面的new User(1)不一樣,所以第二個new User(1)就無法在用Map取得了。

嘗試在User類裡面重寫這兩個方法:

package crazyHzm;

public class User {
	int id;

	User(int id) {
		this.id = id;
	}

	public String toString() {
		return "key="+id;
	}
	
	public int hashCode() {
		return id;
	}

	public boolean equals(Object o) {
		return o instanceof User && (id == ((User) o).id);
	}
}

檢視結果:


這個時候就能夠在Map裡面找到了,所以equals()相等的物件,hashCode()的值一定相等,但是equals()不相等的兩個物件,他們的hashCode()並不一定相等,這是雜湊表衝突造成的,反過來hashCode()不相等的,equals()一定不相等,但是hashCode()相等的,equals()不一定相等。

TreeMap

    TreeMap基於紅黑樹,檢視“鍵”或“鍵值對”時,它們會被排序(次序由Comparable或者Comparator決定)。TreeMap特點在於,所得到的結果是經過排序的。TreeMap是 唯一帶有subMap()方法的Map,它可以返回一個子樹。

    底層原理:

樹節點Entry實現了Map.Entry,採用內部類實現:

	static final class Entry<K,V> implements Map.Entry<K,V> {
        K key;
        V value;
        Entry<K,V> left;
        Entry<K,V> right;
        Entry<K,V> parent;
        boolean color = BLACK;

        /**
         * Make a new cell with given key, value, and parent, and with
         * {@code null} child links, and BLACK color.
         */
        Entry(K key, V value, Entry<K,V> parent) {
            this.key = key;
            this.value = value;
            this.parent = parent;
        }

        定義:

	public class TreeMap<K,V>
		extends AbstractMap<K,V>
		implements NavigableMap<K,V>, Cloneable, java.io.Serializable

        屬性:

    // 用於接收外部比較器,插入時用於比對元素的大小
    private final Comparator<? super K> comparator;
    // 紅黑樹的根節點
    private transient Entry<K,V> root;

    /**
     * The number of entries in the tree
     */
    // 樹中元素個數
    private transient int size = 0;

    /**
     * The number of structural modifications to the tree.
     */
    private transient int modCount = 0;

    構造方法:

	//預設構造方法會建立一顆空樹。
	//預設使用key的自然順序來構建有序樹
	//所謂自然順序,意思是key的型別是什麼,就採用該型別的compareTo方法來比較大小,決定順序。
	//例如key為String型別,就會用String類的compareTo方法比對大小,如果是Integer型別,就用Integer的compareTo方法比對。
	//Java自帶的基本資料型別及其裝箱型別都實現了Comparable介面的compareTo方法。
	//key的型別,必須實現Comparable介面,如果不實現,就沒辦法完成元素大小的比較來實現有序性的。
	//比如自定義了一個類User來作為key,忘記實現了Comparable介面,就沒有一個規則比較User的大小,無法實現TreeMap最重要的有序性。
	public TreeMap() {
		comparator = null;
	}

	//// 提供指定的比較器
	public TreeMap(Comparator<? super K> comparator) {
		this.comparator = comparator;
	}

	// 構造方法三,採用自然序維持TreeMap中節點的順序,同時將傳入的Map中的內容新增到TreeMap中
	public TreeMap(Map<? extends K, ? extends V> m) {
		comparator = null;
		putAll(m);
	}

	//構造方法四,接收SortedMap引數,根據SortedMap的比較器維持TreeMap中的節點順序,
	//同時通過buildFromSorted(int size, Iterator it, java.io.ObjectInputStream str, V defaultVal)方法將SortedMap中的內容新增到TreeMap中
	public TreeMap(SortedMap<K, ? extends V> m) {
		comparator = m.comparator();
		try {
			buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
		} catch (java.io.IOException cannotHappen) {
		} catch (ClassNotFoundException cannotHappen) {
		}
	}

    儲存資料:

	public V put(K key, V value) {
		Entry<K, V> t = root;
		if (t == null) {
			compare(key, key); // type (and possibly null) check

			root = new Entry<>(key, value, null);
			size = 1;
			modCount++;
			return null;
		}
		int cmp;
		Entry<K, V> parent;
		// split comparator and comparable paths
		Comparator<? super K> cpr = comparator;
		if (cpr != null) {// 外部比較器
			do {
				parent = t;
				cmp = cpr.compare(key, t.key);
				if (cmp < 0)
					t = t.left;
				else if (cmp > 0)
					t = t.right;
				else
					return t.setValue(value);
			} while (t != null);
		} else {// 預設key的比較器
			if (key == null)
				throw new NullPointerException();
			@SuppressWarnings("unchecked")
			Comparable<? super K> k = (Comparable<? super K>) key;
			do {
				parent = t;
				cmp = k.compareTo(t.key);
				if (cmp < 0)
					t = t.left;
				else if (cmp > 0)
					t = t.right;
				else
					return t.setValue(value);
			} while (t != null);
		}
		Entry<K, V> e = new Entry<>(key, value, parent);
		if (cmp < 0)
			parent.left = e;
		else
			parent.right = e;
		 // 插入完成,執行紅黑樹的性質恢復操作
		fixAfterInsertion(e);
		size++;
		modCount++;
		return null;
	}

TreeMap的put方法跟其他Map的put方法一樣,向Map中加入鍵值對,若原先的鍵已經存在,那麼替換它的value,並返回原先的值。不過多的是在put後面加入fixAfterInsertion()方法,這個方法負責在插入節點以後調整樹結構和著色,以滿足紅黑樹的要求。以下就是這個方法的原始碼:

	private void fixAfterInsertion(Entry<K, V> x) {
		// 插入節點預設為紅色
		x.color = RED;
		// 迴圈條件是x不為空、不是根節點、父節點的顏色是紅色(如果父節點不是紅色,則沒有連續的紅色節點,不再調整)
		while (x != null && x != root && x.parent.color == RED) {
			// x節點的父節點p(記作p)是其父節點pp(p的父節點,記作pp)的左孩子(pp的左孩子)
			if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
				// 獲取pp節點的右孩子r
				Entry<K, V> y = rightOf(parentOf(parentOf(x)));
				// pp右孩子的顏色是紅色(colorOf(Entry e)方法在e為空時返回BLACK),不需要進行旋轉操作(因為紅黑樹不是嚴格的平衡二叉樹)
				if (colorOf(y) == RED) {
					 // 將父節點設定為黑色
					setColor(parentOf(x), BLACK);
					// y節點,即r設定成黑色
					setColor(y, BLACK);
					// pp節點設定成紅色
					setColor(parentOf(parentOf(x)), RED);
					// x“移動”到pp節點
					x = parentOf(parentOf(x));
				} else {
					//父親的兄弟是黑色的,這時需要進行旋轉操作,根據是“內部”還是“外部”的情況決定是雙旋轉還是單旋轉
					// x節點是父節點的右孩子(因為上面已近確認p是pp的左孩子,所以這是一個“內部,左-右”插入的情況,需要進行雙旋轉處理)
					if (x == rightOf(parentOf(x))) {
						 // x移動到它的父節點
						x = parentOf(x);
						// 左旋操作
						rotateLeft(x);
					}
					// x的父節點設定成黑色
					setColor(parentOf(x), BLACK);
					// x的父節點的父節點設定成紅色
					setColor(parentOf(parentOf(x)), RED);
					// 右旋操作
					rotateRight(parentOf(parentOf(x)));
				}
			} else {
				// 獲取x的父節點(記作p)的父節點(記作pp)的左孩子
				Entry<K, V> y = leftOf(parentOf(parentOf(x)));
				// y節點是紅色的
				if (colorOf(y) == RED) {
					// x的父節點,即p節點,設定成黑色
					setColor(parentOf(x), BLACK);
					// y節點設定成黑色
					setColor(y, BLACK);
					// pp節點設定成紅色
					setColor(parentOf(parentOf(x)), RED);
					// x移動到pp節點
					x = parentOf(parentOf(x));
				} else {
					// x是父節點的左孩子(因為上面已近確認p是pp的右孩子,所以這是一個“內部,右-左”插入的情況,需要進行雙旋轉處理),
					if (x == leftOf(parentOf(x))) {
						// x移動到父節點
						x = parentOf(x);
						// 右旋操作
						rotateRight(x);
					}
					// x的父節點設定成黑色
					setColor(parentOf(x), BLACK);
					// x的父節點的父節點設定成紅色
					setColor(parentOf(parentOf(x)), RED);
					// 左旋操作
					rotateLeft(parentOf(parentOf(x)));
				}
			}
		}
		// 根節點為黑色
		root.color = BLACK;
	}

而涉及到的左右旋轉的原始碼如下:

	private void rotateLeft(Entry<K, V> p) {
		if (p != null) {
			Entry<K, V> r = p.right;
			p.right = r.left;
			if (r.left != null)
				r.left.parent = p;
			r.parent = p.parent;
			if (p.parent == null)
				root = r;
			else if (p.parent.left == p)
				p.parent.left = r;
			else
				p.parent.right = r;
			r.left = p;
			p.parent = r;
		}
	}

	/** From CLR */
	private void rotateRight(Entry<K, V> p) {
		if (p != null) {
			Entry<K, V> l = p.left;
			p.left = l.right;
			if (l.right != null)
				l.right.parent = p;
			l.parent = p.parent;
			if (p.parent == null)
				root = l;
			else if (p.parent.right == p)
				p.parent.right = l;
			else
				p.parent.left = l;
			l.right = p;
			p.parent = l;
		}
	}

左旋:


右旋:


        獲取資料:

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

	final Entry<K, V> getEntry(Object key) {
		// 如果外部比較器,就採用外部比較器比對查詢元素
		if (comparator != null)
			return getEntryUsingComparator(key);
		if (key == null)
			throw new NullPointerException();
		// 採用key的預設比較器
		@SuppressWarnings("unchecked")
		Comparable<? super K> k = (Comparable<? super K>) key;
		Entry<K, V> p = root;
		while (p != null) {
			int cmp = k.compareTo(p.key);
			if (cmp < 0)
				p = p.left;
			else if (cmp > 0)
				p = p.right;
			else
				return p;
		}
		return null;
	}

    刪除資料:

	public V remove(Object key) {
		Entry<K, V> p = getEntry(key);
		if (p == null)
			return null;

		V oldValue = p.value;
		deleteEntry(p);
		return oldValue;
	}

	private void deleteEntry(Entry<K, V> p) {
		modCount++;
		size--;

		// If strictly internal, copy successor's element to p and then make p
		// point to successor.
		// 情況一:待刪除的節點有兩個孩子
		if (p.left != null && p.right != null) {
			Entry<K, V> s = successor(p);
			p.key = s.key;
			p.value = s.value;
			p = s;
		} // p has 2 children

		// Start fixup at replacement node, if it exists.
		Entry<K, V> replacement = (p.left != null ? p.left : p.right);

		// 情況二:待刪除節點只有一個孩子
		if (replacement != null) {
			// Link replacement to parent
			replacement.parent = p.parent;
			if (p.parent == null)
				root = replacement;
			else if (p == p.parent.left)
				p.parent.left = replacement;
			else
				p.parent.right = replacement;

			// Null out links so they are OK to use by fixAfterDeletion.
			p.left = p.right = p.parent = null;

			// Fix replacement
			if (p.color == BLACK)
				fixAfterDeletion(replacement);
		} else if (p.parent == null) { // 情況三:根節點
			root = null;
		} else { // 情況四:無任何孩子節點
			if (p.color == BLACK)
				fixAfterDeletion(p);

			if (p.parent != null) {
				if (p == p.parent.left)
					p.parent.left = null;
				else if (p == p.parent.right)
					p.parent.right = null;
				p.parent = null;
			}
		}
	}

	

deleteEntry(Entry e)方法中主要有兩個方法呼叫需要分析:successor(Entry<K,V> t)和fixAfterDeletion(Entry<K,V> x)。successor(Entry<K,V> t)返回指定節點的繼承者。分三種情況處理,第一。t節點是個空節點:返回null;第二,t有右孩子:找到t的右孩子中的最左子孫節點,如果右孩子沒有左孩子則返回右節點,否則返回找到的最左子孫節點;第三,t沒有右孩子:沿著向上(向跟節點方向)找到第一個自身是一個左孩子的節點或根節點,返回找到的節點。與新增節點之後的修復類似的是,TreeMap 刪除節點之後也需要進行類似的修復操作,通過這種修復來保證該排序二叉樹依然滿足紅黑樹特徵。大家可以參考插入節點之後的修復來分析刪除之後的修復。TreeMap 在刪除之後的修復操作由 fixAfterDeletion(Entry<K,V> x) 方法提供,以下就是這個兩個方法的原始碼:

	static <K, V> TreeMap.Entry<K, V> successor(Entry<K, V> t) {
		// 如果t本身是一個空節點,返回null
		if (t == null)
			return null;
		// 如果t有右孩子,找到右孩子的最左子孫節點
		else if (t.right != null) {
			Entry<K, V> p = t.right;
			while (p.left != null)
				// 獲取p節點最左的子孫節點,如果存在的話
				p = p.left;
			// 返回找到的繼承節點
			return p;
		} else {
			//t不為null且沒有右孩子
			Entry<K, V> p = t.parent;
			Entry<K, V> ch = t;
			// 沿著右孩子向上查詢繼承者,直到根節點或找到節點ch是其父節點的左孩子的節點
			while (p != null && ch == p.right) {
				ch = p;
				p = p.parent;
			}
			return p;
		}
	}

	private void fixAfterDeletion(Entry<K, V> x) {
		// 迴圈處理,條件為x不是root節點且是黑色的(因為紅色不會對紅黑樹的性質造成破壞,所以不需要調整)
		while (x != root && colorOf(x) == BLACK) {
			// x是一個左孩子
			if (x == leftOf(parentOf(x))) {
				// 獲取x的兄弟節點sib
				Entry<K, V> sib = rightOf(parentOf(x));
				if (colorOf(sib) == RED) {
					setColor(sib, BLACK);
					// 將父節點設定成紅色
					setColor(parentOf(x), RED);
					// 左旋父節點
					rotateLeft(parentOf(x));
					// sib移動到旋轉後x的父節點p的右孩子(參見左旋示意圖,獲取的節點是旋轉前p的右孩子r的左孩子rl)
					sib = rightOf(parentOf(x));
				}
				// sib的兩個孩子的顏色都是黑色(null返回黑色)
				if (colorOf(leftOf(sib)) == BLACK
						&& colorOf(rightOf(sib)) == BLACK) {
					setColor(sib, RED);
					// x移動到x的父節點
					x = parentOf(x);
				} else {
					// sib的左右孩子都是黑色的不成立
					// sib的右孩子是黑色的
					if (colorOf(rightOf(sib)) == BLACK) {
						setColor(leftOf(sib), BLACK);
						setColor(sib, RED);
						rotateRight(sib);
						sib = rightOf(parentOf(x));
					}
					setColor(sib, colorOf(parentOf(x)));
					setColor(parentOf(x), BLACK);
					setColor(rightOf(sib), BLACK);
					rotateLeft(parentOf(x));
					x = root;
				}
			} else { // symmetric
				Entry<K, V> sib = leftOf(parentOf(x));

				if (colorOf(sib) == RED) {
					setColor(sib, BLACK);
					setColor(parentOf(x), RED);
					rotateRight(parentOf(x));
					sib = leftOf(parentOf(x));
				}

				if (colorOf(rightOf(sib)) == BLACK
						&& colorOf(leftOf(sib)) == BLACK) {
					setColor(sib, RED);
					x = parentOf(x);
				} else {
					if (colorOf(leftOf(sib)) == BLACK) {
						setColor(rightOf(sib), BLACK);
						setColor(sib, RED);
						rotateLeft(sib);
						sib = leftOf(parentOf(x));
					}
					setColor(sib, colorOf(parentOf(x)));
					setColor(parentOf(x), BLACK);
					setColor(leftOf(sib), BLACK);
					rotateRight(parentOf(x));
					x = root;
				}
			}
		}

		setColor(x, BLACK);
	}
	

其他的方法比較簡單,不在這裡一一列舉如需要了解,請查詢JDK1.8原始碼。

LinkedHashMap

類似於HashMap,但是迭代遍歷它時,它得“鍵值對”的順序是插入次序,或者是最近使用(LRU)次序。只是比HashMap慢一點,而在迭代訪問時反而更快,因為它使用連結串列維護內部次序。

LinkedHashMap的key跟vlaue都允許為空,並且key重複會覆蓋,value重複沒有影響,執行緒的不安全的。LinkedHashMap實現思想是多型。

    底層原理:

    定義:

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

    屬性:

	// 用於指向雙向連結串列的頭部
	transient LinkedHashMap.Entry<K, V> head;

	/**
	 * The tail (youngest) of the doubly linked list.
	 */
	//用於指向雙向連結串列的尾部
	transient LinkedHashMap.Entry<K, V> tail;

	/**
	 * The iteration ordering method for this linked hash map: <tt>true</tt> for
	 * access-order, <tt>false</tt> for insertion-order.
	 *
	 * @serial
	 */
	 // 用來指定LinkedHashMap的迭代順序,
	 //true則表示按照基於訪問的順序來排列,意思就是最近使用的entry,放在連結串列的最末尾
	 //false則表示按照插入順序來
	final boolean accessOrder;

    構造方法:

	/**
	 * Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance
	 * with the specified initial capacity and load factor.
	 *
	 * @param initialCapacity
	 *            the initial capacity
	 * @param loadFactor
	 *            the load factor
	 * @throws IllegalArgumentException
	 *             if the initial capacity is negative or the load factor is
	 *             nonpositive
	 */
	// 構造方法1,構造一個指定初始容量和負載因子的、按照插入順序的LinkedHashMap
	public LinkedHashMap(int initialCapacity, float loadFactor) {
		super(initialCapacity, loadFactor);
		accessOrder = false;
	}

	/**
	 * Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance
	 * with the specified initial capacity and a default load factor (0.75).
	 *
	 * @param initialCapacity
	 *            the initial capacity
	 * @throws IllegalArgumentException
	 *             if the initial capacity is negative
	 */
	// 構造方法2,構造一個指定初始容量的LinkedHashMap,取得鍵值對的順序是插入順序
	public LinkedHashMap(int initialCapacity) {
		super(initialCapacity);
		accessOrder = false;
	}

	/**
	 * Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance
	 * with the default initial capacity (16) and load factor (0.75).
	 */
	// 構造方法3,用預設的初始化容量和負載因子建立一個LinkedHashMap,取得鍵值對的順序是插入順序
	public LinkedHashMap() {
		super();
		accessOrder = false;
	}

	/**
	 * Constructs an insertion-ordered <tt>LinkedHashMap</tt> instance with the
	 * same mappings as the specified map. The <tt>LinkedHashMap</tt> instance
	 * is created with a default load factor (0.75) and an initial capacity
	 * sufficient to hold the mappings in the specified map.
	 *
	 * @param m
	 *            the map whose mappings are to be placed in this map
	 * @throws NullPointerException
	 *             if the specified map is null
	 */
	// 構造方法4,通過傳入的map建立一個LinkedHashMap,容量為預設容量(16)和(map.zise()/DEFAULT_LOAD_FACTORY)+1的較大者,裝載因子為預設值
	public LinkedHashMap(Map<? extends K, ? extends V> m) {
		super();
		accessOrder = false;
		putMapEntries(m, false);
	}

	/**
	 * Constructs an empty <tt>LinkedHashMap</tt> instance with the specified
	 * initial capacity, load factor and ordering mode.
	 *
	 * @param initialCapacity
	 *            the initial capacity
	 * @param loadFactor
	 *            the load factor
	 * @param accessOrder
	 *            the ordering mode - <tt>true</tt> for access-order,
	 *            <tt>false</tt> for insertion-order
	 * @throws IllegalArgumentException
	 *             if the initial capacity is negative or the load factor is
	 *             nonpositive
	 */
	// 構造方法5,根據指定容量、裝載因子和鍵值對保持順序建立一個LinkedHashMap
	public LinkedHashMap(int initialCapacity, float loadFactor,
			boolean accessOrder) {
		super(initialCapacity, loadFactor);
		this.accessOrder = accessOrder;
	}

 從構造方法中可以看出,預設都採用插入順序來維持取出鍵值對的次序。所有構造方法都是通過呼叫父類的構造方法來建立物件的。

    重要方法:

	// get(Object key)方法通過HashMap的getEntry(Object key)方法獲取節點,並返回該節點的value值
    public V get(Object key) {
        Node<K,V> e;
        if ((e = getNode(hash(key), key)) == null)
            return null;
        if (accessOrder)
            afterNodeAccess(e);
        return e.value;
    }
    //clear()方法先呼叫父類的方法clear()方法,之後將連結串列的header節點的before和after引用都指向header自身,
    //即header節點就是一個雙向迴圈連結串列。這樣就無法訪問到原連結串列中剩餘的其他節點,他們都將被GC回收。
    public void clear() {
        super.clear();
        head = tail = null;
    }
    // 重寫父類的containsValue(Object value)方法,直接通過header遍歷連結串列判斷是否有值和value相等,而不用查詢table陣列。
    public boolean containsValue(Object value) {
        for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {
            V v = e.value;
            if (v == value || (value != null && value.equals(v)))
                return true;
        }
        return false;
    }
而put方法直接使用了HashMap的put方法,所以不再列舉。