目录
引言
在 Java 编程领域里,高效地存储和处理数据是一项关键任务。集合框架就像是一个功能强大的工具箱,为开发者提供了各种数据结构来满足不同的需求。而 Java 8 引入的 Stream 流,则如同给这个工具箱增添了一把锋利的瑞士军刀,让集合操作变得更加高效和便捷。本文将深入且详细地介绍 Java 中常用的集合类 ArrayList
、HashSet
、HashMap
以及强大的 Stream 流。
一、ArrayList
1. 概述
ArrayList
是 Java 集合框架中 List
接口的一个重要实现类。它本质上是一个动态数组,继承自 AbstractList
类并实现了 List
接口。这意味着 ArrayList
可以存储重复的元素,并且元素是按照插入的顺序进行存储的。打个比方,ArrayList
就像是一个可以自动变长的书架,你可以按照顺序一本一本地往书架上放书,而且同一本书可以放多本。
2. 特点
动态扩容
当我们使用数组时,需要提前确定数组的大小。但在实际开发中,数据的数量往往是不确定的。ArrayList
就很好地解决了这个问题,它具有动态扩容的特性。当 ArrayList
中的元素数量超过其当前容量时,它会自动进行扩容,就像书架的格子不够用了,它会自动增加格子的数量,这样我们就可以继续往里面放书,而无需手动去管理数组的大小,使用起来非常方便。
初始容量
在 Java 中,ArrayList
有多种构造方法。当我们使用无参构造方法创建 ArrayList
对象时,其初始容量并不是立即分配为某个固定值。实际上,在 Java 7 及以后的版本中,使用无参构造方法创建 ArrayList
时,初始容量为 0。当第一次向 ArrayList
中添加元素时,它会将容量初始化为 10。例如:
import java.util.ArrayList;
import java.util.List;
public class ArrayListInitialCapacityExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
// 此时 list 的容量为 0
System.out.println("添加元素前的容量猜测:" + getCapacity(list));
list.add("element");
// 第一次添加元素后,容量变为 10
System.out.println("添加元素后的容量猜测:" + getCapacity(list));
}
// 此方法通过反射获取 ArrayList 的容量
private static int getCapacity(List<?> list) {
try {
java.lang.reflect.Field field = ArrayList.class.getDeclaredField("elementData");
field.setAccessible(true);
return ((Object[]) field.get(list)).length;
} catch (Exception e) {
return -1;
}
}
}
上述代码通过反射机制尝试获取 ArrayList
的实际容量。可以看到,在使用无参构造方法创建 ArrayList
后,在添加元素之前容量为 0,添加第一个元素后,容量变为 10。
如果我们使用带有初始容量参数的构造方法来创建 ArrayList
,则可以指定其初始容量。例如:
List<String> list = new ArrayList<>(20);
这里创建的 ArrayList
初始容量就是 20。
扩容倍数
当 ArrayList
中的元素数量达到其当前容量时,就需要进行扩容操作。ArrayList
的扩容机制是将原容量扩大为原来的 1.5 倍。具体来说,在 ArrayList
的源码中,扩容操作主要通过 grow
方法实现,其核心代码如下:
private void grow(int minCapacity) {
// 获取旧容量
int oldCapacity = elementData.length;
// 计算新容量,新容量为旧容量的 1.5 倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 调用 Arrays.copyOf 方法将原数组元素复制到新数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
在上述代码中,oldCapacity >> 1
表示将旧容量右移一位,相当于将旧容量除以 2。所以 newCapacity = oldCapacity + (oldCapacity >> 1)
实际上就是将原容量扩大为原来的 1.5 倍。例如,如果原容量为 10,当需要扩容时,新容量将变为 10 + 10 / 2 = 15。
随机访问高效
由于 ArrayList
是基于数组实现的,它在内存中是连续存储元素的。这就好比书架上的书是一本挨着一本整齐排列的,我们可以根据书的位置(索引)快速地找到某一本书。在 ArrayList
中,通过索引访问元素的时间复杂度为 O(1),也就是说无论 ArrayList
中有多少个元素,我们都能在几乎相同的时间内找到指定索引位置的元素。