揭秘ArrayList初始容量与扩容机制——90%的人都不知道

孙尚香 2023-11-30 11:00:59 浏览数 (2243)
反馈

在Java编程中,ArrayList是一种常用的数据结构,它提供了便捷的动态数组功能。然而,了解ArrayList的内部机制对于优化代码性能和避免不必要的资源浪费至关重要。本文将深入探讨ArrayList的两个关键问题:初始容量和扩容机制。我们将揭示ArrayList的初始容量到底是0还是10,并详细解析ArrayList的扩容机制,包括何时触发扩容、扩容的策略以及如何提高代码的效率和性能。通过对ArrayList的深入了解,我们能够更好地理解和利用这一重要的数据结构,为我们的Java编程提供更强大的工具。

ArrayList的初始容量

ArrayList的初始容量是指创建一个空的ArrayList时,它内部数组的大小。这个大小会影响到ArrayList的内存占用和扩容次数。不同的版本的Java实现可能有不同的初始容量设置,但通常有两种方式来指定或修改它:

  • 使用无参构造函数创建一个默认的ArrayList,它的初始容量由实现决定。例如,在Java 1.6中,它的初始容量为10,而在Java 1.7和1.8中,它的初始容量为0,只有在添加第一个元素时才分配10个对象空间。

       源码如下:

// 默认容量大小
private static final int DEFAULT_CAPACITY = 10;

// 空数组
private static final Object[] EMPTY_ELEMENTDATA = {};

// 默认容量的数组对象
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// 存储元素的数组
transient Object[] elementData;

// 数组中元素个数,默认是0
private int size;

// 无参初始化,默认是空数组
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

// 有参初始化,指定容量大小
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        // 直接使用指定的容量大小
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
    }
}

  • 使用带有int参数的构造函数创建一个指定初始容量的ArrayList,这样可以根据预期的元素数量来优化内存分配和扩容次数。例如,如果我们知道要存储100个元素,我们可以这样创建一个ArrayList:

ArrayList<Integer> list = new ArrayList<>(100);

       这样就可以避免多次扩容的开销,同时也不会浪费太多的空间。

ArrayList的扩容机制

ArrayList的扩容机制是指当ArrayList的内部数组已经满了,无法再容纳新的元素时,它会自动创建一个更大的数组,并将原来的数组的内容复制到新的数组中,以实现动态增长的功能。这个过程会影响到ArrayList的性能和内存使用,因为数组的创建和复制都是耗时的操作,而且会产生额外的垃圾对象。

ArrayList的扩容策略也可能因为不同的Java实现而有所差异,但通常有以下几个特点:

  • 扩容的时机是在添加元素之前,检查当前的容量是否足够,如果不够,就进行扩容。例如,在Java 1.8中,ArrayList的add()方法的源码如下:

// 添加元素
public boolean add(E e) {
  // 确保数组容量够用,size是元素个数
  ensureCapacityInternal(size + 1);
  // 直接在下个位置赋值
  elementData[size++] = e;
  return true;
}

// 确保数组容量够用
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

// 计算所需最小容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
  	// 如果数组等于空数组,就设置默认容量为10
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

// 确保容量够用
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
  	// 如果所需最小容量大于数组长度,就进行扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

其中,ensureCapacityInternal方法会判断是否需要扩容,如果需要,就调用grow方法进行扩容。

  • 扩容的幅度是按照一定的比例增加,而不是固定的值,这样可以保证扩容的次数是对数级别的,而不是线性级别的,从而降低平均的时间复杂度。例如,在Java 1.8中,ArrayList的grow方法的源码如下:

// 扩容,就是把旧数据拷贝到新数组里面
private void grow(int minCapacity) {
  int oldCapacity = elementData.length;
  // 计算新数组的容量大小,是旧容量的1.5倍
  int newCapacity = oldCapacity + (oldCapacity >> 1);

  // 如果扩容后的容量小于最小容量,扩容后的容量就等于最小容量
  if (newCapacity - minCapacity < 0)
    newCapacity = minCapacity;

  // 如果扩容后的容量大于Integer的最大值,就用Integer最大值
  if (newCapacity - MAX_ARRAY_SIZE > 0)
    newCapacity = hugeCapacity(minCapacity);
 
  // 扩容并赋值给原数组
  elementData = Arrays.copyOf(elementData, newCapacity);
}

总结

ArrayList是一种灵活和高效的动态列表,它可以根据需要自动调整容量,以存储任意数量的元素。但是,它的初始容量和扩容机制也会影响到它的性能和内存使用,因此,在使用ArrayList时,我们应该根据实际的场景和需求,合理地选择或指定它的初始容量,以避免不必要的开销和浪费。

1698630578111788

如果你对Java工程师职业和编程技术感兴趣,不妨访问编程狮官网(https://www.w3cschool.cn/)。编程狮官网提供了大量的技术文章、编程教程和资源,涵盖了Java工程师、编程、职业规划等多个领域的知识。无论你是初学者还是有经验的开发者,编程狮官网都为你提供了有用的信息和资源,助你在编程领域取得成功。不要错过这个宝贵的学习机会!

0 人点赞