original - Java Array vs. ArrayList: Comparison and Conversion author - Lokesh Gupta
在 Java
编程中,arrays
和 arraylists
都是基本的数据结构,用来存放数据集合。虽然两者的用途一样,但是它们的特点极大地影响应用的性能和灵活性。
本文探讨 arrays
和 arraylists
的重要特性,它们各自的强项和弱点。当需要的时候,实现两种数据结构的无缝转换。
1. 介绍
在 Java
中,ArrayList
是集合框架的一部分,是可调整数据结构的实现。这一位意味着 arraylist
内部维护了一个需要动态增长或者收缩的数组。
1.1 Java Arrays
array
是一个固定大小的数据结构,在连续的内存空间中存放相同类型的数据。 array
中的每个元素通过索引或者位置识别,首个元素从 0
开始。
Java
语言中的 arrays
跟其他语言中的数组类似。通常,它们有下面的特性:
Arrays
通常存储同种数据类型的元素。数据的类型在初始化的时候就已经定义好。- 一个整数类型数组只能存放整数。
Java
的编译器不允许在整数类型的数组中存放字符串数据。 - 数组中的每个元素只能通过索引获取。没有其他获取数组元素的方法。
- 数组的大小通常是固定的并且不能更改。要存放比数组大小更多的元素,我们必须创建新的数组,然后将旧数组数据拷贝到新数组。当我们尝试添加超过数组大小的元素,将会抛出 ** ArrayIndexOutOfBoundsException** 错误。
比如,以下数组是其在内部存储器的表现:
int [] a = new int[5];
a[0] = 1;
a[1] = 2;
a[2] = 4;
a[3] = 8;
a[4] = 16;
Arrays
有一个固定的长度,这就是说一旦数组被创建,数组大小不可以被改变。数组中元素的获取通过响应的索引获取。
for(int i = 0; i < a.length; i++) {
System.out.println(a[i]);
}
1.2 Java ArrayList
ArrayList
类是 Java
集合框架的一部分,用来实现 List
接口。不像 arrays
,ArrayLists
伴随着元素的添加或者移除来增长或者缩小。
可以在 arraylist
中存放多种类型的元素,但是通常不推荐,因为在运行时获取数组元素时可能会引发 ClassCastException 异常。为了确保类型安全,泛型 generics
被用来声明存储在 arraylist
中的元素类型。
List<Integer> arraylist = new ArrayList<>();
arraylist.add(1); // allowed
// arraylist.add("one"); // NOT allowed
除了可以使用 for
循环来顺序获取之外,arraylist
也允许使用迭代器来获取,比如 ListIterator。当我们使用迭代器并使用迭代器修改集合时,不会抛出 ConcurrentModificationException 异常。
List<Integer> arraylist = new ArrayList<>();
arraylist.add(1);
arraylist.add(2);
arraylist.add(3);
// 1 - using foreach loop
arraylist.forEach(System.out::println)
// 2 - using iterator
ListIterator<Integer> listIterator = arraylist.listIterator();
while(listIterator.hasNext()) {
System.out.println(listiterator.next());
}
2. Java 中 Array 和 ArrayList 的不同
下面的表格是 arrays
和 arraylists
的比较总结。比较这两个数据机构,基于它们的性能,使用和场景。
特性 | Arrays | ArrayLists |
固定大小和动态大小 | 在初始化的时候就分配固定大小 | 动态调整大小,随着元素的添加或删除而变化 |
内存管理和效率 | 如果数组大小超出了实际需要的个数,固定大小可能导致内存浪费 | 动态调整大小会带来轻微的性能开销,但是可以优化内存使用 |
语法和使用难易 | 初始化,增加,移除和更新操作语法很直白 | 使用 |
性能 | 对于读/写操作, | 除了需要调整大小的写操作,其他的操作 |
类型安全 |
| 通过泛型, |
最佳使用场景 | 当需要固定大小的集合并且看中内存效率,则使用 |
|
3. Array 转换为 ArrayList
将 Array
转换为 ArrayList
最直接的方法是使用 Arrays.asList()
方法,该方法创建了数组的列表视图,然后我们使用 ArrayList
构造函数创建一个新的 ArrayList
。这有效地将数组转换为 ArrayList
。
String[] array = {"apple", "banana", "cherry"};
ArrayList<String> arrayList = new ArrayList<>(Arrays.asList(array));
另外,我们也可以使用 Java 8 streams 来迭代数组元素,并将它们收集在一个新的 ArrayList
中。它给我们提供了在将元素收集到列表前对数组的每个元素执行额外操作的机会。
ArrayList<String> arrayList = Arrays.stream(array)
// additional actions
.collect(Collectors.toCollection(ArrayList::new));
4. ArrayList 转换为 Array
最简单的转换一个 arraylist
为 array
方案是使用 ArrayList.toArray() 方法,该方法返回一个包含列表中所有元素的数组,并且元素顺序正确。toArray()
返回 Object[]
类型的数组,所以你需要提供你想要的数组类型作为参数。
新数组的大小是由 ArrayList
大小决定的。
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("apple");
arrayList.add("banana");
arrayList.add("cherry");
String[] array = arrayList.toArray(new String[arrayList.size()]);
5. 最佳实践和推荐
ArrayLists
和 arrays
都有相同的目的和各自的优点。以下建议是有助于我们缩小范围并选择适合我们应用程序的正确数据结构。
5.1 频繁更改大小操作
因为特殊的需求,如果数组需要频繁更改大小,建议使用 ArrayList
。 调整大小操作的内部处理消除了应用程序代码的复杂度,并提供了和手动修改相似的性能增益。
5.2 性能提升的可量化
如果性能提升不是很重要,那么还是推荐使用 arraylists
。 ArrayLists
避免了复杂性,并且让代码可读性更高,且性能在处理小的集合跟 array
相似。
衡量性能增益的最佳方式是使用诸如 JMH
之类的工具进行测量。
5.3 原始类型 VS 包装对象
Array
可以直接处理原始类型,而 arraylists
则与对象(即包装类)一起使用。 如果在应用程序中处理它们时需要不断进行两种类型的转换,最好使用 arrays
,因为它们会消除不必要的类型转换来简化代码,并因此带来轻微的性能提升。
int[] array = new int[10];
//Creating arraylist for 'int' type is not possible. We must create arraylist of type 'Integer'
ArrayList<Integer> arraylist = new ArrayList<>();
5.4 与其他集合类型互操
ArrayList
是 Java
集合框架的一部分,所以和其他类型(比如:Map
,Set
)的操作是无缝的。使用 arrays
将会产生转换成其他类型不必要的额外步骤。
使用 arraylist
将减少这类的转换,因此代码可读性更高且更简洁。
6. 总结
总之,Java
开发的最佳实践通常推荐使用 ArrayLists
和其他内置的集合类,因为它们更灵活,并且相比中等大小的集合性能相似。
然而,arrays
也有实用的场景,特别是在意性能和内存效率的情况。