HashMap,TreeMap以及LinkedHashMap的區別
HashMap:HashMap數據是無序的,根據鍵的hashCode進行數據的存取,對數據的訪問速度非常快,在map中插入刪除
和定位元素,hashMap無疑是最好的選擇,
TreeMap:裏面的數據是有序的,底層是一個紅黑樹,如果想按照自定義順序或者自然順序存儲數據,TreeMap是一個最好的選擇
LinkedHashMap:他是hashMap的一個子類,底層維護了一個雙向鏈表,他可以實現輸入的順序和輸出的順序相同
下面來講講LinkedHashMap是如何實現有序的:
LinkedHashMap具有可預知的叠代順序,根據鏈表中元素的順序可以分為:按插入順序的鏈表,和按訪問順序(調用get方法)的鏈表。
默認是按插入順序排序,如果指定按訪問順序排序,那麽調用get方法後,會將這次訪問的元素移至鏈表尾部,不斷訪問可以形成按訪問順序排序的鏈表。 可以重寫removeEldestEntry方法返回true值指定插入元素時移除最老的元素。
如何實現叠代有序?
重新定義了數組中保存的元素Entry(繼承於HashMap.Entry),該Entry除了保存當前對象的引用外,還保存了其上一個元素before和下一個元素after的引用,從而在哈希表的基礎上又構成了雙向鏈接列表。仍然保留next屬性,所以既可像HashMap一樣快速查找,用next獲取該鏈表下一個Entry,也可以通過雙向鏈接,通過after完成所有數據的有序叠代。
accessOrder為true時,按訪問順序排序,false時,按插入順序排序。默認false,即下文中recordAccess方法沒有改變什麽。 copy
private final boolean accessOrder
存儲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; }
重寫方法:
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的區別