Map接口Map中的每個成員方法由一個關(guān)鍵字(key)和一個值(value)構(gòu)成。Map接口不直接繼承于Collection接口,因為它包裝的是一組成對的“鍵-值”對象的集合,,而且在Map接口的集合中也不能有重復(fù)的key出現(xiàn),,因為每個鍵只能與一個成員元素相對應(yīng)。 Map接口的子接口以及主要實現(xiàn)類有: 子接口:Bindings,、ConcurrentMap,、ConcurrentNavigableMap、MessageContext,、LogicMessageContext,、NavigableMap、SOAPMessageMap,、SortedMap 實現(xiàn)類:AbstractMap, Attributes,AuthProvider, ConcurrentHashMap, EnumMap,ConcurrentSkipListMap,HashMap, Hashtable, IdentityHashMap, LinkedHashMap, PrinterStateReasons,Properties,Provider, RenderingHints, SimpleBindings, TabularDataSupport,TreeMap,UIDefaults,WeakHashMap
Map中定義的方法說明: 在Map接口中定義的通用方法并不是很多,。 a) 添加和刪除Map中的某個元素 · put(K, V) : 將給定的“鍵-值”對放入到給定的Map當中 · putAll(Map<? extends K, ? extends V) : 將指定的Map中的“鍵-值”對放入到給定的Map當中 · remove(Object key) : 從該集合中移除指定的對象,并返回對應(yīng)的value · clear() : 清空Map中的所有對象 b) 查詢與Map有關(guān)的數(shù)據(jù) · int size() : 返回此Map中“鍵-值”對的個數(shù) · boolean isEmpty() : 判斷此Map中“鍵-值”對的個數(shù)是否為0 · boolean containsKey(Object key) : 測試此Map中是否有該key · boolean containsValue(Object value) : 測試此Map中是否包含該value · V get(Object key) : 通過指定的key查詢Map中對應(yīng)的value · Collection<Object value> values() : 取得Map中所有的value · Set<Object key> keySet() : 取得當前Map中key的集合 · Set<Entry<K, V>> entrySet() : 取得當前Map中entry的集合 HashMap的特點,、實現(xiàn)機制及使用方法a)HashMap的特點:HashMap實現(xiàn)了Map,、CloneMap、Serializable三個接口,,并且繼承自AbstractMap類,。 b)HashMap的實現(xiàn)機制:HashMap基于hash數(shù)組實現(xiàn),若key的hash值相同則使用鏈表方式進行保存,,詳見HashSet中的說明,。我引用網(wǎng)上一個名詞叫“鏈表散列”來形容這樣的數(shù)據(jù)結(jié)構(gòu)。 新建一個HashMap時會初始化一個大小為16,,負載因子為0.75的空的HashMap,。 [java]
view plaincopy
那么Entry到底是怎么實現(xiàn)的呢? 1. static class Entry<K,V> implements Map.Entry<K,V> { 2. final K key; 3. V value; 4. Entry<K,V> next; 5. final int hash; 6. ...... 上面代碼其實告訴我們Entry是一個結(jié)點,,它持有下一個元素的引用,,這樣就構(gòu)成了一個鏈表。 那么,,整體上來說HashMap底層就是使用這樣一個數(shù)據(jù)結(jié)構(gòu)來實現(xiàn)的,。 我們提到使用Hash,但是Hash值如何與元素的存儲建立關(guān)系呢,?(Hash算法) 在數(shù)據(jù)結(jié)構(gòu)課中我們學(xué)習(xí)過Hash的簡單算法,,就是給你一個Hash因子,通過對該元素的hashCode簡單的求余,,來實現(xiàn)對其快速的定位和索引,。 在HashMap中有這樣的代碼: 1. /** 2. * Returns index for hash code h. 3. */ 4. static int indexFor(int h, int length) { 5. return h & (length-1); 6. } 這個方法在HashMap中非常重要,凡是與查詢,、添加,、刪除有關(guān)的方法中都有調(diào)用該方法,,為什么這么短的一個代碼使用率這么高?根據(jù)代碼注釋我們知道,,這個方法是根據(jù)hashCode及當前table的長度(數(shù)組的長度,,不是map的size)得到該元素應(yīng)該存放的位置,或者在table中的索引,。 現(xiàn)在我們需要看一下當數(shù)據(jù)量已經(jīng)超過初始定義的負載因子時,,HashMap如何處理? 在HashMap中當數(shù)據(jù)量很多時,,并且已經(jīng)達到了負載限度時,,會重新做一次哈希,也就是說會再散列,。調(diào)用的方法為resize(),,并且Java默認傳入的參數(shù)為2*table.length。先看一下JDK源碼: [java]
view plaincopy
看到這里我們會發(fā)現(xiàn)resize(再哈希)的工作量是不是很大啊,。再哈希是重新建一個指定容量的數(shù)組,,然后將每個元素重新計算它要放的位置,這個工作量確實是很大的,。 這里就產(chǎn)生了一個很重要的問題,那就是怎么讓哈希表的分布比較均勻,,也就是說怎么讓它即不會成為一個單鏈表(極限情況,,每個key的hash值都集中到了一起),又不會使hash空間過大(導(dǎo)致內(nèi)存浪費),? 上面兩個問題一個是解決了怎么計算hash值快速存取,,一個是怎么實現(xiàn)再哈希,何時需要再哈希,??焖俅嫒〉那疤崾窃胤植季鶆颍恢劣诩械揭稽c,,再哈希是元素過于零散,,導(dǎo)致不斷的重新構(gòu)建表。 那么在第一個問題中我們看到了這樣一個代碼return h & (length-1);在第二個問題中我們說過內(nèi)部調(diào)用傳入的值為2*table.length;并且默認情況下HashMap的大小為一個16的數(shù)字,,除了默認構(gòu)造提供大小為16的空間外,,如果我們使用 public HashMap(int initialCapacity,float loadFactor) 上面的構(gòu)造方法,我們會發(fā)現(xiàn)這樣的代碼: 1. // Find a power of 2 >= initialCapacity 2. int capacity = 1; 3. while (capacity < initialCapacity) 4. capacity <<= 1; 5. …… 6. table = new Entry[capacity]; 也就是說當我們傳入1000時,,它并沒有給我們構(gòu)造一個容量為1000的哈希表,,而是構(gòu)建了一個容量為1024大小的哈希表。 從整體上我們發(fā)現(xiàn)一個問題,,那就是無論什么情況HashMap中哈希表的容量總是2的n次方的一個數(shù),。并且有這樣一個公式: 當length=2^n時,hashcode& (length-1) == hashcode % length 也就是這一點驗證了第一個問題,hash索引值的計算方法其實就是對哈希因子求余,。只有大小為2的n次方時,,那樣的計算才成立,所以HashMap為我們維護了一個這樣大小的一個哈希表,。(位運算速度比取模運算快的多) c) HashMap的使用方法: 我在很多代碼中都用到了HashMap,,原因是首先它符合存儲關(guān)聯(lián)數(shù)據(jù)的要求,其次它的存取速度快,,這是一個選擇的問題,。 比較重要的是HashMap的遍歷方法,KeySet,EntrySet。 總結(jié):1. HashMap采用數(shù)組方式存儲key,,value構(gòu)成的Entry對象,,無容量限制。 2. HashMap基于key hash尋找Entry對象存放到數(shù)組的位置,,對于Hash沖突采用鏈表的方式來解決,。 3. HashMap在插入元素時可能要擴大數(shù)組的容量,擴大容量時對所有的數(shù)據(jù)要重新計算哈希和存放到新數(shù)組中,。當元素個數(shù)size大于threshold擴容threshold = (int)(newCapacity* loadFactor); 4. HashMap保證數(shù)組的大小為2的指數(shù)大小,。 5. HashMap非線程安全。 HashMap與Hashtable的區(qū)別:1. HashMap從java1.2后開始引進,。Hashtable從java1.0開始引入,。Hashtable一開始基于繼承陳舊的抽象類Dictionary實現(xiàn),后面也實現(xiàn)了Map接口。HashMap基于Map接口實現(xiàn),。 2. HashMap允許存放key為null,,value為null。對于key為null時,,HashMap首先獲取Entry數(shù)組中的第一個Entry對象,,并基于Entry對象的next遍歷鏈表,當找到其中Entry對象的key屬性為null時,,更新其value值,。如果沒有key屬性為null的Entry,則調(diào)用addEntry(int hash, K key, V value, int bucketIndex),,參數(shù)為0,null,value,0,,增加一個Entry對象,增加時先獲取當前數(shù)組的第一個Entry對象,,記為e,,然后創(chuàng)建一個key為null,value為傳入值得得Entry對象,,next為之前獲取的e,。數(shù)組的第一個Entry對象鏈表,,賦為新創(chuàng)建的Entry對象。由此,,addEntry鏈表倒序插入元素的,。Hashtable不允許key為null或value為null,否則會拋出NullPointerException,。這從put方法的源碼中很容易看出來,,置于為什么真么限制,不明白,? 3. HashMap是非線程安全,;Hashtable是線程安全的,其方法包含synchronized關(guān)鍵字實現(xiàn)線程安全,。 4. HashMap的計算hash值方法如下:首先對key取其hashCode,然后通過如下計算: h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h>>> 4); 最后取index時:return h &(length-1); Hashtable計算hash值,,直接去key的hashCode值,如下: int hash =key.hashCode(); 取index時:int index = (hash& 0x7FFFFFFF) % tab.length; 5. HashMap和Hashtable默認構(gòu)造時,,默認容量值不同,。 HashMap默認數(shù)組大小為16,加載因子為0.75,重新hash閾值為12. Hashtable默認數(shù)組大小為11,加載因子為0.75,,重新hash閾值為8. 6. HashMap和Hashtable的數(shù)組擴容方式不同,。 HashMap中的數(shù)組容量大小始終保證為2的指數(shù)。重新hash,擴充容量方式為,,當前容量大小*2. Hashtable擴充容量方式為:int newCapacity = oldCapacity * 2 + 1; 7. 一些成員方法不同,。 Hashtable包含一些舊的方法,如contains方法,。 面試中常會碰到。 LinkedHashMap的特點,、實現(xiàn)機制及使用方法a)LinkedHashMap的特點:LinkedHashMap繼承自HashMap并且實現(xiàn)了Map接口,。和HashMap一樣,LinkedHashMap允許key和value均為null,。 于該數(shù)據(jù)結(jié)構(gòu)和HashMap一樣使用到hash算法,,因此它不能保證映射的順序,尤其是不能保證順序持久不變(再哈希),。 如果你想在多線程中使用,,那么需要使用Collections.synchronizedMap方法進行外部同步,。 LinkedHashMap與HashMap的不同之處在于,,LinkedHashMap維護著運行于所有條目的雙向鏈接列表,此鏈接列表可以是插入順序或者訪問順序,。 b) LinkedHashMap的實現(xiàn)機制:
對于LinkedHashMap而言,,它繼承與HashMap,、底層使用哈希表與雙向鏈表來保存所有元素,。其基本操作與父類HashMap相似,它通過重寫父類相關(guān)的方法,,來實現(xiàn)自己的鏈接列表特性,。下面我們來分析LinkedHashMap的源代碼: 1)Entry元素:LinkedHashMap采用的hash算法和HashMap相同,但是它重新定義了數(shù)組中保存的元素Entry,,該Entry除了保存當前對象的引用外,,還保存了其上一個元素before和下一個元素after的引用,從而在哈希表的基礎(chǔ)上又構(gòu)成了雙向鏈接列表,??丛创a: Java代碼 1. /** 2. * 雙向鏈表的表頭元素。 3. */ 4. private transient Entry<K,V> header; 5. 6. /** 7. * LinkedHashMap的Entry元素,。 8. * 繼承HashMap的Entry元素,,又保存了其上一個元素before和下一個元素after的引用。 9. */ 10.private static class Entry<K,V> extends HashMap.Entry<K,V> { 11. Entry<K,V> before, after; 12. …… 13.} 2)初始化:通過源代碼可以看出,,在LinkedHashMap的構(gòu)造方法中,,實際調(diào)用了父類HashMap的相關(guān)構(gòu)造方法來構(gòu)造一個底層存放的table數(shù)組。如: Java代碼 1. public LinkedHashMap(int initialCapacity, float loadFactor) { 2. super(initialCapacity, loadFactor); 3. accessOrder = false; 4. } HashMap中的相關(guān)構(gòu)造方法: Java代碼 1. public HashMap(int initialCapacity, float loadFactor) { 2. if (initialCapacity < 0) 3. throw new IllegalArgumentException("Illegal initial capacity: " + 4. initialCapacity); 5. if (initialCapacity > MAXIMUM_CAPACITY) 6. initialCapacity = MAXIMUM_CAPACITY; 7. if (loadFactor <= 0 || Float.isNaN(loadFactor)) 8. throw new IllegalArgumentException("Illegal load factor: " + 9. loadFactor); 10. 11. // Find a power of 2 >= initialCapacity 12. int capacity = 1; 13. while (capacity < initialCapacity) 14. capacity <<= 1; 15. 16. this.loadFactor = loadFactor; 17. threshold = (int)(capacity * loadFactor); 18. table = new Entry[capacity]; 19. init(); 20.} 我們已經(jīng)知道LinkedHashMap的Entry元素繼承HashMap的Entry,,提供了雙向鏈表的功能,。在上述HashMap的構(gòu)造器 Java代碼 1. void init() { 2. header = new Entry<K,V>(-1, null, null, null); 3. header.before = header.after = header; 4. } 3)存儲:LinkedHashMap并未重寫父類HashMap的put方法,,而是重寫了父類HashMap的put方法調(diào)用的子方法void addEntry(int hash, Kkey, V value, int bucketIndex) 和voidcreateEntry(int hash, K key, V value, int bucketIndex),提供了自己特有的雙向鏈接列表的實現(xiàn),。 Java代碼 1. void addEntry(int hash, K key, V value, int bucketIndex) { 2. // 調(diào)用create方法,,將新元素以雙向鏈表的的形式加入到映射中。 3. createEntry(hash, key, value, bucketIndex); 4. 5. // 刪除最近最少使用元素的策略定義 6. Entry<K,V> eldest = header.after; 7. if (removeEldestEntry(eldest)) { 8. removeEntryForKey(eldest.key); 9. } else { 10. if (size >= threshold) 11. resize(2 * table.length); 12. } 13.} Java代碼 1. void createEntry(int hash, K key, V value, int bucketIndex) { 2. HashMap.Entry<K,V> old = table[bucketIndex]; 3. Entry<K,V> e = new Entry<K,V>(hash, key, value, old); 4. table[bucketIndex] = e; 5. // 調(diào)用元素的addBrefore方法,,將元素加入到哈希,、雙向鏈接列表。 6. e.addBefore(header); 7. size++; 8. } Java代碼 1. private void addBefore(Entry<K,V> existingEntry) { 2. after = existingEntry; 3. before = existingEntry.before; 4. before.after = this; 5. after.before = this; 6. } 4)讀?。?/span>LinkedHashMap重寫了父類HashMap的get方法,,實際在調(diào)用父類getEntry()方法取得查找的元素后,再判斷當排序模式accessOrder為true時,,記錄訪問順序,,將最新訪問的元素添加到雙向鏈表的表頭,,并從原來的位置刪除。由于的鏈表的增加,、刪除操作是常量級的,,故并不會帶來性能的損失。 Java代碼 1. public V get(Object key) { 2. // 調(diào)用父類HashMap的getEntry()方法,,取得要查找的元素,。 3. Entry<K,V> e = (Entry<K,V>)getEntry(key); 4. if (e == null) 5. return null; 6. // 記錄訪問順序。 7. e.recordAccess(this); 8. return e.value; 9. } Java代碼 1. void recordAccess(HashMap<K,V> m) { 2. LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m; 3. // 如果定義了LinkedHashMap的迭代順序為訪問順序,, 4. // 則刪除以前位置上的元素,,并將最新訪問的元素添加到鏈表表頭。 5. if (lm.accessOrder) { 6. lm.modCount++; 7. remove(); 8. addBefore(lm.header); 9. } 10.} 5)排序模式:LinkedHashMap定義了排序模式accessOrder,,該屬性為boolean型變量,,對于訪問順序,為true,;對于插入順序,,則為false。 Java代碼 1. private final boolean accessOrder; 一般情況下,,不必指定排序模式,,其迭代順序即為默認為插入順序??碙inkedHashMap的構(gòu)造方法,,如: Java代碼 1. public LinkedHashMap(int initialCapacity, float loadFactor) { 2. super(initialCapacity, loadFactor); 3. accessOrder = false; 4. } 這些構(gòu)造方法都會默認指定排序模式為插入順序。如果你想構(gòu)造一個LinkedHashMap,,并打算按從近期訪問最少到近期訪問最多的順序(即訪問順序)來保存元素,,那么請使用下面的構(gòu)造方法構(gòu)造LinkedHashMap: Java代碼 1. public LinkedHashMap(int initialCapacity, 2. float loadFactor, 3. boolean accessOrder) { 4. super(initialCapacity, loadFactor); 5. this.accessOrder = accessOrder; 6. } 該哈希映射的迭代順序就是最后訪問其條目的順序,這種映射很適合構(gòu)建LRU緩存,。LinkedHashMap提供了removeEldestEntry(Map.Entry<K,V>eldest)方法,,在將新條目插入到映射后,put和 putAll將調(diào)用此方法,。該方法可以提供在每次添加新條目時移除最舊條目的實現(xiàn)程序,默認返回false,,這樣,,此映射的行為將類似于正常映射,即永遠不能移除最舊的元素,。 Java代碼 1. protected boolean removeEldestEntry(Map.Entry<K,V> eldest) { 2. return false; 3. } 此方法通常不以任何方式修改映射,,相反允許映射在其返回值的指引下進行自我修改。如果用此映射構(gòu)建LRU緩存,,則非常方便,,它允許映射通過刪除舊條目來減少內(nèi)存損耗,。 Java代碼 1. private static final int MAX_ENTRIES = 100; 2. protected boolean removeEldestEntry(Map.Entry eldest) { 3. return size() > MAX_ENTRIES; 4. } 總結(jié):1. LinkedHashMap繼承于HashMap,此鏈接列表定義了迭代順序,,該迭代順序可以是插入順序或者是訪問順序(按從近期訪問最少到近期訪問最多的順序),。默認情況下按插入順序訪問,當accessOrder設(shè)置為true時,,按最后訪問順序遍歷數(shù)據(jù),。 2. LinkedHashMap非線程安全。 3. LinkedHashMap適合做LRU緩存,。 TreeMap的特點: TreeMap是一個支持排序的基于紅黑樹(Red-Black tree)的 SortedMap提供關(guān)于鍵的總體排序的Map。該映射是根據(jù)其鍵的自然順序進行排序的,,或者根據(jù)通常在創(chuàng)建有序映射時提供的 Comparator 進行排序,。對有序映射的 collection 視圖(由 entrySet、keySet 和 values 方法返回)進行迭代時,,此順序就會反映出來,。 插入SortedMap的所有鍵都必須實現(xiàn) Comparable 接口(或者被指定的比較器接受)。另外,,所有這些鍵都必須是可互相比較的:對有序映射中的任意兩個鍵 k1 和 k2 執(zhí)行 k1.compareTo(k2)(或 comparator.compare(k1, k2))都不得拋出 ClassCastException,。試圖違反此限制將導(dǎo)致違反規(guī)則的方法或者構(gòu)造方法調(diào)用拋出 ClassCastException。 所有通用SortedMap實現(xiàn)類都應(yīng)該提供 4 個“標準”構(gòu)造方法: 1) void(無參數(shù))構(gòu)造方法,,它創(chuàng)建一個空的有序映射,,按照鍵的自然順序(實現(xiàn)Comparable接口,方法compareTo為自然比較方法,,按此方法比較大小排序)進行排序,。 2) 帶有一個 Comparator 類型參數(shù)的構(gòu)造方法,它創(chuàng)建一個空的有序映射,,根據(jù)指定的比較器進行排序,。 3) 帶有一個 Map 類型參數(shù)的構(gòu)造方法,它創(chuàng)建一個新的有序映射,,其鍵-值映射關(guān)系與參數(shù)相同,,按照鍵的自然順序進行排序,。 4) 帶有一個 SortedMap 類型參數(shù)的構(gòu)造方法,它創(chuàng)建一個新的有序映射,,其鍵-值映射關(guān)系和排序方法與輸入的有序映射相同,。無法保證強制實施此建議,因為接口不能包含構(gòu)造方法,。 SortedMap新增了一些成員方法,,如下圖所示: NavigableMap<K,V>接口,擴展自 SortedMap,,具有了針對給定搜索目標返回最接近匹配項的導(dǎo)航方法,。方法 lowerEntry、floorEntry,、ceilingEntry 和 higherEntry 分別返回與小于,、小于等于、大于等于,、大于給定鍵的鍵關(guān)聯(lián)的 Map.Entry 對象,,如果不存在這樣的鍵,則返回 null,。類似地,,方法 lowerKey、floorKey,、ceilingKey 和 higherKey 只返回關(guān)聯(lián)的鍵,。所有這些方法是為查找條目而不是遍歷條目而設(shè)計的。 TreeMap實現(xiàn)機制:TreeMap()將comparator屬性賦值為null,。如果要控制TreeMap中元素的存儲順序,,應(yīng)使用帶Comparator參數(shù)的構(gòu)造器。 put(K,V)先判斷root是否為null,,如果為null,,則創(chuàng)建一個新的Entry對象,并賦值給root屬性,。否則,,首先判斷是否傳入了Compatator實現(xiàn),如果是,,則基于紅黑樹的方式遍歷,,直到為樹節(jié)點null,使用傳入的comparator比較Key的大小,,如果找到相等的key則更新其值,若沒有找到……,,如果comparator為null,,則判斷key是否為null,,如果是null,拋出NullPointerException,,對于key不是null時,,取key對于的Comparator進行紅黑樹的遍歷和比較,與上述類似,。 如果沒有找到相同的key,,則創(chuàng)建一個新的Entry對象,并將其parent設(shè)置為上面所尋找到的元素,,并根據(jù)和parent key比較的情況設(shè)置parent的left和right屬性,。最后對紅黑樹進行調(diào)整。 ConcurrentHashMap的特點,,實現(xiàn)原理:ConcurrentHashMap是線程安全的HashMap實現(xiàn),。支持獲取,查找時完全并發(fā)和更新時可調(diào)整并發(fā)的哈希表,。獲取操作(包括 get)通常不會受阻塞,。并發(fā)場景中(多個線程的情況下) ConcurrentHashMap比HashMap優(yōu)秀很多。 |
|
來自: Dragon_chen > 《Java》