Java集合源碼分析

工做了3年多,一直都沒花心思去看看jdk經常使用類的源碼。之前大學的數據結構和算法課程在剛接觸java時候感受好像無用武之地。像c語言直接使用基礎類型,可能須要去實現鏈表,棧,隊列等。jdk都已經提供了實現類。趁最近有時間看看源碼實現。java

1.ArrayList

成員屬性

elementData:數據存儲的數組,任何操做都是基於這個數組。
size:集合如今實際的大小。爲何不直接使用elementData.length,若是直接使用elementData,每次插入,刪除都要操做數組的空間(代價大),因此elementData並不是每一個空間都實際用到,所以不能使用length表示大小。(後面的其餘集合對象也都是同樣的道理)node

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;

    private transient Object[] elementData;

    private int size;
初始化

1.默認數組長度10
2.指定數組長度
3.傳入現用集合複製的數組算法

public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
    }
    
    public ArrayList() {
        this(10);
    }
    
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        size = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    }
操做

get:判斷座標是否超出SIZE,並不是數組長度,不過只判斷是否超出,沒有判斷是否小於0,這點也奇怪。而後返回讀取數組的數據。
set:同get,判斷座標,替換數組中的元素。
add(E e):判斷當前大小是否超出數組長度或者小於0,(這裏又判斷了),超出進行擴容grow(int minCapacity),擴容大小->原來長度位移1位,實際就是*1.5,而後就是往數組[size]賦值
add(int index, E element):同上,檢查位置,不過區別在於指定座標,就要把座標以後的元素所有後移一位(這就是和LinkedList的效率差異,花銷太大),使用System.arraycopy移動,最後同樣賦值。
remove:同上,檢查位置,判斷是否最後一個元素,是的狀況,置空最後一個元素。否的狀況,同樣進行位移,把座標後面的元素所有前移一位(花銷大)。數組

public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }
    
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // Let gc do its work

        return oldValue;
    }

額外說明:這裏限制數組的最大長度使用了-8,按說明是爲了給vms保留字頭安全

