引言
在Java编程中,数据存储和操作是最基础也是最重要的部分。Java提供了两种主要的方式来存储一组数据:传统的数组(Array)和ArrayList。虽然它们都用于存储元素集合,但在实现方式、功能和性能上有着显著差异。本文将深入探讨这两者的区别,并通过JDK 1.8中的代码示例进行详细说明。
1. 基本概念
数组(Array)
数组是Java中最基本的数据结构之一,它是一个固定长度的容器,可以存储相同类型的元素。
// 数组声明和初始化
int[] intArray = new int[5]; // 固定长度为5的整型数组
String[] stringArray = {"Java", "Python", "C++"}; // 初始化时指定元素
ArrayList
ArrayList是Java集合框架的一部分,它实现了List接口,基于动态数组实现,可以根据需要自动调整容量。
// ArrayList声明和初始化
ArrayList<Integer> intList = new ArrayList<>(); // 初始容量为10的空列表
ArrayList<String> stringList = new ArrayList<>(Arrays.asList("Java", "Python", "C++"));
2. 核心区别
2.1 大小固定性
数组的大小在创建时就固定了,无法动态改变。
int[] fixedArray = new int[3];
fixedArray[0] = 1;
fixedArray[1] = 2;
fixedArray[2] = 3;
// fixedArray[3] = 4; // 抛出ArrayIndexOutOfBoundsException
ArrayList的大小是动态的,会根据需要自动增长。
ArrayList<Integer> dynamicList = new ArrayList<>();
dynamicList.add(1);
dynamicList.add(2);
dynamicList.add(3);
dynamicList.add(4); // 自动扩容,不会抛出异常
System.out.println(dynamicList.size()); // 输出4
2.2 类型安全
数组在运行时检查类型安全。
Object[] objectArray = new String[3];
objectArray[0] = "Hello";
// objectArray[1] = 123; // 抛出ArrayStoreException
ArrayList使用泛型在编译时提供类型安全。
ArrayList<String> stringList = new ArrayList<>();
stringList.add("Hello");
// stringList.add(123); // 编译时错误
2.3 性能比较
基本操作性能对比:
// 数组访问
long start = System.nanoTime();
int arrayElement = fixedArray[1];
long end = System.nanoTime();
System.out.println("数组访问时间: " + (end - start) + " ns");
// ArrayList访问
start = System.nanoTime();
int listElement = dynamicList.get(1);
end = System.nanoTime();
System.out.println("ArrayList访问时间: " + (end - start) + " ns");
通常数组的访问速度略快于ArrayList,因为ArrayList有额外的方法调用开销。
2.4 功能丰富性
ArrayList提供了更多便捷的方法:
ArrayList<String> languages = new ArrayList<>(Arrays.asList("Java", "Python", "C++"));
// 添加元素
languages.add("JavaScript");
// 删除元素
languages.remove("Python");
// 检查包含
boolean hasJava = languages.contains("Java");
// 获取子列表
List<String> subList = languages.subList(1, 3);
// 遍历(JDK 1.8 lambda)
languages.forEach(System.out::println);
相比之下,数组的功能非常基础,需要手动实现许多操作。
3. 内存管理
数组的内存分配
数组在创建时就分配了连续的内存空间:
int[] largeArray = new int[1000000]; // 立即分配内存
ArrayList的内存管理
ArrayList使用动态扩容策略:
ArrayList<Integer> list = new ArrayList<>(); // 初始容量10
// 当元素数量超过容量时,自动扩容(通常增加50%)
for (int i = 0; i < 100; i++) {
list.add(i);
}
可以通过ensureCapacity方法预先分配空间以提高性能:
ArrayList<Integer> optimizedList = new ArrayList<>();
optimizedList.ensureCapacity(1000000); // 预先分配大容量
4. 多维结构
多维数组
Java支持真正的多维数组:
int[][] matrix = new int[3][3];
matrix[0][0] = 1;
ArrayList的多维实现
ArrayList需要通过嵌套实现多维结构:
ArrayList<ArrayList<Integer>> matrixList = new ArrayList<>();
matrixList.add(new ArrayList<>(Arrays.asList(1, 2, 3)));
matrixList.add(new ArrayList<>(Arrays.asList(4, 5, 6)));
5. 原始类型处理
数组处理原始类型
数组可以直接存储原始类型:
int[] primitiveArray = new int[10]; // 不涉及装箱/拆箱
ArrayList处理原始类型
ArrayList只能存储对象,需要使用包装类:
ArrayList<Integer> wrapperList = new ArrayList<>(); // 存储Integer对象
wrapperList.add(5); // 自动装箱
int value = wrapperList.get(0); // 自动拆箱
6. 使用场景建议
适合使用数组的场景
- 知道确切且不会改变的元素数量
- 需要最高性能的原始类型操作
- 需要多维数据结构
- 与需要数组作为输入的低级API交互
// 图像处理中的像素矩阵
int[][] imagePixels = new int[1024][768];
适合使用ArrayList的场景
- 元素数量不确定或会变化
- 需要频繁的插入、删除操作
- 需要丰富的集合操作方法
- 需要与其他集合框架组件交互
// 用户动态添加的商品列表
ArrayList<Product> shoppingCart = new ArrayList<>();
shoppingCart.add(new Product("Laptop", 999.99));
shoppingCart.removeIf(p -> p.getPrice() > 1000); // JDK 1.8新方法
7. 相互转换
数组转ArrayList
String[] array = {"A", "B", "C"};
ArrayList<String> listFromArray = new ArrayList<>(Arrays.asList(array));
注意:Arrays.asList()返回的是固定大小的列表,不能添加/删除元素。
ArrayList转数组
ArrayList<String> list = new ArrayList<>(Arrays.asList("X", "Y", "Z"));
String[] arrayFromList = list.toArray(new String[0]);
8. JDK 1.8中的新特性应用
JDK 1.8为ArrayList引入了Stream API支持:
ArrayList<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
// 使用Stream API过滤和收集
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
// 并行流处理
int sum = numbers.parallelStream()
.mapToInt(Integer::intValue)
.sum();
结论
|
特性 |
数组(Array) |
ArrayList |
|
大小 |
固定 |
动态可变 |
|
类型安全 |
运行时检查 |
编译时泛型检查 |
|
性能 |
原始类型操作更快 |
有额外方法调用开销 |
|
功能 |
基础 |
丰富的方法集 |
|
多维支持 |
直接支持 |
需要嵌套实现 |
|
原始类型 |
直接支持 |
需要使用包装类 |
|
内存管理 |
一次性分配 |
动态扩容 |
在选择使用数组还是ArrayList时,应根据具体需求决定。对于性能关键且大小固定的原始类型数据,数组是更好的选择。而对于需要动态大小和丰富操作的情况,ArrayList提供了更多便利。
911

被折叠的 条评论
为什么被折叠?



