業內常常說的一句話是不要重複造輪子,可是有時候,只有本身造一個輪子了,纔會深入明白什麼樣的輪子適合山路,什麼樣的輪子適合平地!html
我將會持續更新java基礎知識,歡迎關注。java
往期章節:c++
在上一章節中,咱們講了集合框架的Collection部分,下面咱們來說一下Map接口框架
咱們再看一下集合框架的結構圖函數
map接口的實現類大體有 HashMap、LinkedHahMap、TreeMap、HashTable 四類。源碼分析
Map
從這個名字上咱們能夠知道是「地圖、映射」的意思。
map集合使用鍵(key)值(value)來保存數據,其中值(value)能夠重複,但鍵(key)必須是惟一,也能夠爲空,但最多隻能有一個key爲空。
HashMap
對於hashMap,咱們須要從名字中的hash入手,去分析他,hash,咱們常常叫作哈希表又或者叫作散列函數。
在 JDK 中,Object 的 hashcode 方法是本地方法,也就是用 c 語言或 c++ 實現的,該方法直接返回對象的 內存地址。
先看一張圖
如上圖中的結構過程整個造成過程大體以下:
1,假如咱們先插入一個鍵值對,而後進行hash後,存放在了數組的1號位置;
2,而後咱們再插入一個鍵值對 ,通過hash後,存在了4號位置,;
3,再而後咱們又插入了一個鍵值對,這個時候很不幸,與1號位置衝突,而後這個時候會在1號位置以後追加一個鏈表,讓1號位置的Entry中的next指向此次最新追加的這一個鍵值對,以此類推;
從上圖中咱們大概能夠了解到,整個的HashMap的數據結構就是 數組+鏈表(在jdk1.8中確切的說是Node對象,只不過Node實現了Entry接口),咱們先看看Node 類的代碼:
從上面的代碼咱們能夠看出主要有4個屬性,key value、 hash、以及再包含一個自身的類對象Node~ 這部分其實和咱們上一節講過的LinkedList的數據結構很像
當咱們在代碼中咱們會調用put方法,源碼以下:
1 /** 2 * Associates the specified value with the specified key in this map. 3 * If the map previously contained a mapping for the key, the old 4 * value is replaced. 5 * 6 * @param key key with which the specified value is to be associated 7 * @param value value to be associated with the specified key 8 * @return the previous value associated with <tt>key</tt>, or 9 * <tt>null</tt> if there was no mapping for <tt>key</tt>. 10 * (A <tt>null</tt> return can also indicate that the map 11 * previously associated <tt>null</tt> with <tt>key</tt>.) 12 */ 13 public V put(K key, V value) { 14 return putVal(hash(key), key, value, false, true); 15 }
從註釋中咱們能夠了解大意:「關聯一個指定的值和鍵在這個map 若是map顯然已經包含了這個鍵的映射,那麼舊值就會被替代」
在這個代碼中繼續調用putval方法,咱們繼續看源碼:
1 /** 2 * Implements Map.put and related methods 3 * 4 * @param hash hash for key 5 * @param key the key 6 * @param value the value to put 7 * @param onlyIfAbsent if true, don't change existing value 8 * @param evict if false, the table is in creation mode. 9 * @return previous value, or null if none 10 */ 11 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, 12 boolean evict) { 13 Node<K,V>[] tab; Node<K,V> p; int n, i; 14 if ((tab = table) == null || (n = tab.length) == 0) 15 n = (tab = resize()).length; 16 if ((p = tab[i = (n - 1) & hash]) == null) 17 tab[i] = newNode(hash, key, value, null); 18 else { 19 Node<K,V> e; K k; 20 if (p.hash == hash && 21 ((k = p.key) == key || (key != null && key.equals(k)))) 22 e = p; 23 else if (p instanceof TreeNode) 24 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); 25 else { 26 for (int binCount = 0; ; ++binCount) { 27 if ((e = p.next) == null) { 28 p.next = newNode(hash, key, value, null); 29 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 30 treeifyBin(tab, hash); 31 break; 32 } 33 if (e.hash == hash && 34 ((k = e.key) == key || (key != null && key.equals(k)))) 35 break; 36 p = e; 37 } 38 } 39 if (e != null) { // existing mapping for key 40 V oldValue = e.value; 41 if (!onlyIfAbsent || oldValue == null) 42 e.value = value; 43 afterNodeAccess(e); 44 return oldValue; 45 } 46 } 47 ++modCount; 48 if (++size > threshold) 49 resize(); 50 afterNodeInsertion(evict); 51 return null; 52 }
從註釋中咱們能夠知道:實現map.put 和相關的方法
從第14行代碼開始意思是,這個map若是是空的,那先初始化一個數組,而後再new一個 Node對象,放入數組中,具體的位置根據數組的長度和hash的 「與」 值肯定 ;
若是已經有元素存在map中,則代碼從19行開始執行,對於這塊大體意思就是,若是已經存在相同的hashcode ,那麼會判斷鍵是否相同,若是也相同則替換原有的值。
另外關於hashMap的擴容,咱們須要瞭解的是默認的容量是16 重載因子是0.75 什麼意思呢?
就是說當集合中的數據量大小達到了門限值 16*0.75=12,那再添加元素進去,就會對當前的集合進行擴容,擴容爲原來的2倍。
注意,這裏是根據門限值進行擴容也就是12,而不是總容量16。
TreeMap
根據名字咱們能夠知道這是一個和樹有關的map,關於treemap的結構圖大體以下:
從上圖中能夠大概瞭解到,treemap的實現底層是紅黑樹,瞭解這個的同窗相信也就能知道爲何treemap是有序的了。
這個圖中的每個節點對象都是Entry,他和hashmap中的不同,而是一個類,不是接口,代碼以下圖所示:
從上圖圈中能夠了解到,每個節點都包含 key、value、 以及Entry類型的 left、right、parent 屬性
上述結構圖造成過程大體描述以下:
1,咱們先插入一個鍵值對,鍵爲2;
2,再插入一個鍵爲1,這個時候,會將這個鍵值對放在上一個的左子節點部分;
3,再插入一個3,這個時候,會將他放在第一個節點的右子節點部分;
4,若是再插入一個4,那這個時候,會把前面3個總體做爲他的左子節點,以此類推;
咱們再來看看源碼:
1 /** 2 * Associates the specified value with the specified key in this map. 3 * If the map previously contained a mapping for the key, the old 4 * value is replaced. 5 * 6 * @param key key with which the specified value is to be associated 7 * @param value value to be associated with the specified key 8 * 9 * @return the previous value associated with {@code key}, or 10 * {@code null} if there was no mapping for {@code key}. 11 * (A {@code null} return can also indicate that the map 12 * previously associated {@code null} with {@code key}.) 13 * @throws ClassCastException if the specified key cannot be compared 14 * with the keys currently in the map 15 * @throws NullPointerException if the specified key is null 16 * and this map uses natural ordering, or its comparator 17 * does not permit null keys 18 */ 19 public V put(K key, V value) { 20 Entry<K,V> t = root; 21 if (t == null) { 22 compare(key, key); // type (and possibly null) check 23 24 root = new Entry<>(key, value, null); 25 size = 1; 26 modCount++; 27 return null; 28 } 29 int cmp; 30 Entry<K,V> parent; 31 // split comparator and comparable paths 32 Comparator<? super K> cpr = comparator; 33 if (cpr != null) { 34 do { 35 parent = t; 36 cmp = cpr.compare(key, t.key); 37 if (cmp < 0) 38 t = t.left; 39 else if (cmp > 0) 40 t = t.right; 41 else 42 return t.setValue(value); 43 } while (t != null); 44 } 45 else { 46 if (key == null) 47 throw new NullPointerException(); 48 @SuppressWarnings("unchecked") 49 Comparable<? super K> k = (Comparable<? super K>) key; 50 do { 51 parent = t; 52 cmp = k.compareTo(t.key); 53 if (cmp < 0) 54 t = t.left; 55 else if (cmp > 0) 56 t = t.right; 57 else 58 return t.setValue(value); 59 } while (t != null); 60 } 61 Entry<K,V> e = new Entry<>(key, value, parent); 62 if (cmp < 0) 63 parent.left = e; 64 else 65 parent.right = e; 66 fixAfterInsertion(e); 67 size++; 68 modCount++; 69 return null; 70 }
從註釋能夠了解,和hashmap表達的意思同樣,再看代碼中,若是尚未初始化,那先進行初始化root節點,若是有指定比較器,那就根據指定的比較器,進行比較。
而後後面的代碼就是根據插入的鍵進行大小的比較,而後設置各類左右子父節點屬性等。
LinkedHahMap
關於linkedHashMap,從名字能夠知道他是和鏈表有關係的,結構圖以下所示:
注意圖中的灰色箭頭,在這裏咱們須要和一開始咱們講的hashmap中的結構圖,作一個比較,在hashmap中雖然也有鏈表,可是咱們要知道,那個鏈表只是爲了解決hash衝突,鏈表中的元素互相之間沒有什麼關係,不表明誰早誰晚,或者說誰大誰小。
而這裏的鏈表中比hashmap多了一個before 和after,以下圖:
那麼這裏的before 和after就肯定了插入的順序前後。
HashTable
關於hashTable基本上和HashMap結構一致,再也不贅述,惟一區別就是他是線程安全的,並且不容許null value,若是有直接拋出異常
爲何是線程安全的?咱們看以下源碼:
1 /** 2 * Maps the specified <code>key</code> to the specified 3 * <code>value</code> in this hashtable. Neither the key nor the 4 * value can be <code>null</code>. <p> 5 * 6 * The value can be retrieved by calling the <code>get</code> method 7 * with a key that is equal to the original key. 8 * 9 * @param key the hashtable key 10 * @param value the value 11 * @return the previous value of the specified key in this hashtable, 12 * or <code>null</code> if it did not have one 13 * @exception NullPointerException if the key or value is 14 * <code>null</code> 15 * @see Object#equals(Object) 16 * @see #get(Object) 17 */ 18 public synchronized V put(K key, V value) { 19 // Make sure the value is not null 20 if (value == null) { 21 throw new NullPointerException(); 22 } 23 24 // Makes sure the key is not already in the hashtable. 25 Entry<?,?> tab[] = table; 26 int hash = key.hashCode(); 27 int index = (hash & 0x7FFFFFFF) % tab.length; 28 @SuppressWarnings("unchecked") 29 Entry<K,V> entry = (Entry<K,V>)tab[index]; 30 for(; entry != null ; entry = entry.next) { 31 if ((entry.hash == hash) && entry.key.equals(key)) { 32 V old = entry.value; 33 entry.value = value; 34 return old; 35 } 36 } 37 38 addEntry(hash, key, value, index); 39 return null; 40 }
從上面的方法中加上的 synchronized 關鍵詞,咱們就能夠知道了。
另外要注意的是hashTable已經不建議使用了,取而代之的是ConcurrentHashMap ,由於他既保證了線程的安全性,又進一步提升了,效率。
四個實現類的異同:
1. HashMap是基於哈希表(hash table)實現,其keys和values都沒有順序。
2. TreeMap是基於紅黑樹(red-black tree)實現,按照keys排序元素,能夠指定排序規則,若是不指定則按照天然排序。
3. LinkedHashMap是基於哈希表(hash table)實現,按照插入順序排序元素。
4. Hashtable區別與HashMap的地方只有,它是同步的(synchronized),所以性能較低些。爲了性能,在線程安全的代碼中,優先考慮使用HashMap。
Map的遍歷
對於map的遍歷大體有如下3種方式:
1 //第1種只遍歷鍵或者值,經過增強for循環 2 for(String s1:map.keySet()){//遍歷map的鍵 3 System.out.println("鍵key :"+s1); 4 } 5 for(String s2:map.values()){//遍歷map的值 6 System.out.println("值value :"+s2); 7 } 8 9 10 //第2種方式Map.Entry<String, String>的增強for循環遍歷輸出鍵key和值value 11 for(Map.Entry<String, String> entry : map.entrySet()){ 12 System.out.println("鍵 key :"+entry.getKey()+" 值value :"+entry.getValue()); 13 } 14 15 16 //第3種Iterator遍歷獲取,而後獲取到Map.Entry<String, String>,再獲得getKey()和getValue() 17 Iterator<Map.Entry<String, String>> it=map.entrySet().iterator(); 18 while(it.hasNext()){ 19 Map.Entry<String, String> entry=it.next(); 20 System.out.println("鍵key :"+entry.getKey()+" value :"+entry.getValue()); 21 }
比較器
在這裏說如下Comparable 、 Comparator 2個接口
Comparable
只有一個方法compareto,並且是經過返回的int型數值判斷大小
一、比較者大於被比較者(也就是compareTo方法裏面的對象),那麼返回正整數
二、比較者等於被比較者,那麼返回0
三、比較者小於被比較者,那麼返回負整數
Comparator
Comparator接口裏面有一個compare方法,方法有兩個參數T o1和T o2,是泛型的表示方式,分別表示待比較的兩個對象,方法返回值和Comparable接口同樣是int,有三種狀況:
一、o1大於o2,返回正整數
二、o1等於o2,返回0
三、o1小於o3,返回負整數
兩者之間的區別:
1,比較方法參數數不一樣;
2,後者接口中提供了更多的方法能夠供使用;
3,前者只能和本身比較,後者能夠對其餘2個類進行比較;
關於集合部分,到這裏咱們就所有結束了。
注;以上源碼分析都是基於jdk1.8
文中如有不正之處,歡迎批評指正!