original - Java Array vs. ArrayList: Comparison and Conversion author - Lokesh Gupta

Java 编程中,arraysarraylists 都是基本的数据结构,用来存放数据集合。虽然两者的用途一样,但是它们的特点极大地影响应用的性能和灵活性。

本文探讨 arraysarraylists 的重要特性,它们各自的强项和弱点。当需要的时候,实现两种数据结构的无缝转换。

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;

Java 中 Array 和 ArrayList 的比较和转换_数组

Arrays 有一个固定的长度,这就是说一旦数组被创建,数组大小不可以被改变。数组中元素的获取通过响应的索引获取。

for(int i = 0; i < a.length; i++) {
  System.out.println(a[i]);
}

1.2 Java ArrayList

ArrayList 类是 Java 集合框架的一部分,用来实现 List 接口。不像 arraysArrayLists 伴随着元素的添加或者移除来增长或者缩小。

可以在 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 的不同

下面的表格是 arraysarraylists 的比较总结。比较这两个数据机构,基于它们的性能,使用和场景。

特性

Arrays

ArrayLists

固定大小和动态大小

在初始化的时候就分配固定大小

动态调整大小,随着元素的添加或删除而变化

内存管理和效率

如果数组大小超出了实际需要的个数,固定大小可能导致内存浪费

动态调整大小会带来轻微的性能开销,但是可以优化内存使用

语法和使用难易

初始化,增加,移除和更新操作语法很直白

使用 Collections API 方法更直观、更方便工作

性能

对于读/写操作,arrays 通常很快,因为它是直接使用元素索引获取。对于需要调整大小的写操作,arraylists 性能优于 array

除了需要调整大小的写操作,其他的操作 arraylists 表现得比 arrays

类型安全

Arrays 具有有限的类型安全性,并允许任何类型的元素存储在同个数组中

通过泛型,ArrayLists 提供更好的类型安全性,确保特定类型的元素被存储

最佳使用场景

当需要固定大小的集合并且看中内存效率,则使用 array

ArrayList 最适合用于小型的集合,其中方便性微小且可忽略的性能提升

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

最简单的转换一个 arraylistarray 方案是使用 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. 最佳实践和推荐

ArrayListsarrays 都有相同的目的和各自的优点。以下建议是有助于我们缩小范围并选择适合我们应用程序的正确数据结构。

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 与其他集合类型互操

ArrayListJava 集合框架的一部分,所以和其他类型(比如:MapSet)的操作是无缝的。使用 arrays 将会产生转换成其他类型不必要的额外步骤。

使用 arraylist 将减少这类的转换,因此代码可读性更高且更简洁。

6. 总结

总之,Java 开发的最佳实践通常推荐使用 ArrayLists 和其他内置的集合类,因为它们更灵活,并且相比中等大小的集合性能相似。

然而,arrays 也有实用的场景,特别是在意性能和内存效率的情况。