HashMap和ArrayList如何擴容

來源:互聯網
上載者:User

Java中最長用的是HashMap和ArrayList兩種集合,這裡介紹他們是如何擴容的。 HashMap 初始化

1.直接初始化

/**     * Constructs an empty <tt>HashMap</tt> with the default initial capacity     * (16) and the default load factor (0.75).     */    public HashMap() {        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);}
2.設定參數初始化
/**     * Constructs an empty <tt>HashMap</tt> with the specified initial     * capacity and load factor.     *     * @param  initialCapacity the initial capacity     * @param  loadFactor      the load factor     * @throws IllegalArgumentException if the initial capacity is negative     *         or the load factor is nonpositive     */    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);        this.loadFactor = loadFactor;        threshold = initialCapacity;        init();}
3.用Map初始化
/**     * Constructs a new <tt>HashMap</tt> with the same mappings as the     * specified <tt>Map</tt>.  The <tt>HashMap</tt> is created with     * default load factor (0.75) and an initial capacity sufficient to     * hold the mappings in the specified <tt>Map</tt>.     *     * @param   m the map whose mappings are to be placed in this map     * @throws  NullPointerException if the specified map is null     */    public HashMap(Map<? extends K, ? extends V> m) {        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,                      DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);        inflateTable(this.threshold);        putAllForCreate(m);    }
,負載因子是0.75。

注意:m.size() / DEFAULT_LOAD_FACTO,再看下面的代碼:

   /**    * Inflates the table.    */   private void inflateTable(int toSize) {       // Find a power of 2 >= toSize       int capacity = roundUpToPowerOf2(toSize);       threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);       table = new Entry[capacity];       initHashSeedAsNeeded(capacity);   }
所以,桶的個數是initCapcity,而閾值是 capacity * this.loadFactor。表示閾值只有桶個數*loadFactor,那麼痛不會滿。同時,看下面添加可知,元素個數在添加過程中,也不會多餘桶個數*loadFactor。

roundUpToPowerOf2其實就是取比這個值(initCapcity)大的第一個2的整數冪。

上面的代碼在put方法中也有使用。 添加

/**     * Associates the specified value with the specified key in this map.     * If the map previously contained a mapping for the key, the old     * value is replaced.     *     * @param key key with which the specified value is to be associated     * @param value value to be associated with the specified key     * @return the previous value associated with <tt>key</tt>, or     *         <tt>null</tt> if there was no mapping for <tt>key</tt>.     *         (A <tt>null</tt> return can also indicate that the map     *         previously associated <tt>null</tt> with <tt>key</tt>.)     */    public V put(K key, V value) {        if (table == EMPTY_TABLE) {            inflateTable(threshold);        }        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;    }
最終的添加是調用addEntry操作的

/**     * Adds a new entry with the specified key, value and hash code to     * the specified bucket.  It is the responsibility of this     * method to resize the table if appropriate.     *     * Subclass overrides this to alter the behavior of put method.     */    void addEntry(int hash, K key, V value, int bucketIndex) {        if ((size >= threshold) && (null != table[bucketIndex])) {            resize(2 * table.length);            hash = (null != key) ? hash(key) : 0;            bucketIndex = indexFor(hash, table.length);        }        createEntry(hash, key, value, bucketIndex);    }
這裡可以看到,HashMap是在元素個數(不是桶的個數)達到閾值的情況下,將HashMap的的桶數翻倍的。

/**     * Rehashes the contents of this map into a new array with a     * larger capacity.  This method is called automatically when the     * number of keys in this map reaches its threshold.     *     * If current capacity is MAXIMUM_CAPACITY, this method does not     * resize the map, but sets threshold to Integer.MAX_VALUE.     * This has the effect of preventing future calls.     *     * @param newCapacity the new capacity, MUST be a power of two;     *        must be greater than current capacity unless current     *        capacity is MAXIMUM_CAPACITY (in which case value     *        is irrelevant).     */    void resize(int newCapacity) {        Entry[] oldTable = table;        int oldCapacity = oldTable.length;        if (oldCapacity == MAXIMUM_CAPACITY) {            threshold = Integer.MAX_VALUE;            return;        }        Entry[] newTable = new Entry[newCapacity];        transfer(newTable, initHashSeedAsNeeded(newCapacity));        table = newTable;        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);    }
void transfer(Entry[] paramArrayOfEntry, boolean paramBoolean) {int i = paramArrayOfEntry.length;Entry[] arrayOfEntry = this.table;int j = arrayOfEntry.length;for (int k = 0; k < j; ++k) {Entry localEntry;for (Object localObject = arrayOfEntry[k]; null != localObject; localObject = localEntry) {localEntry = ((Entry) localObject).next;if (paramBoolean)((Entry) localObject).hash = ((null == ((Entry) localObject).key) ? 0: hash(((Entry) localObject).key));int l = indexFor(((Entry) localObject).hash, i);((Entry) localObject).next = paramArrayOfEntry[l];paramArrayOfEntry[l] = localObject;}}}
可以看出,擴容後,HashMap會重新將元素按照hash演算法插入新的桶中;所以平時寫代碼,要避免這種情況,如果能預Crowdsourced Security Testing道或者估計HashMap的大小,就分配足夠的空間。

