HashMap,TreeMap以及LinkedHashMap的區別


HashMap:HashMap數據是無序的,根據鍵的hashCode進行數據的存取,對數據的訪問速度非常快,在map中插入刪除

和定位元素,hashMap無疑是最好的選擇,

TreeMap:裏面的數據是有序的,底層是一個紅黑樹,如果想按照自定義順序或者自然順序存儲數據,TreeMap是一個最好的選擇

LinkedHashMap:他是hashMap的一個子類,底層維護了一個雙向鏈表,他可以實現輸入的順序和輸出的順序相同

下面來講講LinkedHashMap是如何實現有序的:


LinkedHashMap具有可預知的迭代順序,根據鏈表中元素的順序可以分爲:按插入順序的鏈表,和按訪問順序(調用get方法)的鏈表。  

默認是按插入順序排序,如果指定按訪問順序排序,那麼調用get方法後,會將這次訪問的元素移至鏈表尾部,不斷訪問可以形成按訪問順序排序的鏈表。  可以重寫removeEldestEntry方法返回true值指定插入元素時移除最老的元素。


如何實現迭代有序?


  1. 重新定義了數組中保存的元素Entry(繼承於HashMap.Entry),該Entry除了保存當前對象的引用外,還保存了其上一個元素before和下一個元素after的引用,從而在哈希表的基礎上又構成了雙向鏈接列表。仍然保留next屬性,所以既可像HashMap一樣快速查找,用next獲取該鏈表下一個Entry,也可以通過雙向鏈接,通過after完成所有數據的有序迭代。

  2. accessOrder爲true時,按訪問順序排序,false時,按插入順序排序。默認false,即下文中recordAccess方法沒有改變什麼。 copy

  1. private final boolean accessOrder
  2. 存儲put

    LinkedHashMap並未重寫父類HashMap的put方法,而是重寫了父類HashMap的put方法調用的子方法void recordAccess(HashMap m),void addEntry(int hash, K key, V value, int bucketIndex) 和void createEntry(int hash, K key, V value, int bucketIndex),提供了自己特有的雙向鏈接列表的實現。

  • put時,key已存在,替換value(同HashMap),並調用recordAccess方法,方法作用爲根據accessOrder的值保持鏈表順序不變或者將將訪問的當前節點移到鏈表尾部(頭結點的前一個節點)。

  • key不存在,添加新的Entry,仍然是Table[i]= newEntry,舊鏈表首個爲newEntry.next(同HashMap),將newEntry加到雙向鏈表末尾(即header前,這樣就保留了插入順序)。copy 

  • HashMap.put:  
      
    public V put(K key, V value) {    
            if (key == null)    
                return putForNullKey(value);    
            int hash = hash(key.hashCode());    
            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 || key.equals(k))) {    
                    V oldValue = e.value;    
                    e.value = value;    
                    e.recordAccess(this);    
                    return oldValue;    
                }    
            }    
        
            modCount++;    
            addEntry(hash, key, value, i);    
            return null;    
        }

  1.  重寫方法:  

      

  2. void recordAccess(HashMap<K,V> m) {    
                LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;    
                if (lm.accessOrder) {    
                    lm.modCount++;    
                    remove();    
                    addBefore(lm.header);    
                }    
            }    
    void addEntry(int hash, K key, V value, int bucketIndex) {    
        // 調用create方法,將新元素以雙向鏈表的的形式加入到映射中。    
        createEntry(hash, key, value, bucketIndex);    
        
        // 刪除最近最少使用元素的策略定義    
        Entry<K,V> eldest = header.after;    
        if (removeEldestEntry(eldest)) {    
            removeEntryForKey(eldest.key);    
        } else {    
            if (size >= threshold)    
                resize(2 * table.length);    
        }    
    }    
    void createEntry(int hash, K key, V value, int bucketIndex) {    
        HashMap.Entry<K,V> old = table[bucketIndex];    
        Entry<K,V> e = new Entry<K,V>(hash, key, value, old);    
        table[bucketIndex] = e;    
        // 調用元素的addBrefore方法,將元素加入到哈希、雙向鏈接列表。    
        e.addBefore(header);    
        size++;    
    }    
    private void addBefore(Entry<K,V> existingEntry) {    
        after  = existingEntry;    
        before = existingEntry.before;    
        before.after = this;    
        after.before = this;    
    }


4.讀取

同樣調用recordAccess方法,是否將訪問的當前節點移到鏈表尾部,與HashMap的區別是:當LinkedHashMap按訪問順序排序的時候,會將訪問的當前節點移到鏈表尾部(頭結點的前一個節點)。

public V get(Object key) {    
    // 調用父類HashMap的getEntry()方法,取得要查找的元素。    
    Entry<K,V> e = (Entry<K,V>)getEntry(key);    
    if (e == null)    
        return null;    
    // 記錄訪問順序。    
    e.recordAccess(this);    
    return e.value;    
}


view plai

5.迭代view plain copy

//返回鏈表下個節點的引用  
Entry<K,V> nextEntry() {  
    //快速失敗機制  
    if (modCount != expectedModCount)  
        throw new ConcurrentModificationException();  
    //鏈表爲空情況  
    if (nextEntry == header)  
        throw new NoSuchElementException();  
      
    //給lastReturned賦值,最近一個從迭代器返回的節點對象  
    Entry<K,V> e = lastReturned = nextEntry;  
    nextEntry = e.after;  
    return e;  
}

 

下面來講講TreeMap是如何實現有序的:

TreeMap底層是一個紅黑樹,那麼他的中序遍歷就是有序的,因此treeMap是可以實現有序的,那麼他又是如何實現自定義排序的呢?

1、讓元素自身具備比較功能

		實現Comparable接口,重寫comparaTo()方法。
		@Override
		public int compareTo(Object o) {
			
			Person p = (Person) o;
			int temp = this.age - p.age;
			return temp == 0?this.name.compareTo(p.name):temp;

	//		Person p = (Person) o;
	//		if(this.age > p.age)
	//			return 1;
	//		if(this.age < p.age)
	//			return -1;
	//		else {
	//			return this.name.compareTo(p.name);
	//		}
		}

2、如果不要按照對象中具備的自然順序進行排序。如果對象中不具備自然順序。也就是對象不是自己定義的,怎麼辦?

可以使用TreeSet集合的第二種排序方式:

讓集合自身具備比較功能,使用比較器,定義一個類實現Comparator接口,覆蓋compare方法,將該類對象作爲參數

傳遞給TreeSet集合的構造函數


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