Java集合之Map和Set源碼分析

之前就知道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>());
    }
相關文章
相關標籤/搜索