void createEntry(int paramInt1, K paramK, V paramV, int paramInt2) {Entry localEntry = this.table[paramInt2];this.table[paramInt2] = new Entry(paramInt1, paramK, paramV, localEntry);this.size += 1;}

這裡順帶看一下hash演算法。

/**     * Retrieve object hash code and applies a supplemental hash function to the     * result hash, which defends against poor quality hash functions.  This is     * critical because HashMap uses power-of-two length hash tables, that     * otherwise encounter collisions for hashCodes that do not differ     * in lower bits. Note: Null keys always map to hash 0, thus index 0.     */    final int hash(Object k) {        int h = hashSeed;        if (0 != h && k instanceof String) {            return sun.misc.Hashing.stringHash32((String) k);        }        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);    }


刪除

    /**     * Removes and returns the entry associated with the specified key     * in the HashMap.  Returns null if the HashMap contains no mapping     * for this key.     */    final Entry<K,V> removeEntryForKey(Object key) {        if (size == 0) {            return null;        }        int hash = (key == null) ? 0 : hash(key);        int i = indexFor(hash, table.length);        Entry<K,V> prev = table[i];        Entry<K,V> e = prev;        while (e != null) {            Entry<K,V> next = e.next;            Object k;            if (e.hash == hash &&                ((k = e.key) == key || (key != null && key.equals(k)))) {                modCount++;                size--;                if (prev == e)                    table[i] = next;                else                    prev.next = next;                e.recordRemoval(this);                return e;            }            prev = e;            e = next;        }        return e;    }

可見刪除的時候,HashMap並沒resize。


ArrayList 初始化

/**     * Constructs a list containing the elements of the specified     * collection, in the order they are returned by the collection's     * iterator.     *     * @param c the collection whose elements are to be placed into this list     * @throws NullPointerException if the specified collection is null     */    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);    }
其中

/**     * Copies the specified array, truncating or padding with nulls (if necessary)     * so the copy has the specified length.  For all indices that are     * valid in both the original array and the copy, the two arrays will     * contain identical values.  For any indices that are valid in the     * copy but not the original, the copy will contain <tt>null</tt>.     * Such indices will exist if and only if the specified length     * is greater than that of the original array.     * The resulting array is of the class <tt>newType</tt>.     *     * @param original the array to be copied     * @param newLength the length of the copy to be returned     * @param newType the class of the copy to be returned     * @return a copy of the original array, truncated or padded with nulls     *     to obtain the specified length     * @throws NegativeArraySizeException if <tt>newLength</tt> is negative     * @throws NullPointerException if <tt>original</tt> is null     * @throws ArrayStoreException if an element copied from     *     <tt>original</tt> is not of a runtime type that can be stored in     *     an array of class <tt>newType</tt>     * @since 1.6     */    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {        T[] copy = ((Object)newType == (Object)Object[].class)            ? (T[]) new Object[newLength]            : (T[]) Array.newInstance(newType.getComponentType(), newLength);        System.arraycopy(original, 0, copy, 0,                         Math.min(original.length, newLength));        return copy;    }
最常用的初始化方法是

    /**     * Constructs an empty list with an initial capacity of ten.     */    public ArrayList() {        super();        this.elementData = EMPTY_ELEMENTDATA;    }
預設是10個大小的數組。

添加

    /**     * Appends the specified element to the end of this list.     *     * @param e element to be appended to this list     * @return <tt>true</tt> (as specified by {@link Collection#add})     */    public boolean add(E e) {        ensureCapacityInternal(size + 1);  // Increments modCount!!        elementData[size++] = e;        return true;    }
這是會調用 ensureCapacityInternal,作用是保證數組可以容納足夠元素
private void ensureCapacityInternal(int minCapacity) {       if (elementData == EMPTY_ELEMENTDATA) {           minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);       }        ensureExplicitCapacity(minCapacity);}private void ensureExplicitCapacity(int minCapacity) {       modCount++;        // overflow-conscious code       if (minCapacity - elementData.length > 0)       grow(minCapacity);}
增長方法在grow中。

/**     * Increases the capacity to ensure that it can hold at least the     * number of elements specified by the minimum capacity argument.     *     * @param minCapacity the desired minimum capacity     */    private void grow(int minCapacity) {        // overflow-conscious code        int oldCapacity = elementData.length;        int newCapacity = oldCapacity + (oldCapacity >> 1);        if (newCapacity - minCapacity < 0)            newCapacity = minCapacity;        if (newCapacity - MAX_ARRAY_SIZE > 0)            newCapacity = hugeCapacity(minCapacity);        // minCapacity is usually close to size, so this is a win:        elementData = Arrays.copyOf(elementData, newCapacity);    }
顯然,每次增長原來長度的一半。

刪除

    /**     * Removes the element at the specified position in this list.     * Shifts any subsequent elements to the left (subtracts one from their     * indices).     *     * @param index the index of the element to be removed     * @return the element that was removed from the list     * @throws IndexOutOfBoundsException {@inheritDoc}     */    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; // clear to let GC do its work        return oldValue;    }
刪除調用系統的複製功能,size會自動化佈建好。


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.