1. Map集合
Map集合提供的是一種key-value鍵值對元素的存儲容器,key值不允許重複,重複的key值會導致元素覆蓋!
Map接口提供的能力:
public interface Map<K,V> {
/*判斷map容器是否爲空*/
boolean isEmpty();
/*判斷key是否存在map中*/
boolean containsKey(Object key);
/*判斷value是否存在map中*/
boolean containsValue(Object value);
/*通過key獲取value*/
V get(Object key);
/*將key-value鍵值對更新到map中*/
V put(K key, V value);
/*將對應key的鍵值對從map中移除*/
V remove(Object key);
/*將Map m的內容全部更新到當前map中去*/
void putAll(Map<? extends K, ? extends V> m);
/*清空map*/
void clear();
/*獲取當前map中所有key值的set集合*/
Set<K> keySet();
/*獲取當前map中所有value值的集合,可以重複*/
Collection<V> values();
/*獲取當前map中entry的set集合*/
Set<Map.Entry<K, V>> entrySet();
/*判斷兩個map集合是否相等*/
boolean equals(Object o);
/*返回map集合的hashCode*/
int hashCode();
/*Java8,Map接口中新增的default方法*/
/*默認值爲defaultValue的get方法*/
default V getOrDefault(Object key, V defaultValue) {
V v;
return (((v = get(key)) != null) || containsKey(key))
? v
: defaultValue;
}
/*指定action函數式接口的foreach*/
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
action.accept(k, v);
}
}
/*通過函數式接口function獲取需要替換到Map中value值*/
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
Objects.requireNonNull(function);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
// ise thrown from function is not a cme.
v = function.apply(k, v);
try {
entry.setValue(v);
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
}
}
/*key值無效或者value爲null,put成功*/
default V putIfAbsent(K key, V value) {
V v = get(key);
if (v == null) {
v = put(key, value);
}
return v;
}
/*key,value都匹配刪除成功,否則進行remove行爲*/
default boolean remove(Object key, Object value) {
Object curValue = get(key);
if (!Objects.equals(curValue, value) ||
(curValue == null && !containsKey(key))) {
return false;
}
remove(key);
return true;
}
/*key,value都匹配成功的時候,進行value值的替換*/
default boolean replace(K key, V oldValue, V newValue) {
Object curValue = get(key);
if (!Objects.equals(curValue, oldValue) ||
(curValue == null && !containsKey(key))) {
return false;
}
put(key, newValue);
return true;
}
/*key值有效的時候,進行替換,如果是新增key值,不會新建鍵值對*/
default V replace(K key, V value) {
V curValue;
if (((curValue = get(key)) != null) || containsKey(key)) {
curValue = put(key, value);
}
return curValue;
}
/*見compute*/
default V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction);
V v;
if ((v = get(key)) == null) {
V newValue;
if ((newValue = mappingFunction.apply(key)) != null) {
put(key, newValue);
return newValue;
}
}
return v;
}
/*見compute*/
default V computeIfPresent(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue;
if ((oldValue = get(key)) != null) {
V newValue = remappingFunction.apply(key, oldValue);
if (newValue != null) {
put(key, newValue);
return newValue;
} else {
remove(key);
return null;
}
} else {
return null;
}
}
/*通過remappingFunction函數式接口對value進行二次計算,然後再進一步操作鍵值對*/
default V compute(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue = get(key);
V newValue = remappingFunction.apply(key, oldValue);
if (newValue == null) {
// delete mapping
if (oldValue != null || containsKey(key)) {
// something to remove
remove(key);
return null;
} else {
// nothing to do. Leave things as they were.
return null;
}
} else {
// add or replace old mapping
put(key, newValue);
return newValue;
}
}
/*通過remappingFunction函數式接口,對value進行二次計算,再put*/
default V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
Objects.requireNonNull(value);
V oldValue = get(key);
V newValue = (oldValue == null) ? value :
remappingFunction.apply(oldValue, value);
if(newValue == null) {
remove(key);
} else {
put(key, newValue);
}
return newValue;
}
}
Java8在Map接口中新增的default方法:
Map接口的遍歷:
public class MapTest
{
public static void main(String args[])
{
Map<String,String> mp = new HashMap<>();
mp.put("key1","value1");
mp.put("key2","value2");
mp.put("key3","value3");
/*通過Map的keySet遍歷*/
for (String s : mp.keySet())
{
System.out.println("key:" + s + " value:" + mp.get(s));
}
/*通過Map的values遍歷*/
for (String s : mp.values())
{
System.out.println("value:" + s);
}
/*通過Map的entrySet遍歷*/
for (Map.Entry<String,String> entry : mp.entrySet())
{
System.out.println("key:" + entry.getKey() + " value:" + entry.getValue());
}
/*通過Map接口的forEach默認方法*/
mp.forEach((key,value)->{
System.out.println("key:" + key + " value:" +value);
});
}
}
2. HashMap集合:
2.1 HashMap集合簡述:
- HashMap是繼承自AbstractMap抽象類,AbstractMap抽象類繼承自Map接口;
- HashMap是線程不安全集合;
- HashMap可以接收null元素,key值,value值均可以爲null;
- HashMap是一個無序的集合;
2.2 HashMap的實現原理:
2.2.1 HashMap的原則:
一個key對應一個唯一的value;當key重複的時候,覆蓋原有的value;HashMap的key的唯一性取決於HashCode值相等並且equals返回true!
2.2.2 HashMap的默認配置:
/*Map容量,默認值爲16,會被自動轉換爲最近的2的N次冪*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
/*Map容器最大容量*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/*當達到容量的75%的時候,啓動容器擴容,一次擴一倍*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/*Java8後,link長度達到8的時候,滿足鏈表轉紅黑樹條件1*/
static final int TREEIFY_THRESHOLD = 8;
/*Java8後,link長度從8降到6的時候,滿足紅黑樹轉換鏈表條件*/
static final int UNTREEIFY_THRESHOLD = 6;
/*Java8後,當容器達到64的時候,滿足鏈表轉紅黑樹條件2*/
static final int MIN_TREEIFY_CAPACITY = 64;
2.2.3 HashMap的存儲形式:
- Java8以前,HashMap的存儲結構是哈希+鏈表,將元素key值的hashcode轉換爲哈希桶的索引;當未發生哈希碰撞的時候,元素直接入桶,當發生哈希碰撞的時候(即哈希桶索引相同、hashcode可能相同,也可能不相同),衝突元素遍歷哈希桶中的鏈表,如果發現hashcode相等且equals相等的元素直接覆蓋,未發現hashcode相等同時equals也相等的元素,將元素插入哈希桶中的鏈表!查找元素的時間複雜度爲O(1)+O(n),如果哈希碰撞嚴重的情況時間複雜度就接近O(n)了,性能不優!所以有了後面的演進!
- Java8以後,HashMap的存儲結構是哈希+鏈表&紅黑樹,入桶規則和Java8以前相同,但是當Map容器達到64,哈希桶中鏈表元素超過8的時候,會自動將鏈表轉換爲紅黑樹,這樣當哈希碰撞嚴重的時候,查找元素的時間複雜度就接近O(nlogn),比O(n)更優!
Ps:注意HashMap中的哈希碰撞有兩種形式:
- 一種是hashcode不相等,但是轉換到哈希桶索引的時候,索引相等!這種碰撞由於hashcode不相同,紅黑樹是接近對稱的,查找元素的複雜度接近O(nlogn)!
- 另一種是hashcode相等時候發生的碰撞,這種碰撞會導致紅黑樹在遍歷相等hashcode的時候更接近一個鏈表的遍歷,因爲相等hashcode的元素會一直在左樹,這樣查找元素的時間複雜度又接近O(n)了!但是可以讓key值支持Comparable接口,支持相同hashcode但是equals不相等的場景的比較,這樣在hashcode相等equals不相等的時候,HashMap會自動根據key對象提供的Comparable能力確認節點在紅黑樹的左邊還是右邊!這樣查找元素的時間複雜度又提升到了O(nlogn)了!
2.2.4 hashcode()方法和equals()方法重寫的注意事項:
- HashMap實現通過key值對象的hashcode和equals來確認key值的唯一性,兩者均相等的時候,確認爲一個唯一key!
- hashcode相等,一定會發生哈希碰撞;hashcode不相等,也可能會發生哈希碰撞,只是會降低碰撞的概率!碰撞越多,查詢時,性能越差,所以不同的key值,最好使用不同的hashcode,來降低碰撞概率!
- 當業務形態中,認爲兩個key值對象equals相等就是相同key的時候;建議重寫equals()的同時一定要同步重寫hashcode(),並且最好equals不相等,hashcode也不相等;equals相等,hashcode也相等,這樣既滿足設計也能保證性能最優!
簡單分析原因(前提條件:當業務形態中,認爲兩個key值對象equals相等就是相同key的時候):
- equals相等,hashcode相等:由於equals、hashcode都相等,HashMap會用後來的key元素覆蓋,原有的key元素!(元素位置:相同哈希桶的相同hashcode位置)
- equals相等,hashcode不相等:業務側認爲equals相等的,key就是相同的;但是由於hashcode不相等,HashMap會認爲是兩個key,分別都存放在容器裏面,不符合設計預期!(元素位置:在不同的哈希桶或者相同哈希桶的不同hashcode位置)
- equals不相等,hashcode相等:由於hashcode相等,equals不相等,在發生哈希碰撞後,哈希桶中的紅黑樹會存在相同hashcode不同equals的節點;再獲取數據的時候,需要遍歷這些相同hashcode不同equals的節點,由於在key不支持Comparable接口的時候,左樹右樹的選擇是通過hashcode判斷的,導致相同hashcode會一直查找左樹;如果這樣的節點數目增多,紅黑樹的查找複雜度就接近鏈表查找的複雜度O(n)了,導致查詢效率降低!(元素位置,在相同哈希桶的不同hashcode位置)
- equals不相等,hashcode不相等:由於hashcode不相等,哈希桶中的紅黑樹元素可以通過hashcode直接區分,不存在性能降低的問題,推薦使用!(元素位置,在不同的哈希桶或者相同哈希桶的不同hashcode位置)
2.2.5 HashMap擴容問題:
由於這裏不想對HashMap的resize()進行展開描述,僅僅記錄下Java8以前和Java8以後的實現差異;
- Java8以前,在數據轉移的時候,使用頭插法,同時併發調用put()方法觸發擴容,可能會造成逆序環形鏈表的問題,造成死鏈!
- Java8以後,將頭插法改爲了尾插法,不會再出現逆序環形鏈表的問題,不會造成死鏈!
2.2.6 key值的不可變性:
HashMap中的元素位置主要是依賴於key值對象的hashcode和equals進行判斷選擇的,所有針對key值對象的選擇,儘可能選擇不可變對象;如果選擇可變對象,必須保證在元素進入HashMap後,對象不再改變!因爲如果key值對象改變導致hashcode和equals改變了,就會造成通過key值無法獲取到正確元素的情況!
2.2.7 HashMap線程安全性問題討論:
HashMap是個線程不安全的集合,多線程同時操作可能會造成數據異常;如鏈表與紅黑樹相互轉換的時候併發、HashMap擴容的時候併發、鏈表插入讀取的時候併發、紅黑樹插入讀取的時候併發等場景;
如果想讓HashMap支持線程安全,可以使用Collections.synchronizedMap(HashMap)進行包裝!
2.2.8 HashXXX集合中contains()方法、equals()方法的特殊性:
由於HashXXX這類集合的特殊性:通過使用hashcode確定元素位置的方式來提高元素查詢的效率!導致key相等的判斷不僅僅通過equals方法判斷,同時需要hashcode相等!這樣的話HashSetXXX的contains()、HashMapXXX的containsKey()、HashXXX的equals()等涉及到key值判斷的方法,都不再像其他集合那樣僅僅判斷eqauls就可以了,還需要同步判斷hashcode!
2.2.9 hashcode重寫建議:
原則:equals相等的hashcode必須相等,equals不相等的hashcode儘可能不相等!
- 將對象中每個有意義的變量計算出一個int類型的hashCode值
- 對每個hashCode值乘上一個不同的質數,最後求和
Ps:由於這裏針對HashMap討論比較簡單,並未深入源碼層面,推薦兩篇比較詳細的博文<HashMap看這篇就夠了> <一文搞定HashMap的實現原理和麪試>
3. LinkedHashMap集合:
3.1 LinkedHashMap集合簡述:
LinkedHashMap是HashMap的一個子類,是一個有序的HashMap(可以根據插入順序,遍歷Map);LinkedHashMap通過重寫了HashMap中的newNode()方法,每次調用put插入數據的時候,都會同時將插入順序記錄到一個鏈表中;當需要遍歷Map的時候,通過鏈表中的順序進行遍歷,來實現Map的有序性!
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;
}
4. Hashtable集合:
4.1 Hashtable集合簡述:
- Hashtable繼承自Map接口和Dictionary抽象類
- Hashtable是線程安全集合;
- Hashtable不可以接收null元素,key值,value值都不可以爲null,否則會報空指針異常;
- Hashtable是一個無序的集合;
4.2 Hashtable的實現:
- 存儲形式:哈希+鏈表,細節實現與Java8以前的HashMap類似
- 同步方式:通過synchronized同步方法,實現線程安全
4.3 Hashtable與HashMap比較:
- HashMap線程不安全,Hashtable線程安全
- HashMap可以接收null元素,Hashtable不可以接收null元素
- HashMap中引入紅黑樹提升查詢性能,Hashtable依然使用鏈表存儲哈希桶中的數據
- HashMap與Hashtable都是無序的Map集合
Ps:由此可以看出,HashMap更加優選於Hashtable(Hashtable的查詢效率很低、同步效率也很低、目前處於半廢棄狀態),如果需要線程安全的HashMap,可以使用Collections.synchronizedMap()進行包裝或者使用ConcurrentHashMap集合!
5. TreeMap集合:
5.1 TreeMap集合簡述:
- TreeMap繼承自Map接口和SortedMap抽象類
- TreeMap是線程不安全集合;
- TreeMap的key值不可以爲null,value值可以爲null;
- TreeMap是一個有序的集合,順序爲key元素排列升序;
5.2 TreeMap的實現:
- TreeMap不同於XXXHashMap,不是通過Hash桶實現的Key值的存在;TreeMap是一個有序Map,存儲結構爲紅黑樹;
- TreeMap元素紅黑樹的確認規則:如果TreeMap選擇自然排序(即構造函數中未設置Comparator對象),則通過每個key元素的compareTo方法進行元素大小關係判斷,進而確認元素位置,所以在自然排序的TreeMap每個key元素必須是Comparable接口的實現類!如果TreeMap選擇定製排序,則可以通過自己對Comparator的實現,來決定兩個key元素判斷標準,如果不再通過key元素的compareTo方法進行判斷,key元素可以不再是Comparable的實現類!
- TreeMap的key元素相等判斷規則:僅僅根據元素的compareTo方法或者Comparator實現來判斷,不使用equals進行判斷;但是爲了保證邏輯的一致性,建議compareTo方法或者Comparator實現邏輯與equals邏輯一致!當compareTo方法返回0的時候,判斷爲兩個key元素相等,與HashMap不同的是,此時僅僅更新value的內容,key元素不再刷新!
public class TreeMapTest
{
public static void main(String args[])
{
/*自然排序*/
TreeMap<TmKey,String> tmap = new TreeMap<>();
TmKey key1 = new TmKey(100);
TmKey key2 = new TmKey(200);
TmKey key3 = new TmKey(300);
tmap.put(key2, "key2");
tmap.put(key1, "key1");
tmap.put(key3, "key3");
System.out.println(tmap.size());
for (Map.Entry<TmKey, String> entry : tmap.entrySet())
{
System.out.println(entry.getKey().num);
System.out.println(entry.getValue());
}
}
}
class TmKey implements Comparable<Object>
{
public int num;
public TmKey(int num)
{
this.num = num;
}
@Override
public int compareTo(Object obj) {
return 0;
}
@Override
public boolean equals(Object obj) {
return true;
}
}
輸出:
Ps:Comparable接口中obj1.compareTo(obj2)方法,返回正整數表示obj1大於obj2;返回負整數表示obj1小於obj2;返回0表示obj1和obj2相等!
5.3 TreeMap的使用:
public class TreeMapTest
{
public static void main(String args[])
{
/*自然排序*/
TreeMap<TmKey,String> tmap = new TreeMap<>();
TmKey key1 = new TmKey(100);
TmKey key2 = new TmKey(200);
TmKey key3 = new TmKey(300);
tmap.put(key2, null);
tmap.put(key1, null);
tmap.put(key3, null);
System.out.println(tmap.size());
for (Map.Entry<TmKey, String> entry : tmap.entrySet())
{
System.out.println(entry.getKey().num);
}
/*定製排序*/
TreeMap<TmKey,String> tmap2 = new TreeMap<>((obj1,obj2)->{
return obj1.num > obj2.num ? 1 : obj1.num < obj2.num ? -1 : 0;
});
tmap2.put(key2, null);
tmap2.put(key1, null);
tmap2.put(key3, null);
System.out.println(tmap2.size());
for (Map.Entry<TmKey, String> entry : tmap2.entrySet())
{
System.out.println(entry.getKey().num);
}
}
}
class TmKey implements Comparable<Object>
{
public int num;
public TmKey(int num)
{
this.num = num;
}
@Override
public int compareTo(Object obj) {
TmKey m = (TmKey)obj;
return this.num > m.num ? 1 : this.num < m.num ? -1 : 0;
}
public boolean equals(Object obj) {
if (obj == this)
{
return true;
}
if (obj != null && obj.getClass() == TmKey.class)
{
TmKey m = (TmKey)obj;
return m.num == this.num;
}
return false;
}
}
Ps:通過上面TreeMap自然排序和定製排序的例子,可以說明TreeMap的遍歷輸出都是升序輸出!
5.4 TreeMap提供的方法:
TreeMap類中除了實現了Map接口中的方法,由於它Key元素的有序性,還提供了大量的關於Entry和Key數值比較相關的方法,此處就不再展開贅述了!
常用方法:
6. WeakHashMap集合:
WeakHashMap與HashMap基本完全相同,只是HashMap的key保留對象實例的強引用;WeakHashMap的key保留對象的弱引用!當key被Jvm垃圾回收機制回收後,WeakHashMap會自動清理該key的鍵值對!
7. IdentityHashMap集合:
IdentityHashMap與HashMap基本完全相同,只是HashMap判斷key相等的條件是hashcode相等同時equals相等;IdentityHashMap判斷key相等的條件必須是兩個key爲同一個對象實例(key1==key2)!
8. EnumMap集合:
- EnumMap繼承自Map接口;
- EnumMap是線程不安全集合;
- EnumMap的key值不可以爲null,value值可以爲null;
- EnumMap是一個有序的集合,集合順序爲key元素枚舉值的數值升序;
- EnumMap中的key元素只能是同一類枚舉類型的枚舉實例,EnumMap構造的時候已經確定了key值元素的枚舉類型,如果類型不一致則拋出異常!
- EnumMap是通過位運算進行存儲,有着佔用內存小,操作效率的優勢;但是侷限爲key值只能是枚舉類型!