深入理解ArrayList:数据结构、源码分析与常见面试题
引言
ArrayList
是 Java 中最常用的集合类之一,它基于动态数组实现,提供了高效的随机访问和动态扩容能力。本文将深入探讨 ArrayList
的数据结构、源码实现以及常见的面试题,帮助你更好地理解和使用它。
一、数据结构:数组
数组的下标为什么从0开始?
数组的选址地址公式为:
baseAddr + i * typeSize
其中:
baseAddr
是数组的起始地址。i
是数组下标。typeSize
是每个元素占用的字节数。
如果数组下标从1开始,公式将变为:
baseAddr + (i - 1) * typeSize
这意味着 CPU 需要额外执行一个减法操作,增加了计算开销。而下标从0开始可以避免这种额外的计算,从而提高效率。
二、ArrayList源码分析
1. 成员变量
ArrayList
的核心成员变量如下:
java
// 默认初始容量
private static final int DEFAULT_CAPACITY = 10;
// 用于空实例的共享空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// 用于默认大小空实例的共享空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 存储ArrayList元素的数组缓冲区
transient Object[] elementData; // 非私有,以简化嵌套类访问
// ArrayList的大小(包含的元素数量)
private int size;
2. 构造方法
ArrayList
提供了三种构造方法:
(1) 指定初始容量
java
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);
}
}
(2) 默认构造方法(初始容量为10)
java
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
(3) 通过集合初始化
java
public ArrayList(Collection<? extends E> c) {
Object[] a = c.toArray();
if ((size = a.length) != 0) {
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
elementData = EMPTY_ELEMENTDATA;
}
}
3. 添加与扩容机制
ArrayList
的添加操作涉及三种情况:
- 第一次添加元素:将容量从0扩容到默认值10。
- 第2到10次添加:直接添加元素,无需扩容。
- 第11次及以后添加:当容量不足时,扩容为原来的1.5倍,并进行数组拷贝。
扩容的核心逻辑在 grow
方法中实现:
java
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. ArrayList底层实现的原理?
ArrayList
底层采用动态数组实现。初始化时容量为0,首次添加元素时容量会初始化为10。当容量不足时,ArrayList
会将容量扩容为原来的1.5倍,并通过数组拷贝将旧数据迁移到新数组中。
2. new ArrayList(10)
中数组扩容了几次?
new ArrayList(10)
只是实例化了一个对象,并指定初始容量为10,此时并未添加任何元素,因此不会触发扩容。
3. 如何实现数组与List之间的转换?
- 数组转List :使用
Arrays.asList(数组对象)
。 - List转数组 :使用
List.toArray(数组对象)
。
4. 转换后修改原内容,转换后的对象是否受影响?
- 数组转List :受影响。因为
Arrays.asList
返回的List
是基于原数组的视图,修改原数组会影响List
。 - List转数组 :不受影响。因为
toArray
方法会创建一个新的数组,与原List
无关。
四、总结
ArrayList
是 Java 集合框架中非常重要的一个类,它的动态数组实现使其在随机访问和动态扩容方面表现出色。通过本文的分析,我们了解了 ArrayList
的核心实现原理、扩容机制以及常见的面试题。掌握这些知识,不仅可以帮助你更好地使用 ArrayList
,还能在面试中游刃有余。
希望本文对你有所帮助!如果你有任何问题或建议,欢迎在评论区留言讨论。
参考文献:
- Java ArrayList 源码
- 《Java 编程思想》
- 《Effective Java》