文章目錄
HashMap、ConcurrentHashMap、LinkedHashMap、TreeMap與Map的關係:
他們都實現了Map接口,都以key-value形式保存元素,同時各自又有不同的特點,下面會一一分析。
HashMap
HashMap在項目中使用很頻繁,底層實現是數組+鏈表(jdk1.8版本加入了紅黑樹)。我們知道數組的優點是查找快,增加或刪除元素慢;鏈表的優點是增加或刪除快,查找慢。HashMap結合了兩者的特點,增刪改查都有不錯的性能,下面就來了解一下HashMap的內部機制。
HashMap的存儲結構
從圖中可以看出,HashMap是由數組+鏈表(jdk1.8在鏈表長度大於8時轉換成紅黑樹)組成。HashMap存儲的是鍵值對,Node<K,V>即是每個節點中存儲鍵值對的映射(Node<K,V>的內部結構後面分析)。
- 當存儲元素時,Key值經過hash方法算出key對應table數組的索引位置,如果該位置沒有元素,則直接將該key、value生成Node並放入該索引位置;如果該位置有元素,利用頭結點方式放入該索引所在的鏈表中,當鏈表的長度超過8時,鏈表轉換成紅黑樹,這樣即使元素多也能保證高效性。
- 當添加元素時發現HashMap的元素個數超過閾值後,會將數組大小翻倍並對所有元素重新進行hash計算並放入新的數組中。
- 取元素時,依然是先通過key找到對應table數組中的索引,並最終找到該索引中對應的鏈表(或紅黑樹)中對應的值value。
以下源碼分析是基於jdk1.8版本的,跟1.7版本相比,1.8中有幾處優化,後面會列出。
初始化
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;//初始化大小,默認是16
static final float DEFAULT_LOAD_FACTOR = 0.75f;//負載因子默認值
static final int MAXIMUM_CAPACITY = 1 << 30;//最大容量
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;//key-value鍵值對的個數
transient int modCount;//HashMap內部結構發生變化次數
int threshold;//擴容閾值 threshold
final float loadFactor;//負載因子
//1
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
//2
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//3
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;
//如果傳入的容量不是2的倍數 通過tableSizeFor方法返回一個最接近initialCapacity的值。
this.threshold = tableSizeFor(initialCapacity);
}
//4
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
Node是HashMap中的一個內部類,是一個保存key-value的映射。Node的內部結構:
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;
}
}
Node中有4個成員變量:
- hash:哈希值,用來確定Node在table數組中的索引位置
- key: Key值
- value: value值
- next: HashMap使用哈希表(一維數組)存儲元素,當出現哈希衝突(即不同元素對應上table數組中的同一索引位置)時,HashMap採用鏈地址法解決衝突,可以簡單認爲table數組中的每個存儲都是一個鏈表結構,鏈表的節點是Node,Node.next指向的是鏈表中的下一個Node節點.
put & get
put元素
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
static final int hash(Object key) {
int h;
//hashCode的高16位也參與運算 增加Node元素在哈希數組分佈的均勻性
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//如果table數組未初始化 調用resize去初始化數組
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//當前key的hashCode與數組大小取模取得在table數組中的索引位置,如果當前位置沒有元素,說明沒有Hash衝突,直接將當前key、value生成Node並放入當前位置中
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
//如果該索引位置有元素(Hash衝突)且key、key的hashCode相等,直接用新值覆蓋舊值並返回舊值
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 {
//當前節點屬於鏈表,將Key、value封裝成Node放入鏈表的尾部(1.7是放入鏈表的頭部)
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1)
//鏈表長度超過閾值,轉化成紅黑樹
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
}
畫一個大概的流程圖:
get元素
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
//獲取key經過hash算法之後的值
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
//table數組不爲空且key在table數組中索引位置處不能爲空
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
//如果table數組索引位置(可能是紅黑樹或鏈表結構)的key及hash值相等,直接返回索引位置對應的value
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;
}
get(key)對應的流程圖:
擴容
擴容指的是當向HashMap中添加元素時,Node元素總數超過了設定的閾值,那麼HashMap就會進行擴容(擴大到2倍),那麼這個設定的閾值跟哪些變量相關呢?上面HashMap源碼中有這幾個變量:
- loadFactor:負載因子(默認是0.75)。哈希數組大小固定的前提下,負載因子越大那麼HashMap容納的Node元素越多,反之容納的越少。但不是說loadFactor越大越好,loadFactor越大,意味着哈希衝突的可能性越大,查找需要的時間也就更多;反之loadFactor越小,哈希衝突可能性減小,但是同時哈希數組的空間利用率也就越小。默認值0.75是對空間和時間的一個平衡選擇,一般不需要改動。
- Node<K,V>[] table:哈希數組,
- threshold:擴容閾值(即HashMap能容納的最大數量的Node值),當HashMap中的元素Node個數超過此閾值時,需要進行擴容(resize)。threshold = table.length * loadFactor
遍歷Map
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("111", "222");
hashMap.put("333", "444");
hashMap.put("555", "666");
//1、通過entrySet遍歷map
for (Map.Entry<String, String> entry : hashMap.entrySet()) {
System.out.println("entrySet方式,key:" + entry.getKey() + ",value:" + entry.getValue());
}
//2、通過keySet遍歷map
for (String key : hashMap.keySet()) {
System.out.println("keySet 方 式,key:" + key + ",value:" + hashMap.get(key));
}
執行結果:
entrySet方式,key:111,value:222
entrySet方式,key:333,value:444
entrySet方式,key:555,value:666
keySet 方 式,key:111,value:222
keySet 方 式,key:333,value:444
keySet 方 式,key:555,value:666
jdk1.8中的優化
相比於jdk1.7的HashMap,jdk1.8中優化了下面幾個方面:
-
hash算法:hash算法的好壞決定了Node元素在哈希表中的分佈均勻情況,分佈的越均勻,那麼生成鏈表的可能性越小,查找的效率越高(數組查找效率>鏈表),在1.7中通過取模運算(hash = h & (table.length -1))來使Node元素均勻分佈;在1.8中優化了hash算法,hashCode是通過hashCode的高16位異或低16位實現的((h = k.hashCode()) ^ (h >>> 16)),保證了高低位都參與到hashCode的計算中,增加了離散性,使得哈希數組中的Node元素分佈的更均勻。
-
紅黑樹的引入:極端情況下,所有的鍵值對應的哈希值都是一樣的,那麼經過鏈地址法後最後在哈希數組的某個位置上形成了一個鏈表,此時不管是空間性能還是時間性能都是糟糕的,HashMap已經退化成一個鏈表。基於此在1.8中對鏈表做了優化,當鏈表中的Node元素個數大於8時,將鏈表優化成紅黑樹,利用紅黑樹可以快速增刪改查的特點提高HashMap的性能。
ConcurrentHashMap
Hashmap中沒有任何同步操作,HashMap在單線程下使用沒有問題,但是當在多線程中使用時,可能會導致數據不一致問題,因此在多線程下改用JUC中的ConcurrentHashMap。
注:除了ConcurrentHashMap,還可以使用Hashtable或Collections.synchronizedMap(hashmap),但是他們都是直接在最上層加鎖,不管是put還是get操作都需要進行同步加鎖,效率並不高。
jdk1.7版本
ConcurrentHashMap在1.7版本中使用了分段鎖形式來存取元素,如下圖所示:
ConcurrentHashMap在1.7版本的大致流程:
- 當put元素時,首先通過key的hashcode獲取所在的Segment, 其中Segment的父類是ReentrantLock,當對其中一個Segment進行put或get同步操作時,其他的Segment是不受影響的。在定位到的Segment中根據key的hashCode找到HashEntry,然後進行遍歷,如果key值相等,用新值覆蓋舊值;如果不相等新建一個HashEntry放入鏈表中。
- get元素時,將key經過Hash後定位到對應的Segment,再經過遍歷後取得key對應的value。
ConcurrentHashMap相對於Hashtable或Collections.synchronizedMap(hashmap)來說是高效的,尤其是get元素時,不需要加鎖,但是如果鏈表中的元素比較多時,遍歷效率還是比較低(跟HashMap類似),接着看在1.8版本中做了哪些結構變化。
jdk1.8版本
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) {
if (key == null || value == null) throw new NullPointerException();
//根據key獲取hashCode
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
//table數組爲空,先初始化數組
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//key對應的索引位置沒有Node元素,直接通過CAS方式嘗試寫入,失敗則進行自旋至執行成功
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)
//擴容
tab = helpTransfer(tab, f);
else {
V oldVal = null;
//加鎖 遍歷鏈表 有相應key的話value直接覆蓋舊value,沒有的話添加到鏈表的尾部
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
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;
}
}
}
//如果是紅黑樹 按紅黑樹方式添加
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
Node<K,V> c, Node<K,V> v) {
return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
static final int spread(int h) {
return (h ^ (h >>> 16)) & HASH_BITS;
}
get元素
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
//根據key計算出對應的hashCode
int h = spread(key.hashCode());
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
if ((eh = e.hash) == h) {
//如果key在table數組中,直接取得對應的value並返回
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
//遍歷鏈表 獲取key對應的value
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
1.8中的變化
- 1.7中同步策略採用的是Segment分段鎖(內部實現是ReentrantLock),1.8中採用的是CAS+synchronized,效率更高;
- 1.8中引入了紅黑樹(跟HashMap一樣),當鏈表過長時直接將鏈表轉換成紅黑樹,查找效率從O(n)提升到O(logn)。
LinkedHashMap
LinkedHashMap繼承自HashMap,其內部結構是散列表+雙向鏈表,其中對散列表部分的put、get操作跟HashMap一樣,沒有變化。區別於HashMap的是,LinkedHashMap內部還維護了一個雙向鏈表(內部保存了所有元素),我們都知道HashMap基於Key-Value保存數據,但是HashMap不能按put順序去遍歷元素,如果想順序遍歷元素,可以使用HashMap的子類LinkedHashMap,LinkedHashMap維護了兩種迭代順序:
- 按插入順序迭代: 默認設置,鏈表是按插入順序添加的,那麼遍歷時也是按插入順序訪問的。
- 按訪問順序迭代: 調用get方法後,會將這次訪問的元素移動到鏈表的尾部,只有設置accessOrder=true時纔會生效。如果設置了最大容量(capacity)並重寫removeEldestEntry方法(返回true),當新添加元素時,會將鏈表中最老的元素移除。
內部結構
- 基於插入順序訪問:
Map<String, String> linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put("A", "1");
linkedHashMap.put("B", "2");
linkedHashMap.put("C", "3");
linkedHashMap.put("D", "4");
linkedHashMap.put("E", "5");
for (Map.Entry<String, String> entry : linkedHashMap.entrySet()) {
System.out.println("key: " + entry.getKey() + ",value: " + entry.getValue());
}
執行結果:
key: A,value: 1
key: B,value: 2
key: C,value: 3
key: D,value: 4
key: E,value: 5
- 基於訪問順序遍歷:
int initialCapacity = 10;//初始化容量
float loadFactor = 0.75f;//負載因子,一般設置爲0.75
boolean accessOrder = true;//false 基於插入順序 true 基於訪問順序
Map<String, String> linkedHashMap = new LinkedHashMap<String, String>(initialCapacity, loadFactor, accessOrder);
linkedHashMap.put("A", "1");
linkedHashMap.put("B", "2");
linkedHashMap.put("C", "3");
linkedHashMap.put("D", "4");
linkedHashMap.put("E", "5");
for (Map.Entry<String, String> entry : linkedHashMap.entrySet()) {
System.out.println("訪問前:key: " + entry.getKey() + ",value: " + entry.getValue());
}
linkedHashMap.get("C");//調用了get方法,執行完後此元素將被加入到鏈表尾部
System.out.println("-------------------");
for (Map.Entry<String, String> entry : linkedHashMap.entrySet()) {
System.out.println("訪問後:key: " + entry.getKey() + ",value: " + entry.getValue());
}
linkedHashMap.put("F", "6");
System.out.println("-------------------");
for (Map.Entry<String, String> entry : linkedHashMap.entrySet()) {
System.out.println("插入後:key: " + entry.getKey() + ",value: " + entry.getValue());
}
執行結果:
訪問前:key: A,value: 1
訪問前:key: B,value: 2
訪問前:key: C,value: 3
訪問前:key: D,value: 4
訪問前:key: E,value: 5
-------------------
訪問後:key: A,value: 1
訪問後:key: B,value: 2
訪問後:key: D,value: 4
訪問後:key: E,value: 5
訪問後:key: C,value: 3
-------------------
插入後:key: A,value: 1
插入後:key: B,value: 2
插入後:key: D,value: 4
插入後:key: E,value: 5
插入後:key: C,value: 3
插入後:key: F,value: 6
LinkedHashMap初始化時傳入了loadFactor並且設置爲true,當添加完元素後,通過linkedHashMap.get("C")
訪問了其中的某個元素,之後再遍歷LinkedHashMap,發現被訪問的元素已經被放到鏈表的尾部了。
- 移除最近最少使用的元素
int initialCapacity = 10;//初始化容量
float loadFactor = 0.75f;//負載因子,一般設置爲0.75
boolean accessOrder = true;//false 基於插入順序 true 基於訪問順序
Map<String, String> linkedHashMap = new LinkedHashMap<String, String>(initialCapacity, loadFactor, accessOrder){
@Override
protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
return size() > 3;
}
};
linkedHashMap.put("A", "1");
linkedHashMap.put("B", "2");
linkedHashMap.put("C", "3");
linkedHashMap.put("D", "4");
linkedHashMap.put("E", "5");
for (Map.Entry<String, String> entry : linkedHashMap.entrySet()) {
System.out.println("key: " + entry.getKey() + ",value: " + entry.getValue());
}
執行結果:
key: C,value: 3
key: D,value: 4
key: E,value: 5
我們添加了5個元素,但是最後遍歷時只顯示了3個元素,這是因爲重寫了removeEldestEntry方法,當元素的size>3時,此方法會返回true, 當每次添加元素時,會判斷removeEldestEntry方法返回值是否爲true, 是true的話會去除鏈表中最近最少使用的元素。
put元素
LinkedHashMap中沒有重寫put方法,直接使用父類HashMap#put方法,具體實現可以參看上面HashMap的介紹,LinkedHashMap#put絕大部分跟HashMap#put實現一致,在HashMap#put方法中,調用了newNode方法,目的是生成一個新節點Node,而在LinkedhashMap中重寫了這個方法:
//當散列表中沒有key對應的Node節點時,新生成一個key對應的Node節點,並向鏈表的尾部添加元素
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<K,V>(hash, key, value, e);
linkNodeLast(p);
return p;
}
//向鏈表的尾部添加元素
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
//定義了head tail雙向鏈表
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
//如果key已經在散列表中,散列表中的舊值會被覆蓋,同時鏈表中對應的節點也會移動到鏈表的尾部
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) {
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
可見在LinkedhashMap#put方法中調用newNode()時,除了新生成一個節點之外,還將此節點元素加入到一個雙向鏈表中,從而可以按一定順序去遍歷元素。
get元素
public V get(Object key) {
Node<K,V> e;
//調用父類HashMap中的getNode獲取key對應的value元素
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
get方法調用父類HashMap中的getNode獲取key對應的value元素,同時如果accessOrde爲true,同樣會調afterNodeAccess方法將元素添加到鏈表的尾部,實現按訪問順序排序。
移除最近最少使用元素
在HashMap#put中,調用了afterNodeInsertion()方法,意思是在插入新元素後做一些處理,但是在HashMap中該方法只是一個空實現,該方法在LinkedHashMaP中被重寫:
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
//移除元素
removeNode(hash(key), key, null, false, true);
}
}
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
LinkedHashMap#removeEldestEntry默認返回了false,所以默認往LinkedHashMap中put元素時,並不會移除鏈表中頭部的元素(最近最少使用),當我們重寫removeEldestEntry方法並符合條件時返回true,那麼便會觸發移除操作,移除掉最近最少使用的元素,這也是LRUCache的原理。
TreeMap
- TreeMap實現了Map接口,可以存儲有序key-value集合,內部結構是紅黑樹
- TreeMap能比較元素大小,對傳入的k也進行大小排序,可以使用自然順序,也可以自定義比較器(實現Comparable接口並重寫compareTo方法)進行排序
使用元素自然排序
- key是String類型:
public static void main(String[] args) {
TreeMap<String, Integer> treeMap = new TreeMap<>();
treeMap.put("D", 4);
treeMap.put("A", 1);
treeMap.put("C", 3);
treeMap.put("B", 2);
//遍歷TreeMap
for (Map.Entry<String, Integer> entry : treeMap.entrySet()) {
System.out.println("key: " + entry.getKey() + ",value: " + entry.getValue());
}
}
執行結果:
key: A,value: 1
key: B,value: 2
key: C,value: 3
key: D,value: 4
- key是Integer類型
public static void main(String[] args) {
TreeMap<Integer, Integer> treeMap = new TreeMap<>();
treeMap.put(4, 4);
treeMap.put(1, 1);
treeMap.put(3, 3);
treeMap.put(2, 2);
for (Map.Entry<Integer, Integer> entry : treeMap.entrySet()) {
System.out.println("key: " + entry.getKey() + ",value: " + entry.getValue());
}
}
執行結果:
key: 1,value: 1
key: 2,value: 2
key: 3,value: 3
key: 4,value: 4
String、Integer類內部都實現了Comparable接口並在重寫的compareTo方法中比較大小。
使用自定義排序
public static void main(String[] args) {
Person person1 = new Person("張三", 20);
Person person2 = new Person("李四", 10);
Person person3 = new Person("王五", 30);
Person person4 = new Person("趙六", 40);
TreeMap<Person, Integer> treeMap = new TreeMap<>();
treeMap.put(person1, person1.getAge());
treeMap.put(person2, person2.getAge());
treeMap.put(person3, person3.getAge());
treeMap.put(person4, person4.getAge());
//遍歷treeMap,其中key是自定義Person類型,Person內部是按age從小到大排序
for (Map.Entry<Person, Integer> entry : treeMap.entrySet()) {
System.out.println("key: " + entry.getKey() + ",value: " + entry.getValue());
}
}
static class Person implements Comparable<Person> {
private String Name;
private int age;
Person(String name, int age) {
this.Name = name;
this.age = age;
}
public String getName() {
return Name;
}
public void setName(String name) {
Name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int compareTo(Person o) {
if (age >= o.getAge()) {
//年齡大的元素放在後面
return 1;
}
return -1;
}
@Override
public String toString() {
return "[person(name:" + getName() + ",age:" + getAge() + ")]";
}
}
執行結果:
key: [person(name:李四,age:10)],value: 10
key: [person(name:張三,age:20)],value: 20
key: [person(name:王五,age:30)],value: 30
key: [person(name:趙六,age:40)],value: 40
最終遍歷結果是按Person#age從小到大排序的,這裏需要注意一點,放入TreeMap中的Key對象必須實現了Comparable接口,否則會拋出java.lang.ClassCastException: Collection._Map$xxx cannot be cast to java.lang.Comparable
異常信息。
關於TreeMaP的源碼,可以參考下面兩篇文章:
- https://www.jianshu.com/p/2dcff3634326
- https://www.cnblogs.com/skywang12345/p/3310928.html
參考
【1】Java8系列之重新認識HashMap:https://mp.weixin.qq.com/s/oIE4Nnqs5_lOE1D-r9xXWg
【2】https://crossoverjie.top/2018/07/23/java-senior/ConcurrentHashMap/
【3】LinkedHashMap: https://www.cnblogs.com/yulinfeng/p/8590010.html