1. 程式人生 > >HashMap,TreeMap以及LinkedHashMap的區別

HashMap,TreeMap以及LinkedHashMap的區別

header order equal 是否 通過 保持 extent row head


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集合的構造函數


HashMap,TreeMap以及LinkedHashMap的區別