ArrayList
和 Array
是两种常见的数据结构,它们在许多方面有所不同。本文将详细讲解二者的区别,并通过代码示例和源码分析来深入理解这些差异。
1. 动态扩容 vs 固定长度
Array
Array
是一种固定长度的数据结构,数组在创建时必须指定其大小,且一旦创建便不能改变其长度。以下是一个简单的示例:
java
// 初始化一个 String 类型的数组
String[] stringArr = new String[]{"hello", "world", "!"};
System.out.println(Arrays.toString(stringArr)); // 输出: [hello, world, !]
// 修改数组元素的值
stringArr[0] = "goodbye";
System.out.println(Arrays.toString(stringArr)); // 输出: [goodbye, world, !]
// 删除数组中的元素,需要手动移动后面的元素
for (int i = 0; i < stringArr.length - 1; i++) {
stringArr[i] = stringArr[i + 1];
}
stringArr[stringArr.length - 1] = null;
System.out.println(Arrays.toString(stringArr)); // 输出: [world, !, null]
ArrayList
ArrayList
是基于动态数组实现的,它的大小可以根据需要动态增长和缩减。以下是一个简单的示例:
java
// 初始化一个 String 类型的 ArrayList
ArrayList<String> stringList = new ArrayList<>(Arrays.asList("hello", "world", "!"));
System.out.println(stringList); // 输出: [hello, world, !]
// 添加元素到 ArrayList 中
stringList.add("goodbye");
System.out.println(stringList); // 输出: [hello, world, !, goodbye]
// 修改 ArrayList 中的元素
stringList.set(0, "hi");
System.out.println(stringList); // 输出: [hi, world, !, goodbye]
// 删除 ArrayList 中的元素
stringList.remove(0);
System.out.println(stringList); // 输出: [world, !, goodbye]
2. 类型安全 vs 非类型安全
Array
Array
可以存储基本类型数据和对象,但不支持泛型。以下是一个例子:
java
int[] intArray = new int[]{1, 2, 3}; // 基本类型数组
String[] stringArray = new String[]{"hello", "world"}; // 对象类型数组
ArrayList
ArrayList
允许使用泛型来确保类型安全,只能存储对象类型的数据。对于基本类型数据,需要使用其对应的包装类(如 Integer
、Double
等):
java
ArrayList<Integer> intList = new ArrayList<>(Arrays.asList(1, 2, 3)); // 泛型 ArrayList
ArrayList<String> stringList = new ArrayList<>(Arrays.asList("hello", "world")); // 泛型 ArrayList
3. 丰富的 API 支持 vs 基础操作
Array
Array
只提供基础的数组操作,无法动态添加、删除元素。以下是一个示例:
java
String[] stringArray = new String[]{"hello", "world"};
// 访问数组元素
String firstElement = stringArray[0];
// 修改数组元素
stringArray[0] = "hi";
ArrayList
ArrayList
提供了丰富的 API 操作方法,如 add()
、remove()
、set()
等:
java
ArrayList<String> stringList = new ArrayList<>(Arrays.asList("hello", "world"));
// 添加元素
stringList.add("goodbye");
// 删除元素
stringList.remove("world");
// 修改元素
stringList.set(0, "hi");
4. 内部实现和扩容机制
ArrayList 的源码解析
ArrayList
的内部是基于数组实现的,但它提供了动态扩容的机制。以下是 ArrayList
的部分源码和其扩容机制的解析:
java
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
// 底层数组
transient Object[] elementData;
// 当前元素个数
private int size;
// 默认初始容量
private static final int DEFAULT_CAPACITY = 10;
// 最大数组大小
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 添加元素的方法
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 扩容检查
elementData[size++] = e;
return true;
}
// 确保容量
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
// 扩容方法
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 扩容为原来的1.5倍
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
}
在 ArrayList
中,当我们调用 add()
方法时,如果当前数组容量不足以容纳新元素,ArrayList
会自动调用 grow()
方法进行扩容,默认扩容为原来容量的1.5倍。这种机制使得 ArrayList
能够动态地调整大小,具有很高的灵活性。
总结
通过以上对比和源码解析,可以清晰地看到 ArrayList
和 Array
的差异:
ArrayList
支持动态扩容,Array
是固定长度的。ArrayList
允许使用泛型,确保类型安全,Array
不支持泛型。ArrayList
提供了丰富的 API 操作方法,Array
只能进行基础操作。ArrayList
内部实现基于数组,并通过扩容机制实现动态调整大小。
在实际开发中,如果需要一个固定大小的数组,且对性能要求较高,可以选择 Array
。如果需要一个可以动态调整大小的集合,并且需要丰富的操作方法,可以选择 ArrayList
。