/**
     * The maximum size of array to allocate.
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

2.LinkedList

成員屬性

first,last:記錄鏈表的第一個和最後一個元素,Node雙向鏈表(next,prev)
size:集合實際大小,node的個數數據結構

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    transient int size = 0;
    
    transient Node<E> first;
 
    transient Node<E> last;

    private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
操做

get:判斷位置是否超出size和小於0(ArrayList就沒判斷小於0),而後計算index是在size/2的前面仍是後面,若是前面就使用first進行一個個遍歷,若是大於就使用last遍歷(相似二分查找,這裏的相對於Arraylist直接使用數組座標查找的開銷就大不少)
set:同get操做獲取node,把node的值賦成新的element。(與ArrayList相同)
add(E e):直接在尾部追加,newNode.pre = last, last.next=newNode, last=newNode;(數據結構鏈表添加)
add(int index, E element):判斷位置合法性,判斷index==size,知足就直接使用last進行追加,不知足就進行查找,添加Node進行插入(newNode在前面)
remove:判斷位置合法,查找node,node.next和node.prev(node.prev.next=node.next,node.next.prev=node.prev,代碼實現還須要一些額外判斷)app

public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }
    
    public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }
    
    public boolean add(E e) {
        linkLast(e);
        return true;
    }
    
    public void add(int index, E element) {
        checkPositionIndex(index);

        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }

    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }

兩個List對比,其實和數據結構課程概括相似:ArrayList勝在get,LinkedList勝在add,remove。不過查看代碼在add(index,e),也須要使用get,這裏可能就涉及到到底查找開銷大仍是數組位移開銷大。dom

3.HashMap

因爲HashMap涉及到hash表的構造,以及哈希衝突等之前數據結構學過的知識,就先把幾種解決哈希衝突的方法說一下
1.開放定址法(線性探測再散列,二次探測再散列,僞隨機探測再散列fi(key) = (f(key)+di) MOD m (di=1,2,3,......,m-1))
2.再哈希法(有多個不一樣的Hash函數)
3.鏈地址法(HashMap使用這種)
4.創建一個公共溢出區數據結構和算法

成員屬性

DEFAULT_INITIAL_CAPACITY :默認容量
MAXIMUM_CAPACITY :最大容量
DEFAULT_LOAD_FACTOR:加載因子,是一個比例,當HashMap的數據大小>=容量*加載因子時,HashMap會將容量擴容(加載因子:防止衝突過多,若是加載因子太大,致使容量一直沒有擴容,元素的衝突就很是多)
table :存儲數據的數組
size:大小(同ArrayList的size)函數

public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable
{

    static final int DEFAULT_INITIAL_CAPACITY = 16;

    static final int MAXIMUM_CAPACITY = 1 << 30;

    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    transient Entry<K,V>[] table;

    transient int size;
初始化

1.直接使用默認16大小,0.75加載因子初始化
2.指定大小,默認加載因子
3.指定大小,指定加載因子

順便提一個大小的問題:咱們傳入10,初始化後大小也不會是10,在 while (capacity < initialCapacity) capacity <<= 1;經過一直位移來肯定大小,因此傳入10最後會是16。

public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

        // Find a power of 2 >= initialCapacity
        int capacity = 1;
        while (capacity < initialCapacity)
            capacity <<= 1;

        this.loadFactor = loadFactor;
        threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        table = new Entry[capacity];
        useAltHashing = sun.misc.VM.isBooted() &&
                (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        init();
    }
   
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
   
    public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }
操做

get:null特殊處理,存儲位置0。其餘計算key對應的hash值(hash算法能夠研究一下),而後和table長度取餘數,最後遍歷這個座標下面的鏈表(解決哈希衝突)進行hash值,key值比較,獲得value。
put:計算hash,計算在數組的位置(同get操做),查看該位置是否存在改key,有就替換value,沒有就新增entry(在新增的時候會判斷是否擴容,原來2倍,transfer:從新計算hash的存儲位置),每次擴容都要從新計算所有元素hash,開銷很大,因此恰當的加載因子很是重要
remove:找出座標(同get操做),遍歷鏈表找出元素,而後就是單鏈表刪除操做(pre.next=next)。

final int hash(Object k) {
        int h = 0;
        if (useAltHashing) {
            if (k instanceof String) {
                return sun.misc.Hashing.stringHash32((String) k);
            }
            h = hashSeed;
        }

        h ^= k.hashCode();

        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
    
    public V get(Object key) {
        if (key == null)
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);

        return null == entry ? null : entry.getValue();
    }
    
    public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }
    
    public V remove(Object key) {
        Entry<K,V> e = removeEntryForKey(key);
        return (e == null ? null : e.value);
    }
額外說明

indexFor:獲得hash值後與長度作一個&運算,爲何會在前面說成取餘數了?在前面說過table的長度只會是2的倍數,-1以後剩餘的二進制全是1,這時候&運算就變成和取餘數如出一轍。不少地方的位運算都很神奇。

static int indexFor(int h, int length) {
        return h & (length-1);
    }

4.HashTable

HashTable於HashMap做比較說明

成員屬性

loadFactor:加載因子(同HashMap)
table :存儲數據的數組
count:大小(同HashMap的size)

public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable {

    private transient Entry<K,V>[] table;

    private transient int count;

    private float loadFactor;
初始化

默認加載因子:0.75(HashMap相同)
默認大小:11(HashMap:16)
計算實際大小也沒有像HashMap那樣位移計算直接根據傳入的長度進行初始化。

public Hashtable(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal Load: "+loadFactor);

        if (initialCapacity==0)
            initialCapacity = 1;
        this.loadFactor = loadFactor;
        table = new Entry[initialCapacity];
        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        useAltHashing = sun.misc.VM.isBooted() &&
                (initialCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
    }

    public Hashtable(int initialCapacity) {
        this(initialCapacity, 0.75f);
    }

    public Hashtable() {
        this(11, 0.75f);
    }

    public Hashtable(Map<? extends K, ? extends V> t) {
        this(Math.max(2*t.size(), 11), 0.75f);
        putAll(t);
    }
操做(方法加了synchronized保證線程安全)

get:計算hash值(在useAltHashing上的處理與HashMap有區別),直接與長度求餘數(HashMap經過&運算),遍歷鏈表,判斷key和hash值,返回value(基本等同)。少了null的特殊判斷(不支持put.null)
put:value空異常判斷,key若是爲null在hash(key)也會拋出異常(不支持key,value爲null)。計算hash值,遍歷鏈表,若是存在就替換value,返回。不存在,新增entry(在新增的時候會判斷是否擴容rehash: newCapacity = (oldCapacity << 1) + 1;)(基本等同HashMap)
remove:計算hash值(等同get),移除元素,也是鏈表移除操做(等同HashMap)

private int hash(Object k) {
        if (useAltHashing) {
            if (k.getClass() == String.class) {
                return sun.misc.Hashing.stringHash32((String) k);
            } else {
                int h = hashSeed ^ k.hashCode();

                // This function ensures that hashCodes that differ only by
                // constant multiples at each bit position have a bounded
                // number of collisions (approximately 8 at default load factor).
                h ^= (h >>> 20) ^ (h >>> 12);
                return h ^ (h >>> 7) ^ (h >>> 4);
             }
        } else  {
            return k.hashCode();
        }
    }
    
    public synchronized V get(Object key) {
        Entry tab[] = table;
        int hash = hash(key);
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                return e.value;
            }
        }
        return null;
    }
    
    public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry tab[] = table;
        int hash = hash(key);
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                V old = e.value;
                e.value = value;
                return old;
            }
        }

        modCount++;
        if (count >= threshold) {
            // Rehash the table if the threshold is exceeded
            rehash();

            tab = table;
            hash = hash(key);
            index = (hash & 0x7FFFFFFF) % tab.length;
        }

        // Creates the new entry.
        Entry<K,V> e = tab[index];
        tab[index] = new Entry<>(hash, key, value, e);
        count++;
        return null;
    }
    
    public synchronized V remove(Object key) {
        Entry tab[] = table;
        int hash = hash(key);
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<K,V> e = tab[index], prev = null ; e != null ; prev = e, e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                modCount++;
                if (prev != null) {
                    prev.next = e.next;
                } else {
                    tab[index] = e.next;
                }
                count--;
                V oldValue = e.value;
                e.value = null;
                return oldValue;
            }
        }
        return null;
    }

總的來講HashTable:不容許null鍵值,方法都加了synchronized,默認長度11,計算hash (hash & 0x7FFFFFFF) % tab.length;
不過如今有時候爲了保證HashMap線程安全直接使用Collections.synchronizedMap(m),比較少使用HashTable。

5.HashSet

HashSet是Collection接口的子類,HashMap是Map接口的子類,二者在繼承上沒有關聯,可是內部又使用。

成員屬性

map:使用HashMap,說明全部操做都是基於HashMap,也就能夠存儲null值
PRESENT:定義了一個Object實體來當作每次操做HashMap的value值

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
    static final long serialVersionUID = -5024744406713321676L;

    private transient HashMap<E,Object> map;

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();
初始化

所有基於HashMap的初始化方法,提供一一對應的初始化

public HashSet() {
        map = new HashMap<>();
    }

    public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }

    public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }

    public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }
操做

所有基於HashMap的操做方法,進行操做。

public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

   
    public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }

主要的幾個經常使用的Collection,Map都大概看了源碼,其實內部實現都是之前數據結構上學過的那些經常使用的數據結構,不過在看源碼的時候發現不少地方都是使用了位運算,畢竟位運算效率高,並且不少地方都儘可能契合二進制的計算規律。因此在看源碼的時候,能夠重點抓住:數據結構的使用,位運算的使用。

相關文章
相關標籤/搜索