1.介紹
Map接口定義了一個保存key-value的對象,該對象中key值是不存在重復的,每個key值至多對應一個value
在前面幾篇的文章中分別介紹了Map的實現類,如HashMap、Hashtable、TreeMap,詳細可以查看
2.類圖結構
Map結構
如上圖所示是實現Map接口的類圖結構,主要包含了如下的類與接口:
- Map接口: 定義將鍵值映射到值的對象,Map規定不能包含重復的鍵值,每個鍵最多可以映射一個值,這個接口是用來替換Dictionary類。
- SortedMap接口:定義按照key排序的Map結構,規定key-value是根據鍵key值的自然排序進行排序的,或者根據構造key-value時設定的構造器進行排序。
- NavigableMap接口: 是SortedMap接口的子接口,在其基礎上擴展了針對搜索目標返回最近匹配項的導航方法,例如方法lowEntry、floorEntry、ceilingEntry等,如果不存在這樣的鍵,則返回null
- AbstractMap類:提供了一個Map骨架的實現,盡量減少了實現Map接口所需要的工作量
- HashMap類:HashMap是實現了Map接口的key-value集合,實現了所有map的操作,允許key和value為null,它相當于Hashtable,與之存在的區別是hashMap不是線程安全的,HashMap允許null值。
- TreeMap類:TreeMap是基于紅黑樹的實現,也是記錄了key-value的映射關系,該映射根據key的自然排序進行排序或者根據構造方法中傳入的比較器進行排序,也就是說TreeMap是有序的key-value集合
- Hashtable類:它是類似與HashMap的key-value的哈希表,不允許key-value為NULL值,另外一點值得注意的是Hashtable是線程安全的
- Serializable接口:實現了該接口標識了類可以被序列化和反序列化,具體的 查詢序列化詳解
- Cloneable接口:實現了該接口的類可以顯示的調用Object.clone()方法,合法的對該類實例進行字段復制,如果沒有實現Cloneable接口的實例上調用Obejct.clone()方法,會拋出CloneNotSupportException異常。正常情況下,實現了Cloneable接口的類會以公共方法重寫Object.clone()
3.比較
雖然HashMap、Hashtable、TreeMap這三個都是Map接口的實現,其內部實現及性能等還是存在區別,下面將從區別及性能兩個方面去分析。
3.1 區別
3.1.1 基本
- HashMap:初始化容量為16,擴容每次為2*oldCap,key-value可以為NULL值
- Hashtable:初始化容量為11,擴容每次為2*oldCap+1,key-value不可以為NULL值
- TreeMap:初始化容量為0,內部是紅黑樹結構,不存在hash沖突的情況,不存在擴容的操作,key-value不可以為NULL值
3.1.2 實現
- HashMap:實現了Map接口,繼承了AbstractMap類
- Hashtable:實現了Map接口,繼承了AbstractMap類
- TreeMap:由于TreeMap是有序的,所以其除了實現了Map接口,還實現了SortedMap、NavigableMap接口
3.1.3 內部原理
- HashMap:HashMap是散列表實現,內部是數組+鏈表或者紅黑樹的結構
- Hashtable:Hashtable也是散列表實現,內部是數組+鏈表的結構
- TreeMap:TreeMap內部是紅黑樹的結構
3.1.4 線程安全
- HashMap:不是線程安全的,其實通過Map m = Collections.synchronizeMap(hashMap)的方式也可以使得HashMap變成線程安全的,但是這樣做對程序的性能可能是噩夢,在后面會介紹ConcurrentHashMap,建議在多線程的情況下可以使用ConcurrentHashMap替換HashMap.
- Hashtable:是線程安全的,內部方法使用關鍵字synchronized修飾
- TreeMap:不是線程安全的
3.2 性能
按照如下代碼對HashMap、Hashtable、TreeMap的性能進行測試
public class HashMapProgress {
//定義用于測試的HashMap
private static HashMap<Integer,Integer> hashMap = new HashMap<>();
//定義用于測試的Hashtable
private static Hashtable<Integer,Integer> hashtable = new Hashtable<>();
//定義用于測試的TreeMap
private static TreeMap<Integer,Integer> treeMap = new TreeMap<>();
/**
* 添加元素的方法
* @param map 對應的map
* @param count 添加個數
*/
public static void addEntry(Map<Integer,Integer> map, int count){
Long startTime = System.currentTimeMillis();
if (count <= 0){
return;
}
for (int i = 0; i < count; i++) {
map.put(i,i);
}
Long endTime = System.currentTimeMillis();
System.out.println("添加(" + count + ")個元素使用時間:" + (endTime - startTime) + "s");
}
/**
* 獲取元素的方法
* @param map
* @param count
*/
public static void getEntry(Map<Integer,Integer> map, int count){
Long startTime = System.currentTimeMillis();
if (count <= 0){
return;
}
for (int i = 0; i < count; i++) {
map.get(i);
}
Long endTime = System.currentTimeMillis();
System.out.println("獲取(" + count + ")個元素使用時間:" + (endTime - startTime) + "s");
}
public static void main(String[] args){
System.out.println("-------HashMap測試開始-----");
addEntry(hashMap,1000000);
getEntry(hashMap,1000000);
System.out.println("-------HashMap測試結束-----");
System.out.println("-------Hashtable測試開始-----");
addEntry(hashtable,1000000);
getEntry(hashtable,1000000);
System.out.println("-------Hashtable測試結束-----");
System.out.println("-------TreeMap測試開始-----");
addEntry(treeMap,1000000);
getEntry(treeMap,1000000);
System.out.println("-------TreeMap測試結束-----");
}
}
分別測試了100000,1000000,10000000個數據的情況,測試結果如下所示:
數據量 | HashMap | Hashtable | TreeMap |
---|---|---|---|
100000 | 插入用時:18s 查詢用時:9s | 插入用時:14s 查詢用時:5s | 插入用時:33s 查詢用時:17s |
1000000 | 插入用時:98s 查詢用時:20s | 插入用時:625s 查詢用時:31s | 插入用時:242s 查詢用時: 145s |
10000000 | 插入用時:9773s 查詢用時:811s | 插入用時:15055s 查詢用時:3369s | 插入用時:22354s 查詢用時: 3889s |
通過上表可以看出隨著數據量的增加,時間變化差異還是很大的,而在單線程的情況下還是盡量使用HashMap,相對于Hashtable、TreeMap性能更好,而針對特定的情況需視情況而論。
4.總結
本文主要是對前面介紹的HashMap、Hashtable、TreeMap這些Map接口實現類的一個總結,主要分析這些實現類的一些特點以及存在的差異,最后想說的是針對不同的情況可以選擇不同的Map進行編碼,如有問題,望指正。