深入淺出Java中Map介面的實現
一、 基本介紹
java.util.Map介面,檢視定義原始碼為:
package java.util;
public interface Map<K,V> {
……
}
HashMap、TreeMap、Hashtable、LinkedHashMap 都是實現Map介面,均為鍵值對形式,且map的key不允許重複
二、 詳細介紹
1. HashMap
HashMap類,我在之前的文章“Java面試 HashMap、HashSet原始碼解析”中進行過詳細的說明,想要詳細瞭解的可以跳轉過去看一下。
HashMap是最常用的Map類,根據鍵的Hash值計算儲存位置,儲存鍵值對,可以根據鍵獲取對應值。具有很快的訪問速度,但是是無序的、執行緒不安全的。且HashMap不同步,如果需要執行緒同步,則需要使用ConcurrentHashMap,也可以使用Collections.synchronizedMap(HashMap map)方法讓HashMap具有同步的能力。其實是否同步,就看有沒有synchronized關鍵字。 HashMap的key有且只能允許一個null。
注:執行緒不安全(多個執行緒訪問同一個物件或實現進行更新操作時,造成資料混亂)
2. Hashtable
Hashtable繼承自Dictionary類 ,它也是無序的,但是Hashtable是執行緒安全的,同步的,即任一時刻只有一個執行緒能寫Hashtable
由此我們比較一下HashMap和Hashtable的執行效率
測試插入效率如下:
long runCount=1000000;
Map<Integer,Integer> hashMap = new HashMap<Integer, Integer>();
Date dateBegin = new Date();
for (int i = 0; i < runCount; i++) {
hashMap.put(i, i);
}
Date dateEnd = new Date();
System.out.println("HashMap插入用時為:" + (dateEnd.getTime() - dateBegin.getTime()));
Map<Integer,Integer> hashtable = new Hashtable<Integer, Integer>();
Date dateBegin1 = new Date();
for (int i = 0; i < runCount; i++) {
hashtable.put(i, i);
}
Date dateEnd1 = new Date();
System.out.println("Hashtable插入用時為:" + (dateEnd1.getTime() - dateBegin1.getTime()));
執行結果為:
HashMap插入用時為:223
Hashtable插入用時為:674
如果我們將執行次數提高到20000000次,則執行時間分別為:
HashMap插入用時為:36779
Hashtable插入用時為:22632
由此可見,在資料量較小時,HashMap效率較高,但是當資料量增大,HashMap需要進行更多次的resize,這個操作會極大的降低HashMap的執行效率,因此在資料量大之後,Hashtable的執行效率更高。
而反過來重新測試讀取效率,程式碼如下:
long runCount=1000000;
Map<Integer,Integer> hashMap = new HashMap<Integer, Integer>();
for (int i = 0; i < runCount; i++) {
hashMap.put(i, i);
}
Date dateBegin = new Date();
for (Integer key : hashMap.keySet()) {
hashMap.get(key);
}
Date dateEnd = new Date();
System.out.println("HashMap讀取用時為:" + (dateEnd.getTime() - dateBegin.getTime()));
Map<Integer,Integer> hashtable = new Hashtable<Integer, Integer>();
for (int i = 0; i < runCount; i++) {
hashtable.put(i, i);
}
Date dateBegin1 = new Date();
for (Integer key : hashtable.keySet()) {
hashtable.get(key);
}
Date dateEnd1 = new Date();
System.out.println("Hashtable讀取用時為:" + (dateEnd1.getTime() - dateBegin1.getTime()));
執行結果為:
HashMap讀取用時為:54
Hashtable讀取用時為:65
如果將數量增加到20000000,則執行結果為:
HashMap讀取用時為:336
Hashtable讀取用時為:526
由此可見,HashMap的讀取效率更高。
3. LinkedHashMap
LinkedHashMap是Map中常用的有序的兩種實現之一, 它儲存了記錄的插入順序,先進先出。
對於LinkedHashMap而言,它繼承與HashMap,底層使用雜湊表與雙向連結串列來儲存所有元素。其基本操作與父類HashMap相似,它通過重寫父類相關的方法,來實現自己的連結列表特性。LinkedHashMap採用的hash演算法和HashMap相同,但是它重新定義了陣列中儲存的元素Entry,該Entry除了儲存當前物件的引用外,還儲存了其上一個元素before和下一個元素after的引用,從而在雜湊表的基礎上又構成了雙向連結列表,效果圖如下:
示例程式碼如下:
Map<Integer,Integer> linkedHashMap = new LinkedHashMap<Integer, Integer>();
linkedHashMap.put(1, 2);
linkedHashMap.put(3, 4);
linkedHashMap.put(5, 6);
linkedHashMap.put(7, 8);
linkedHashMap.put(9, 0);
System.out.println("linkedHashMap的值為:" + linkedHashMap);
輸出結果為:
linkedHashMap的值為:{1=2, 3=4, 5=6, 7=8, 9=0}
注:LinkedHashMap在遍歷的時候會比HashMap慢,不過有種情況例外,當HashMap容量很大,實際資料較少時,遍歷起來可能會 比LinkedHashMap慢,因為LinkedHashMap的遍歷速度只和實際資料有關,和容量無關,而HashMap的遍歷速度和他的容量有關
4. TreeMap
TreeMap實現SortMap介面,能夠把它儲存的記錄根據鍵排序,預設是按鍵值的升序排序,也可以指定排序的比較器,當用Iterator 遍歷TreeMap時,得到的記錄是排過序的。
TreeMap的排序原理是:紅黑樹演算法的實現 ,具體概念參考:點選開啟連結 。
它的主要實現是Comparator架構,通過比較的方式,進行一個排序,以下是TreeMap的原始碼,
比較的原始碼為:
/**
* Compares two keys using the correct comparison method for this TreeMap.
*/
@SuppressWarnings("unchecked")
final int compare(Object k1, Object k2) {
return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
: comparator.compare((K)k1, (K)k2);
}
我們也可以自定義Comparator, 對TreeMap資料的排序規則進行修改,這點是LinkedHashMap不能實現的
具體程式碼如下:
Map<String,Integer> treeMap = new TreeMap<String, Integer>();
treeMap.put("aa", 888);
treeMap.put("ee", 55);
treeMap.put("dd", 777);
treeMap.put("cc", 88);
treeMap.put("bb", 999);
System.out.println("使用預設排序規則,生成的結果為:" + treeMap);
Map<String, Integer> treeMap2 = new TreeMap<String, Integer>(new Comparator<String>() {
public int compare(String o1, String o2) {
return o2.compareTo(o1);
}
});
treeMap2.put("aa", 888);
treeMap2.put("ee", 55);
treeMap2.put("dd", 777);
treeMap2.put("cc", 88);
treeMap2.put("bb", 999);
System.out.println("使用自定義排序規則,生成的結果為:" + treeMap2);
執行結果為:
使用預設排序規則,生成的結果為:{aa=888, bb=999, cc=88, dd=777, ee=55}
使用自定義排序規則,生成的結果為:{ee=55, dd=777, cc=88, bb=999, aa=888}
這邊可以檢視一下compareTo()的方法原始碼,內容為:
public int compareTo(String anotherString) {
//先得到比較值的字串長度
int len1 = value.length;
int len2 = anotherString.value.length;
//得到最小長度
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;
int k = 0;
//逐個比較字串中字元大小
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;
}
k++;
}
//如果在兩個字串的最小長度內,字元均相同,則比較長度
return len1 - len2;
}
由此可見,當key值中儲存了Integer型別的數字時,將預設無法根據數字大小來進行排序,處理方式如下:
Map<String,Integer> treeMap = new TreeMap<String, Integer>();
treeMap.put("1", 888);
treeMap.put("9", 55);
treeMap.put("31", 777);
treeMap.put("239", 88);
treeMap.put("177", 999);
System.out.println("使用預設排序規則,生成的結果為:" + treeMap);
Map<String, Integer> treeMap2 = new TreeMap<String, Integer>(new Comparator<String>() {
public int compare(String o1, String o2) {
//修改比較規則,按照數字大小升序排列
return Integer.parseInt(o1) - Integer.parseInt(o2);
}
});
treeMap2.put("1", 888);
treeMap2.put("9", 55);
treeMap2.put("31", 777);
treeMap2.put("239", 88);
treeMap2.put("177", 999);
System.out.println("使用自定義排序規則,生成的結果為:" + treeMap2);
執行結果為:
使用預設排序規則,生成的結果為:{1=888, 177=999, 239=88, 31=777, 9=55}
使用自定義排序規則,生成的結果為:{1=888, 9=55, 31=777, 177=999, 239=88}
三、 總結
- Map中,HashMap具有超高的訪問速度,如果我們只是在Map 中插入、刪除和定位元素,而無關執行緒安全或者同步問題,HashMap 是最好的選擇。
- 如果考慮執行緒安全或者寫入速度的話,可以使用HashTable
- 如果想要按照存入資料先入先出的進行讀取。 那麼使用LinkedHashMap
- 如果需要讓Map按照key進行升序或者降序排序,那就用TreeMap
參照
相關文章
- 深入淺出java的MapJava
- 深入淺出 Java 中列舉的實現原理Java
- 深入淺出 Locust 實現
- 【深入淺出ES6】Set\Map
- Java中實現不可變MapJava
- 深入淺出--梯度下降法及其實現梯度
- 深入淺出:使用Java和Spring Security實現認證與授權JavaSpring
- 深入淺出學Java-HashMapJavaHashMap
- 深入淺出MyBatis:JDBC和MyBatis介紹MyBatisJDBC
- 深入淺出 Vue 系列 -- 資料劫持實現原理Vue
- 深入淺出FE(十四)深入淺出websocketWeb
- 深入淺出Java多執行緒Java執行緒
- 深入淺出Java記憶體模型Java記憶體模型
- Java基礎 Java-IO流 深入淺出Java
- 深入 Go 的 Map 使用和實現原理Go
- 深入淺出一下Java的HashMapJavaHashMap
- Java容器深入淺出之String、StringBuffer、StringBuilderJavaUI
- 深入淺出Java執行緒池ThreadPoolExecutorJava執行緒thread
- 深入淺出換膚相關技術以及如何實現
- 淺讀-《深入淺出Nodejs》NodeJS
- 深入淺出mongooseGo
- HTTP深入淺出HTTP
- 深入淺出WebpackWeb
- 深入淺出HTTPHTTP
- mysqldump 深入淺出MySql
- 深入淺出——MVCMVC
- 深入淺出IO
- 深入淺出decorator
- ArrayList 深入淺出
- 深入淺出 RabbitMQMQ
- 深入淺出PromisePromise
- 深入淺出 ZooKeeper
- 深入淺出 Java 併發程式設計 (1)Java程式設計
- 深入淺出 Java 併發程式設計 (2)Java程式設計
- 深入淺出Java多執行緒(十):CASJava執行緒
- 深入淺出Java多執行緒(十一):AQSJava執行緒AQS
- 深入淺出Java執行緒池:使用篇Java執行緒
- 基於React 原始碼深入淺出setState:setState非同步實現React原始碼非同步
- IO多路複用——深入淺出理解select、poll、epoll的實現