ArrayList扩容机制源码及测试

Java SE APIs

1时间复杂度

2扩容机制

3非线程安全

详情(结合部分源码)

1时间复杂度

The sizeisEmptygetsetiterator, and listIterator operations run in constant time. The add operation runs in amortized constant time, that is, adding n elements requires O(n) time. All of the other operations run in linear time (roughly speaking). The constant factor is low compared to that for the LinkedList implementation.

Each ArrayList instance has a capacity. The capacity is the size of the array used to store the elements in the list. It is always at least as large as the list size. As elements are added to an ArrayList, its capacity grows automatically. The details of the growth policy are not specified beyond the fact that adding an element has constant amortized time cost.

最后一句话是指在ArrayList中的增长策略(即动态扩容机制)没有具体的规定,除了一个重要的事实,即添加元素的摊销时间成本是常数。在Java的ArrayList实现中,虽然具体的增长策略没有公开规定,但是有一些常见的实践:

  1. 增长因子: 通常,ArrayList会选择一个适当的增长因子来决定何时扩容。增长因子表示内部数组的大小将如何增加。常见的增长因子是1.5,即当需要扩容时,新数组的大小将是当前数组大小的1.5倍。这是为了减少频繁的扩容操作,同时避免浪费大量的内存。
  2. 扩容操作: 当需要扩容时,ArrayList会创建一个新的内部数组,通常是当前数组大小的1.5倍。然后,它会将所有元素从旧数组复制到新数组,并更新内部引用,使其指向新数组。旧数组会被垃圾回收。这种方式使得添加元素的摊销时间成本是常数。

虽然具体的增长策略没有被硬性规定,但上述常见实践通常是在ArrayList的不同Java版本中使用的。这种增长策略旨在平衡内存效率和性能。在实际使用ArrayList时,你通常无需担心增长策略的细节,因为Java的ArrayList会自动处理扩容操作,以确保它能够有效地处理不同规模的数据集合。

2扩容机制

初始容量

/**
 * Default initial capacity.
 */
private static final int DEFAULT_CAPACITY = 10;

添加元素

private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length)
        elementData = grow();
    elementData[s] = e;
    size = s + 1;
}

public boolean add(E e) {
    modCount++;
    add(e, elementData, size);
    return true;
}

扩容(总之涉及Arrays.copyOf

private Object[] grow(int minCapacity) {
    int oldCapacity = elementData.length;
    if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        int newCapacity = ArraysSupport.newLength(oldCapacity,
                minCapacity - oldCapacity, /* minimum growth */
                oldCapacity >> 1           /* preferred growth */);
        return elementData = Arrays.copyOf(elementData, newCapacity);
    } else {
        return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
    }
}

private Object[] grow() {
    return grow(size + 1);
}

3非线程安全

Note that this implementation is not synchronized. If multiple threads access an ArrayList instance concurrently, and at least one of the threads modifies the list structurally, it must be synchronized externally. (A structural modification is any operation that adds or deletes one or more elements, or explicitly resizes the backing array; merely setting the value of an element is not a structural modification.) This is typically accomplished by synchronizing on some object that naturally encapsulates the list. If no such object exists, the list should be "wrapped" using the Collections.synchronizedList method. This is best done at creation time, to prevent accidental unsynchronized access to the list:

   List list = Collections.synchronizedList(new ArrayList(...));

扩容机制实验

import java.util.ArrayList;

public class ArrayListResizeTest {
    public static void main(String[] args) {
        int initialCapacity = 10; // 初始容量
        int numElementsToAdd = 1000000; // 要添加的元素数量

        // 创建一个ArrayList并设置初始容量
        ArrayList<Integer> arrayList = new ArrayList<>(initialCapacity);

        long startTime = System.currentTimeMillis();

        // 添加大量元素
        for (int i = 0; i < numElementsToAdd; i++) {
            arrayList.add(i);
        }

        long endTime = System.currentTimeMillis();
        long duration = endTime - startTime;

        System.out.println("元素数量: " + numElementsToAdd);
        System.out.println("初始容量: " + initialCapacity);
        System.out.println("实际容量: " + arrayList.size());
        System.out.println("添加元素耗时: " + duration + " 毫秒");
    }
}

结果1:

元素数量: 1000000
初始容量: 10
实际容量: 1000000
添加元素耗时: 86 毫秒

结果2:

元素数量: 1000000
初始容量: 1000000
实际容量: 1000000
添加元素耗时: 44 毫秒

posted @ 2023-10-25 00:33  xkfx  阅读(8)  评论(0编辑  收藏  举报