本篇博客主要講解Map接口的4個實現類HashMap、Hashtable、LinkedHashMap、TreeMap的使用方法以及四者之間的區別。html
注意:本文中代碼使用的JDK版本爲1.8.0_191java
值得注意的是,Map接口是獨立的接口,並無繼承Collection接口(這裏是重點,面試常問):面試
public interface Map<K,V> {
......
}
複製代碼
HashMap是Map接口最經常使用的實現類,存儲Key Value鍵值對,HashMap不保證元素的順序但保證Key必須惟一。安全
HashMap類的代碼聲明以下所示:bash
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
......
}
複製代碼
使用HashMap添加元素有如下3個方法:微信
首先看下put()方法的使用方法:ide
HashMap<String, String> platformMap = new HashMap<>();
// 添加元素
System.out.println(platformMap.put("cnblogs.com", "博客園"));
System.out.println(platformMap.put("juejin.im", "掘金"));
System.out.println(platformMap.put("map.weixin.qq.com", "微信公衆號"));
System.out.println(platformMap.put("zwwhnly.com", "我的博客"));
// 添加劇復的Key,沒有添加成功,可是會更新Key對應的Value值
// 不過代碼不會報錯,而是返回已經存在Key對應的Value
System.out.println(platformMap.put("zwwhnly.com", "我的博客"));
複製代碼
以上代碼運行的輸出結果是:ui
nullthis
nullspa
null
null
我的博客
調試代碼也會發現platformMap只有4個元素,並且元素的順序和添加的順序不一樣:
值得注意的是最後一行代碼platformMap.put("zwwhnly.com", "我的博客")
的返回值是「我的博客」,即以前已存在的Key:zwwhnly.com,對應的Value值。
簡單修改下這句代碼爲:
System.out.println(platformMap.put("zwwhnly.com", "我的博客2"));
複製代碼
再次運行代碼,發現輸出結果沒變,platformMap也仍是4個元素,可是platformMap元素的內容變了:
若是Key存在時,不但願Value值被覆蓋,能夠將代碼修改成:
System.out.println(platformMap.putIfAbsent("zwwhnly.com", "我的博客2"));
複製代碼
另外,HashMap還提供了一個putAll()方法來批量添加元素,使用方法以下所示:
HashMap<String, String> platformMap = new HashMap<>();
HashMap<String, String> majorPlatfromMap = new HashMap<>();
// 添加元素
majorPlatfromMap.put("cnblogs.com", "博客園");
majorPlatfromMap.put("juejin.im", "掘金");
HashMap<String, String> otherPlatformMap = new HashMap<>();
otherPlatformMap.put("map.weixin.qq.com", "微信公衆號");
otherPlatformMap.put("zwwhnly.com", "我的博客");
otherPlatformMap.put("cnblogs.com", "博客園2");
platformMap.putAll(majorPlatfromMap);
platformMap.putAll(otherPlatformMap);
複製代碼
值得注意的是,因爲majorPlatfromMap與otherPlatformMap存在相同的key:cnblogs.com,最終platformMap中Key爲」cnblogs.com「的Value值爲:「博客園2「,以下圖所示:
使用HashMap獲取元素有如下2個方法:
首先看下get()方法的使用方法:
System.out.println(platformMap.get("cnblogs.com"));
System.out.println(platformMap.get("csdn.com"));
複製代碼
輸出結果:
博客園
null
當key不存在時,若是須要設置默認值,可使用getOrDefault():
System.out.println(platformMap.getOrDefault("csdn.com", "CSDN"));
複製代碼
上面這句代碼的輸出結果爲:CSDN。
獲取HashMap元素個數的使用方法以下所示:
System.out.println("platformMap的元素個數爲:" + platformMap.size());
複製代碼
使用HashMap刪除元素有如下2個重載:
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
@Override
public boolean remove(Object key, Object value) {
return removeNode(hash(key), key, value, true, true) != null;
}
複製代碼
使用方法以下所示:
System.out.println(platformMap.remove("zwwhnly.com"));
System.out.println(platformMap.remove("zwwhnly.com"));
System.out.println(platformMap.remove("map.weixin.qq.com", "微信公衆號"));
System.out.println(platformMap.remove("juejin.im", "博客園"));
複製代碼
上面代碼的輸出結果爲:
我的博客
null
true
false
使用HashMap修改元素有如下2個重載:
@Override
public boolean replace(K key, V oldValue, V newValue) {
Node<K,V> e; V v;
if ((e = getNode(hash(key), key)) != null &&
((v = e.value) == oldValue || (v != null && v.equals(oldValue)))) {
e.value = newValue;
afterNodeAccess(e);
return true;
}
return false;
}
@Override
public V replace(K key, V value) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) != null) {
V oldValue = e.value;
e.value = value;
afterNodeAccess(e);
return oldValue;
}
return null;
}
複製代碼
使用方法以下所示:
System.out.println(platformMap.replace("cnblogs.com", "博客園:https://www.cnblogs.com/zwwhnly/"));
System.out.println(platformMap.replace("juejin.im", "掘金", "掘金:https://juejin.im/user/5c7ce730f265da2dca388167"));
複製代碼
上面代碼的輸出結果爲:
博客園
true
判斷HashMap是否爲空的使用方法以下所示:
System.out.println("isEmpty:" + platformMap.isEmpty());
複製代碼
遍歷HashMap的元素主要有如下4種方式:
其中2和3的方式,使用的是Set集合的2種遍歷方式,由於platformMap.entrySet()
返回的類型是一個Set集合,裏面的元素類型是Map.Entry<K,V>
:
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}
複製代碼
使用方法以下所示:
System.out.println("方式1:使用keySet遍歷");
for (String key : platformMap.keySet()) {
System.out.println("Key:" + key + ",Value:" + platformMap.get(key));
}
System.out.println();
System.out.println("方式2:經過Map.entrySet使用iterator遍歷");
Iterator<Map.Entry<String, String>> iterator = platformMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> entry = iterator.next();
System.out.println("Key:" + entry.getKey() + ",Value:" + entry.getValue());
}
System.out.println();
System.out.println("方式3:經過Map.entrySet使用iterator遍歷");
for (Map.Entry<String, String> entry : platformMap.entrySet()) {
System.out.println("Key:" + entry.getKey() + ",Value:" + entry.getValue());
}
System.out.println();
System.out.println("方式4:使用values遍歷,使用這種方式沒法遍歷Key");
for (String value : platformMap.values()) {
System.out.println(value);
}
複製代碼
清空HashMap中全部元素的使用方法以下所示:
platformMap.clear();
複製代碼
上面講解的幾點,完整代碼以下所示:
package collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class MapTest {
public static void main(String[] args) {
HashMap<String, String> platformMap = new HashMap<>();
HashMap<String, String> majorPlatfromMap = new HashMap<>();
// 添加元素
majorPlatfromMap.put("cnblogs.com", "博客園");
majorPlatfromMap.put("juejin.im", "掘金");
HashMap<String, String> otherPlatformMap = new HashMap<>();
otherPlatformMap.put("map.weixin.qq.com", "微信公衆號");
otherPlatformMap.put("zwwhnly.com", "我的博客");
platformMap.putAll(majorPlatfromMap);
platformMap.putAll(otherPlatformMap);
System.out.println(platformMap.get("cnblogs.com"));
System.out.println(platformMap.get("csdn.com"));
System.out.println(platformMap.getOrDefault("csdn.com", "CSDN"));
System.out.println("platformMap的元素個數爲:" + platformMap.size());
System.out.println(platformMap.remove("zwwhnly.com"));
System.out.println(platformMap.remove("zwwhnly.com"));
System.out.println(platformMap.remove("map.weixin.qq.com", "微信公衆號"));
System.out.println(platformMap.remove("juejin.im", "博客園"));
System.out.println(platformMap.replace("cnblogs.com", "博客園:https://www.cnblogs.com/zwwhnly/"));
System.out.println(platformMap.replace("juejin.im", "掘金", "掘金:https://juejin.im/user/5c7ce730f265da2dca388167"));
System.out.println("isEmpty:" + platformMap.isEmpty());
System.out.println("方式1:使用keySet遍歷");
for (String key : platformMap.keySet()) {
System.out.println("Key:" + key + ",Value:" + platformMap.get(key));
}
System.out.println();
System.out.println("方式2:經過Map.entrySet使用iterator遍歷");
Iterator<Map.Entry<String, String>> iterator = platformMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> entry = iterator.next();
System.out.println("Key:" + entry.getKey() + ",Value:" + entry.getValue());
}
System.out.println();
System.out.println("方式3:經過Map.entrySet使用iterator遍歷");
for (Map.Entry<String, String> entry : platformMap.entrySet()) {
System.out.println("Key:" + entry.getKey() + ",Value:" + entry.getValue());
}
System.out.println();
System.out.println("方式4:使用values遍歷,使用這種方式沒法遍歷Key");
for (String value : platformMap.values()) {
System.out.println(value);
}
platformMap.clear();
System.out.println("isEmpty:" + platformMap.isEmpty());
}
}
複製代碼
輸出結果爲:
博客園
null
CSDN
platformMap的元素個數爲:4
我的博客
null
true
false
博客園
true
isEmpty:false
方式1:使用keySet遍歷
Key:cnblogs.com,Value:博客園:www.cnblogs.com/zwwhnly/
Key:juejin.im,Value:掘金:juejin.im/user/5c7ce7…
方式2:經過Map.entrySet使用iterator遍歷
Key:cnblogs.com,Value:博客園:www.cnblogs.com/zwwhnly/
Key:juejin.im,Value:掘金:juejin.im/user/5c7ce7…
方式3:經過Map.entrySet使用iterator遍歷
Key:cnblogs.com,Value:博客園:www.cnblogs.com/zwwhnly/
Key:juejin.im,Value:掘金:juejin.im/user/5c7ce7…
方式4:使用values遍歷,使用這種方式沒法遍歷Key
isEmpty:true
Hashtable也是Map接口的實現類,值得注意的是,它的方法都是同步的,便是線程安全的。
public synchronized int size() {
return count;
}
public synchronized boolean isEmpty() {
return count == 0;
}
複製代碼
HashTable類的代碼聲明以下所示:
public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable {
{
......
}
複製代碼
從以上代碼也能看出,Hashtable的基類是Dictionary,而HashMap的基類是AbstractMap(這裏是重點,面試常問)。
HashTable類的使用方法和HashMap基本同樣,只需修改下聲明處的代碼便可:
Hashtable<String, String> platformMap = new Hashtable<>();
Hashtable<String, String> majorPlatfromMap = new Hashtable<>();
Hashtable<String, String> otherPlatformMap = new Hashtable<>();
複製代碼
LinkedHashMap也是Map接口的實現類,相比於HashMap,它使用到了鏈表,所以能夠保證元素的插入順序,即FIFO(First Input First Output 先進先出)。
LinkedHashMap類的代碼聲明以下所示:
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>
{
......
}
複製代碼
從以上代碼也能看出,LinkedHashMap類繼承了HashMap類。
LinkedHashMap類的使用方法和HashMap基本同樣,只需修改下聲明處的代碼便可:
LinkedHashMap<String, String> platformMap = new LinkedHashMap<>();
LinkedHashMap<String, String> majorPlatfromMap = new LinkedHashMap<>();
LinkedHashMap<String, String> otherPlatformMap = new LinkedHashMap<>();
複製代碼
TreeMap也是Map接口的實現類,值得注意的是,TreeMap中的元素是有序的,默認的排序規則是按照key的字典順序升序排序。
TreeMap類的代碼聲明以下所示:
public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.Serializable {
......
}
複製代碼
TreeMap類的使用方法和HashMap基本同樣,只需修改下聲明處的代碼便可:
TreeMap<String, String> platformMap = new TreeMap<>();
TreeMap<String, String> majorPlatfromMap = new TreeMap<>();
TreeMap<String, String> otherPlatformMap = new TreeMap<>();
複製代碼
1)HashMap、Hashtable、LinkedHashMap、TreeMap都實現了Map接口
2)四者都保證了Key的惟一性,即不容許Key重複
HashMap不保證元素的順序
Hashtable不保證元素的順序
LinkHashMap保證FIFO即按插入順序排序
TreeMap保證元素的順序,支持自定義排序規則
空口無憑,上代碼看效果:
HashMap<String, String> hashMap = new HashMap<>();
Hashtable<String, String> hashtable = new Hashtable<>();
LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>();
TreeMap<String, String> treeMap = new TreeMap<>();
String[] letterArray = new String[]{"B", "A", "D", "C", "E"};
for (String letter : letterArray) {
hashMap.put(letter, letter);
hashtable.put(letter, letter);
linkedHashMap.put(letter, letter);
treeMap.put(letter, letter);
}
System.out.println("HashMap(我不保證順序):" + hashMap);
System.out.println("Hashtable(我不保證順序):" + hashtable);
System.out.println("LinkedHashMap(我保證元素插入時的順序):" + linkedHashMap);
System.out.println("TreeMap(我按排序規則保證元素的順序):" + treeMap);
複製代碼
上面代碼的輸出結果爲:
HashMap(我不保證順序):{A=A, B=B, C=C, D=D, E=E}
Hashtable(我不保證順序):{A=A, E=E, D=D, C=C, B=B}
LinkedHashMap(我保證元素插入時的順序):{B=B, A=A, D=D, C=C, E=E}
TreeMap(我按排序規則保證元素的順序):{A=A, B=B, C=C, D=D, E=E}
HashMap,LinkedHashMap容許添加null值(Key和Value都容許),因此如下代碼是合法的:
HashMap<String, String> hashMap = new HashMap<>();
LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>();
hashMap.put(null, null);
linkedHashMap.put(null, null);
複製代碼
TreeMap不容許Key有null值,但容許Value有null值,因此如下代碼是合法的:
TreeMap<String, String> treeMap = new TreeMap<>();
treeMap.put("cnblogs.com", null);
複製代碼
可是treeMap.put(null, null);
會引起java.lang.NullPointerException
異常:
Hashtable不容許添加null值(Key和Value都不容許),添加null值時會拋出java.lang.NullPointerException
異常。
Hashtable<String, String> hashtable = new Hashtable<>();
hashtable.put("cnblogs.com", null);
hashtable.put(null, null);
複製代碼
運行上面的代碼,報錯信息以下所示:
HashMap、LinkedHashMap、TreeMap不是線程安全的。
Hashtable是線程安全的,這是它的優勢,同時也致使在理論狀況下,Hashtable的效率沒有HashMap高。
因此若是對線程安全沒有要求,建議使用HashMap。
Hashtable的父類是Dictionary。
HashMap的父類是AbstractMap。
LinkedHashMap的父類是HashMap,HashMap的父類是AbstractMap,因此LinkedHashMap也繼承了AbstractMap。
TreeMap的父類是AbstractMap。
TreeMap默認的排序規則是按照key的字典順序升序排序。
先來看下TreeMap存儲String類型的例子:
TreeMap<String, String> treeMap = new TreeMap<>();
String[] letterArray = new String[]{"B", "A", "D", "C", "E"};
for (String letter : letterArray) {
treeMap.put(letter, letter);
}
for (String key : treeMap.keySet()) {
System.out.println("key:" + key + ",Value:" + treeMap.get(key));
}
複製代碼
輸出結果:
key:A,Value:A
key:B,Value:B
key:C,Value:C
key:D,Value:D
key:E,Value:E
那若是TreeMap中放入的元素類型是咱們自定義的引用類型,它的排序規則是什麼樣的呢?
帶着這個疑問,咱們新建個Student類以下:
package collection;
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
複製代碼
而後添加以下驗證代碼:
TreeMap<Student, Student> studentTreeMap = new TreeMap<>();
Student student1 = new Student("zhangsan", 20);
Student student2 = new Student("lisi", 22);
Student student3 = new Student("wangwu", 24);
Student student4 = new Student("zhaoliu", 26);
Student student5 = new Student("zhangsan", 22);
studentTreeMap.put(student1, student1);
studentTreeMap.put(student2, student2);
studentTreeMap.put(student3, student3);
studentTreeMap.put(student4, student4);
studentTreeMap.put(student5, student5);
for (Student student : studentTreeMap.keySet()) {
System.out.println("name:" + student.getName() + ",age:" + student.getAge());
}
複製代碼
滿心歡喜的運行代碼想看下效果,結果卻發現報以下錯誤:
爲何會這樣呢?
這是由於咱們並無給Student類定義任何排序規則,TreeMap說我也不知道咋排序,仍是甩鍋拋出異常吧,哈哈。
怎麼解決呢?有如下兩種方式:
天然排序的實現方式是讓Student類實現接口Comparable,並重寫該接口的方法compareTo,該方法會定義排序規則。
package collection;
public class Student implements Comparable<Student> {
// 省略其它代碼
@Override
public int compareTo(Student o) {
return 0;
}
}
複製代碼
使用IDEA默認生成的compareTo()方法如上所示。
這個方法會在執行add()方法添加元素時執行,以便肯定元素的位置。
若是返回0,表明兩個元素相同,只會保留第一個元素
若是返回值大於0,表明這個元素要排在參數中指定元素o的後面
若是返回值小於0,表明這個元素要排在參數中指定元素o的前面
所以若是對compareTo()方法不作任何修改,直接運行以前的驗證代碼,會發現集合中只有1個元素:
name:zhangsan,age:20
而後修改下compareTo()方法的邏輯爲:
@Override
public int compareTo(Student o) {
// 排序規則描述以下
// 按照姓名的長度排序,長度短的排在前面,長度長的排在後面
// 若是姓名的長度相同,按字典順序比較String
// 若是姓名徹底相同,按年齡排序,年齡小的排在前面,年齡大的排在後面
int orderByNameLength = this.name.length() - o.name.length();
int orderByName = orderByNameLength == 0 ? this.name.compareTo(o.name) : orderByNameLength;
int orderByAge = orderByName == 0 ? this.age - o.age : orderByName;
return orderByAge;
}
複製代碼
再次運行以前的驗證代碼,輸出結果以下所示:
name:lisi,age:22
name:wangwu,age:24
name:zhaoliu,age:26
name:zhangsan,age:20
name:zhangsan,age:22
比較器排序的實現方式是新建一個比較器類,繼承接口Comparator,重寫接口中的Compare()方法。
注意:使用此種方式Student類不須要實現接口Comparable,更不須要重寫該接口的方法compareTo。
package collection;
import java.util.Comparator;
public class StudentComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
// 排序規則描述以下
// 按照姓名的長度排序,長度短的排在前面,長度長的排在後面
// 若是姓名的長度相同,按字典順序比較String
// 若是姓名徹底相同,按年齡排序,年齡小的排在前面,年齡大的排在後面
int orderByNameLength = o1.getName().length() - o2.getName().length();
int orderByName = orderByNameLength == 0 ? o1.getName().compareTo(o2.getName()) : orderByNameLength;
int orderByAge = orderByName == 0 ? o1.getAge() - o2.getAge() : orderByName;
return orderByAge;
}
}
複製代碼
而後修改下驗證代碼中聲明studentTreeSet的代碼便可:
TreeMap<Student, Student> studentTreeMap = new TreeMap<>(new StudentComparator());
複製代碼
輸出結果和使用天然排序的輸出結果徹底同樣。
Java集合中List,Set以及Map等集合體系詳解(史上最全)
打個小廣告,歡迎掃碼關注微信公衆號:「申城異鄉人」,按期分享Java技術乾貨,讓咱們一塊兒進步。