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

9,709次阅读
没有评论

共计 2970 个字符,预计需要花费 8 分钟才能阅读完成。

在 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 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 = hugeCapacity(minCapacity);
 
  // 扩容并赋值给原数组
  elementData = Arrays.copyOf(elementData, newCapacity);
}

总结

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

1698630578111788

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

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

    正文完
     0
    Yojack
    版权声明:本篇文章由 Yojack 于2024-09-20发表,共计2970字。
    转载说明:
    1 本网站名称:优杰开发笔记
    2 本站永久网址:https://yojack.cn
    3 本网站的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系站长进行删除处理。
    4 本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
    5 本站所有内容均可转载及分享, 但请注明出处
    6 我们始终尊重原创作者的版权,所有文章在发布时,均尽可能注明出处与作者。
    7 站长邮箱:laylwenl@gmail.com
    评论(没有评论)