HashMap和ArrayList如何扩容

来源:互联网 发布:看盘软件哪个最好 编辑:程序博客网 时间:2024/06/05 05:13

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算法插入新的桶中;所以平时写代码,要避免这种情况,如果能预先知道或者估计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会自动设置好。


1 0
原创粉丝点击