前言: 相信大家都打開過層級很多很多的文件夾。如果把第一個文件夾看作是樹的根節點的話,下麵的子文件夾就可以看作一個子節點。不過最終我們尋找的還是文件夾中的文件,文件可以看做是葉子節點。下麵我們介紹一種模式,與這種樹級結構息息相關。當然,今天的主角是HashMap。接下來我們一起來看HashMap中到 ...
前言:
相信大家都打開過層級很多很多的文件夾。如果把第一個文件夾看作是樹的根節點的話,下麵的子文件夾就可以看作一個子節點。不過最終我們尋找的還是文件夾中的文件,文件可以看做是葉子節點。下麵我們介紹一種模式,與這種樹級結構息息相關。當然,今天的主角是HashMap。接下來我們一起來看HashMap中到底是怎麼跟樹級結構進行掛鉤的。
一、定義
將對象組合成樹形結構以表示“部分-整體”的一個層次結構,使客戶端對單個對象和組合對象保持一致的方式處理。
二、適用場景
1、客戶端可以忽略組合對象與單個對象的差異
註意組合模式中的概念,當客戶端使用組合模式的時候是對單個對象和組合對象保持一致的方式處理,而不是不同的方式處理。所以如果客戶端可以忽略組合對象和單個對象的差異時,才用組合模式。
2、處理一個樹形結構
這裡就很好理解了,組合模式就是處理樹形結構的
三、結合HashMap看組合模式
1、抽象構件:總的抽象類或者介面,定義一些通用的方法,比如新增、刪除。
2、中間構件:繼承或者實現抽象構件,定義存儲方式,並針對特殊需要重寫抽象構件的方法。
3、葉子節點:繼承或者實現抽象構件,並針對特殊需要重寫抽象構件的方法。
葉子節點需要實現或者繼承抽象構件,如果有需要也可以添加到中間構件上。就比如說我現在進入D盤,D盤就是一個抽象構件,無論在D盤哪裡,我都可以執行新建,刪除操作。中間構件就相當於文件夾,裡面定義了相應的存儲方式,加入的文件是葉子節點,並且會按照這種方式來存儲,同時這個中間構件也可以選擇再加入一個其他中間構件或者自己。葉子節點就可以看作文件。註意,在組合模式下所有類都需要直接或者間接,繼承或實現總的抽象構件。下麵講HashMap中的putAll()方法
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { public void putAll(Map<? extends K, ? extends V> m) { putMapEntries(m, true); } final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) { int s = m.size(); if (s > 0) { if (table == null) { // pre-size float ft = ((float)s / loadFactor) + 1.0F; int t = ((ft < (float)MAXIMUM_CAPACITY) ? (int)ft : MAXIMUM_CAPACITY); if (t > threshold) threshold = tableSizeFor(t); } else if (s > threshold) resize(); for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) { K key = e.getKey(); V value = e.getValue(); putVal(hash(key), key, value, false, evict); } } } }
上面是簡化的HashMap。我們看見putAll方法傳入的是Map對象,Map就是一個抽象構件(同時這個構件中只支持鍵值對的存儲格式),而HashMap是一個中間構件,HashMap中的Node節點就是葉子節點。說到中間構件就會有規定的存儲方式。HashMap中的存儲方式是一個靜態內部類的數組Node<K,V>[] tab。下麵是簡化的具體代碼
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; } }
所以我們調用put方法實際上是加入一個葉子節點,而我們調用putAll相當於把其他Map下麵的文件夾中的文件拷貝過來。還有Hashtable也可以看作一個中間構件,裡面的putAll方法同樣是適合放入實現Map介面的對象。這裡如果將Hashtable的這個中間構件放入HashMap中,那麼我們這些方法還能用嗎。答案是能用,組合模式處理的就是一致性的問題。Map介面中的方法,比如put(),remove(),到下麵的中間構件中其實都是一致的功能,不過就是不同的中間構件中的處理方式可能會有細微的差別。下麵是我的測試代碼
public class Test { public static void main(String[] args) {
Map<Integer,String> rootmap=new HashMap<Integer,String>();
rootmap.put(1,"HashMap文件1");
Map<Integer,String> map1=new Hashtable<Integer,String>();
map1.put(2,"Hashtable文件1");
map1.put(3,"Hashtable文件2");
map1.put(4,"Hashtable文件3");
rootmap.putAll(map1);
System.out.println(rootmap);
} }
輸出結果為:
上面實現將Hashtable中的鍵值對放入HashMap中。如果放在組合模式下,你就可以看作是將Hashtable這個中間構件下的文件拷貝到HashMap這個中間構件中。在我們電腦里文件任何類型的文件都可以拷到其他任意地方。但是用來理解Map這個抽象構件的就不行,因為實現Map介面的類只支持放入鍵值對格式的葉子節點,如果不是(比如說ArrayList)就不行,這是底層方法不支持。同時我們回到組合模式上重要的一點:一致性,Map下麵添加都是put方法,需要傳入兩個值,List下都是add方法,只需要傳入一個值,如果List中的可以通過調用putAll方法放入Map里,那麼我該怎麼給List賦key或value呢。(不是說不能有put(1,list)這樣的操作,而是說在組合模式下操作時候需要有一致性,並且這裡說的組合模式針對的是putAll方法,與其他方法無關)
四、總結
這裡只是解析了HashMap中的putAll組合模式。在平常寫代碼的過程中要使用組合模式,就需要先定義一個抽象類或者介面。在這裡就可以看作是抽象構件,主要實現一些基本的通用的功能。接著如果有需要就要定義一個中間構件,並且實現抽象構件,裡面的方法可以根據特殊需要重寫。而且這個類最重要的是要有存儲結構在裡面,用Map存儲,或者用List存儲等等都可以。接著就是我們真正的葉子節點,同樣是繼承或實現抽象構件,裡面的方法可以根據特殊需要重寫。當你看完這篇博客,你可以再去看定義和適用場景,說不定收穫會更大。