全文目录:
开篇语
今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。
我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,希望以这种方式帮助到更多的初学者或者想入门的小伙伴们,同时也能对自己的技术进行沉淀,加以复盘,查缺补漏。
小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持!
前言
在 Java 中,ArrayList
是最常见的动态数组实现之一,它为我们提供了快速的随机访问能力。在日常开发中,我们经常会用到 ArrayList
,但对它的底层实现和扩容机制的理解往往只是停留在表面。今天,我将带你深入剖析 ArrayList
的源码,探讨它如何高效地处理数据、扩容机制是如何工作的,以及它的优势和不足。
好了,动起来!带着好奇心一起“解锁” ArrayList
的奥秘吧!
1. ArrayList 源码基础
1.1 ArrayList 概述
ArrayList
是一个可变大小的数组实现,属于 List
接口的一个类。它内部使用一个数组来存储数据,并允许快速访问和修改元素。由于它是动态的,大小会根据元素的增加而自动扩展。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable {
private static final long serialVersionUID = 8683452581122892189L;
// 底层数组
transient Object[] elementData;
// ArrayList 中元素的个数
private int size;
// 空构造函数,初始化容量为 10
public ArrayList() {
this(10);
}
// 带初始容量的构造函数
public ArrayList(int initialCapacity) {
if (initialCapacity < 0) {
throw new IllegalArgumentException("Illegal Capacity: " +
initialCapacity);
}
this.elementData = new Object[initialCapacity];
}
// 获取元素数量
public int size() {
return size;
}
// 添加元素
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 确保容量足够
elementData[size++] = e;
return true;
}
// 确保容量足够
private void ensureCapacityInternal(int minCapacity) {
if (elementData.length < minCapacity)
grow(minCapacity);
}
// 扩容方法
private void grow(int minCapacity) {
// 计算新的容量
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5倍扩容
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 重新分配内存
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
1.2 核心成员变量
- elementData:这是一个
Object[]
数组,用来存储ArrayList
中的元素。elementData
在初始化时有一个默认的容量。 - size:
ArrayList
中当前元素的数量。注意,size
只是记录实际元素的数量,不代表数组的长度。
1.3 添加元素
ArrayList
的 add
方法会首先调用 ensureCapacityInternal
来确保数组的容量。如果容量不足,就会调用 grow
方法进行扩容。
2. ArrayList 扩容机制揭秘
2.1 扩容的时机
ArrayList
在以下两种情况下会进行扩容:
- 添加元素时容量不足:当你向
ArrayList
中添加元素,但元素数组的容量已满时,ArrayList
会自动扩容。 - 手动调整容量:你可以通过
ensureCapacity
方法提前扩容,避免在添加元素时频繁扩容。
2.2 扩容的具体实现
扩容的逻辑在 grow
方法中实现,下面是扩容的关键点:
oldCapacity >> 1
:这部分是右移运算,相当于除以 2。意思是,新容量是原容量的 1.5 倍。Arrays.copyOf(elementData, newCapacity)
:这是对原数组的复制,扩容后数据会被复制到新数组。
private void grow(int minCapacity) {
// 获取当前容量
int oldCapacity = elementData.length;
// 扩容1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果新容量仍然不足,使用minCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 复制数据到新数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
2.3 扩容的例子
假设初始容量是 10,当添加第 11 个元素时,ArrayList
会扩容到 15(1.5倍扩容)。然后,如果再添加第 16 个元素,容量将会被扩展到 22(1.5倍扩容)。这种扩容的方式能保证性能不会受到过度扩容的影响。
扩容过程:
- 初始容量:10
- 第一次扩容:10 * 1.5 = 15
- 第二次扩容:15 * 1.5 ≈ 22
- 第三次扩容:22 * 1.5 ≈ 33
这样,每次扩容后,ArrayList
的容量都能够有效满足新的元素添加需求,同时避免了频繁扩容带来的性能问题。
3. 进一步优化:确保容量
在某些场景中,我们知道 ArrayList
需要存储一定数量的元素。此时可以通过 ensureCapacity
方法提前调整容量,避免频繁扩容带来的性能损耗。
3.1 ensureCapacity 方法
ensureCapacity
方法允许你手动设置 ArrayList
的容量,以减少扩容的次数。
public void ensureCapacity(int minCapacity) {
// 容量调整逻辑
int minExpand = (elementData != EMPTY_ELEMENTDATA) ? 0 : DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureCapacityInternal(minCapacity);
}
}
通过这种方式,你可以确保 ArrayList
在添加大量数据时,避免多次扩容导致性能问题。
4. 内存消耗与性能分析
4.1 内存消耗
ArrayList
会根据容量动态分配内存,而扩容机制的核心是按 1.5 倍增长容量,这样可以在提高性能的同时,避免内存浪费。
但是如果元素数量比较少,ArrayList
会浪费部分内存。比如,如果你仅存储 10 个元素,但 ArrayList
扩容到 100 时,就会有 90 个空闲空间。这种内存浪费是不可避免的,但通过合理的容量控制,可以减小这种浪费。
4.2 性能分析
- 添加元素:当容量足够时,
add
操作的时间复杂度是 O(1),但如果发生扩容,扩容时的时间复杂度为 O(n)。 - 查询和更新:由于
ArrayList
是基于数组实现的,所以查询和更新操作的时间复杂度为 O(1),非常高效。 - 删除元素:删除操作需要移动数组中的元素,时间复杂度为 O(n)。
5. 总结
ArrayList
是一个高效的动态数组实现,通过扩容机制,它能够平衡内存使用和性能。当数据量较少时,ArrayList
会保持较小的内存占用,但随着数据量的增长,它会自动扩容,保证操作的高效性。理解 ArrayList
的扩容机制,可以帮助我们在实际开发中做出更加精准的性能优化,避免不必要的扩容和内存浪费。
如果你还对其他数据结构或算法有兴趣,记得持续关注!
… …
文末
好啦,以上就是我这期的全部内容,如果有任何疑问,欢迎下方留言哦,咱们下期见。
… …
学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!
wished for you successed !!!
⭐️若喜欢我,就请关注我叭。
⭐️若对您有用,就请点赞叭。
⭐️若有疑问,就请评论留言告诉我叭。
版权声明:本文由作者原创,转载请注明出处,谢谢支持!