之前就知道Set和Map是java中的兩種集合,Set表明集合元素無序、不可重複的集合;Map是表明一種由多個key-value對組成的集合。而後兩個集合分別有增刪改查的方法。而後就迷迷糊糊地用着。忽然在一個風雨交加的夜晚,感受不能這樣迷迷糊糊,得深刻地去研究一下,因而去看了看源碼(jdk1.8)。html
1.Map源碼。java
/** * An object that maps keys to values. A map cannot contain duplicate keys; * each key can map to at most one value. *The Map interface provides three collection view, which * allow a map's contents to be viewed as a set of keys, collection of values, * or set of key-value mappings.
這是jdk源碼中的對map這個接口的描述,大概意思是說這是一個鍵值對映射的對象,一個map中不能包含重複的鍵,每個鍵最多映射一個值;map這個接口提供了三個集合視圖,一個是關於key的set集合,一個是關於value的collection集合,還有一個是關於key-value映射關係的set集合。分別是如下幾個集合對象。算法
Set<K> keySet();數組
Collection<V> values();數據結構
Set<Map.Entry<K, V>> entrySet();app
能夠很明顯地看出,map就是set的擴展。看了這個有什麼用呢?用途不少,更加深刻理解集合,你會被這些設計者(Josh Bloch)的思想所折服—固然這都比較扯淡。來點實際的,以上三種集合的大小都是同樣的,由於key-value是一一對應的,因此你有三種方式來遍歷map。這位兄臺已經進行實驗。http://www.2cto.com/kf/201212/179013.htmlide
public interface Map<K,V> { // Query Operations ...
這是jdk1.8中的Map接口的定義,能夠發現map並無繼承collection,可是我以前在網上看了好多都說map也繼承的collection,讓我百思不解。性能
2.Set源碼。this
public interface Set<E> extends Collection<E> { // Query Operations ...
set纔是真正地繼承了collection接口,map只是在set的基礎上的一個擴展。繼承collection的還有List;spa
/** * A collection that contains no duplicate elements. More formally, sets * contain no pair of elements <code>e1</code> and <code>e2</code> such that * <code>e1.equals(e2)</code>, and at most one null element. As implied by * its name, this interface models the mathematical <i>set</i> abstraction.
以上是源碼中對Set的描述 set是數學中的集合的概念,正如名字所暗示的同樣,java中的Set是對數學中set的抽象,Set中的元素是不能重複的,Set最多可含一個null元素;對於任意的非null元素e1和e2,都知足e1.equals(e2)==false. 而且在Set接口中,還有一些交集和並集的方法,如 addAll(Collection<? extends E> c); containsAll(Collection<?> c);
(雖然集合號稱存儲的是Java對象,但實際上並不會真正將Java對象放在集合中,而是在集合中保留對象的引用)
3.HashMap和hashSet
(1)HashMap
HashMap是Map的一個具體實現。HashMap其實是一個鏈表散列的數據結構,即數組和鏈表的結合體。HashMap的底層就是一個數組結構。
public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted }
以上是是HashMap中的默認的構造方法,設置了DEFAULT_LOAD_FACTOR=0.75f, 還有帶參數的構造方法,能夠設置負載因子(一種時間和空間成本上的折衷),增大負載因子能夠減小所佔內存的開銷,可是會增長查詢數據的時間開銷,get()和put()都會用到查詢。其餘的構造方法中還有一個參數initialCapacity,定義了一個默認的數值DEFAULT_INITIAL_CAPACITY = 1 << 4;結果就是16,一個hashMap初始的容量就是16,可是會動態地改變大小,這裏的initialCapacity不等於size()返回的值。
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } /** * Implements Map.put and related methods * * @param hash hash for key * @param key the key * @param value the value to put * @param onlyIfAbsent if true, don't change existing value * @param evict if false, the table is in creation mode. * @return previous value, or null if none */ 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; }
以上是hashMap中的對於put方法的描述,若是元素重複,則會保留key,替換value;剛剛在介紹Map時提到了Map.Entry這個東西,在hashMap中,Node<K,V>實現了這個接口(static class Node<K,V> implements Map.Entry<K,V> ); 每一個key-value都放在了Node<K,V>這個對象中,採用 Node<K,V>[] tab 數組的方式來保存key-value對;HashMap使用一種傳說中的「Hash算法」來肯定每一個元素的存儲位置, 調用key的hashCode()方法,經過返回值來肯定每一個元素的存儲位置。若是在數組的該位置上已經存放了其餘元素,那麼這裏的位置將以鏈表的形式存放。同理,get方法也是如此。
(2)HashSet如下是HashSet源碼中的構造方法:
/** * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has * default initial capacity (16) and load factor (0.75). */ public HashSet() { map = new HashMap<>(); }
一看到默認的構造方法就什麼都明白了,HashSet是基於HashMap實現的,只是封裝了HashMap,源碼中也是這樣描述的;在HashSet中也說明了initial capacity (16) and load factor (0.75). 初始的容量是16,默認的負載因子是0.75。
(3)treeMap
TreeMap中的元素也是存儲在一個Entry<K,V>中,可是底層是用一棵「紅黑樹」來保存Entry,所以,TreeMap添加元素、取出元素的性能比HashMap低。當須要添加元素時,要遍歷這棵二叉樹才能插入合適的位置,而HashMap是根據hashCode返回值來肯定Entry的存放位置,因此TreeMap存取元素比較消耗性能。但正由於如此,TreeMap也有本身的優點,TreeMap中的元素老是保持一種有序的狀態。
public static void main(String[] args) { Map map = new TreeMap(); map.put("9", 9); map.put("2", 2); map.put("1", 1); map.put("4", 4); Iterator it = map.keySet().iterator(); while (it.hasNext()) { System.out.println(map.get(it.next())); } }
//結果是:
1
2
4
9
(4)TreeSet
如下是TreeSet源碼中的構造方法,相似於HashSet,封裝了一個TreeMap,在TreeSet中元素也是有序的。
public TreeSet() { this(new TreeMap<E,Object>()); }