杰瑞科技汇

ArrayList在Java中如何使用?

什么是 ArrayList?

ArrayList 是一个动态数组,它实现了 List 接口,可以看作是数组的增强版,与普通数组相比,ArrayList 的最大优势在于其大小可以动态改变,你不需要在创建时就指定它的容量,它会在元素添加或删除时自动调整。

ArrayList在Java中如何使用?-图1
(图片来源网络,侵删)

核心特点:

  • 有序性: ArrayList 中的元素保持插入时的顺序。
  • 可重复性: 允许存储重复的元素。
  • 非线程安全: 它不是同步的,如果在多线程环境下使用,需要手动进行同步处理(使用 Collections.synchronizedList(new ArrayList<>())CopyOnWriteArrayList)。
  • 基于数组: 它的底层实现是一个对象数组Object[])。

为什么使用 ArrayList?(相比普通数组)

特性 普通数组 ArrayList
大小 固定,创建时必须指定 动态可变
性能 访问(随机访问)快,增删慢 访问稍慢(但仍很快),增删也慢(在中间位置)
功能 功能简单,只有 length 属性 功能丰富,提供大量方法(add, remove, size 等)
数据类型 可以是基本类型(int[], double[] 只能存储对象(存储基本类型时需使用其包装类,如 Integer

当你需要一个可以动态改变大小的列表,并且需要丰富的操作方法时,ArrayList 是比普通数组更好的选择。


ArrayList 的核心方法

下面是 ArrayList 最常用的一些方法:

构造方法

  • ArrayList(): 创建一个空的列表,初始容量为 10。
  • ArrayList(int initialCapacity): 创建一个具有指定初始容量的空列表。
  • ArrayList(Collection<? extends E> c): 创建一个包含指定集合元素的列表。

常用方法

方法 描述 示例
添加元素
add(E e) 将指定的元素追加到此列表的末尾。 list.add("Apple");
add(int index, E element) 在此列表的指定位置插入指定的元素。 list.add(1, "Banana");
删除元素
remove(Object o) 移除此列表中首次出现的指定元素(如果存在)。 list.remove("Apple");
remove(int index) 移除此列表中指定位置的元素。 list.remove(0);
获取元素
get(int index) 返回此列表中指定位置的元素。 String fruit = list.get(0);
查询信息
size() 返回此列表中的元素数。 int count = list.size();
isEmpty() 如果此列表中没有元素,则返回 true if (list.isEmpty()) { ... }
contains(Object o) 如果此列表包含指定的元素,则返回 true boolean hasOrange = list.contains("Orange");
修改元素
set(int index, E element) 用指定的元素替换此列表中指定位置的元素。 list.set(0, "Grape");
其他
clear() 从此列表中移除所有元素。 list.clear();
toArray() 以正确的顺序返回一个包含此列表中所有元素的数组。 Object[] arr = list.toArray();

ArrayList 的底层原理与性能分析

理解 ArrayList 的底层实现有助于你更好地使用它。

ArrayList在Java中如何使用?-图2
(图片来源网络,侵删)

核心属性

ArrayList 内部主要有两个属性:

// 存储 ArrayList 元素的数组
private transient Object[] elementData;
// ArrayList 的大小(元素个数)
private int size;

transient 关键字表示 elementData 数组不会被序列化。

动态扩容机制

这是 ArrayList 最核心的机制,当你向一个已满的 ArrayList 中添加元素时,它会创建一个更大的新数组,然后将旧数组中的所有元素复制到新数组中,最后再添加新元素。

  1. 初始容量:默认为 10。
  2. 扩容触发:当 size 达到 capacity 时,再调用 add() 方法就会触发扩容。
  3. 扩容大小:新数组的容量通常是旧容量的 1.5 倍,这个计算在 grow(int minCapacity) 方法中完成:int newCapacity = oldCapacity + (oldCapacity >> 1);(右移一位相当于除以2)。
  4. 性能影响扩容是一个比较耗时的操作,因为它需要创建新数组和复制元素,如果你能预估出大概需要存储多少元素,最好在创建 ArrayList 时指定一个合适的初始容量,以减少扩容带来的性能开销。
// 示例:预估需要存储 1000 个元素
List<String> list = new ArrayList<>(1000); // 避免多次扩容

性能分析

操作 时间复杂度 原因
add(E e) 平均 O(1)
最坏情况 O(n)
平均情况下是添加到末尾,直接赋值即可,但如果触发了扩容,就需要复制整个数组,此时为 O(n)。
add(int index, E element) O(n) 需要将 index 位置之后的所有元素都向后移动一位,以腾出空间。
remove(int index) O(n) 需要将 index 位置之后的所有元素都向前移动一位,填补空缺。
get(int index) O(1) 底层是数组,根据索引直接计算内存地址访问,速度极快,这是 ArrayList 最大的优势。
set(int index, E element) O(1) get 类似,直接通过索引找到位置并赋值。
contains(Object o) O(n) 需要遍历整个数组,逐个比较元素。
  • ArrayList 适合的场景频繁随机访问(遍历、查询),而不需要在中间频繁插入或删除。
  • ArrayList 不适合的场景频繁地在头部或中间位置插入/删除元素,这时,LinkedList(基于链表,增删快,访问慢)会是更好的选择。

代码示例

import java.util.ArrayList;
import java.util.List;
public class ArrayListExample {
    public static void main(String[] args) {
        // 1. 创建一个 ArrayList
        List<String> fruits = new ArrayList<>();
        // 2. 添加元素
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Orange");
        System.out.println("初始列表: " + fruits); // [Apple, Banana, Orange]
        // 3. 在指定位置添加元素
        fruits.add(1, "Mango");
        System.out.println("在索引1处添加Mango后: " + fruits); // [Apple, Mango, Banana, Orange]
        // 4. 获取元素
        String firstFruit = fruits.get(0);
        System.out.println("第一个水果是: " + firstFruit); // Apple
        // 5. 修改元素
        fruits.set(2, "Grape");
        System.out.println("将索引2处的元素改为Grape后: " + fruits); // [Apple, Mango, Grape, Orange]
        // 6. 获取列表大小
        System.out.println("列表大小: " + fruits.size()); // 4
        // 7. 检查是否包含某个元素
        boolean hasBanana = fruits.contains("Banana");
        System.out.println("列表中是否包含Banana? " + hasBanana); // false
        // 8. 删除元素
        fruits.remove("Mango"); // 删除第一个匹配的"Mango"
        System.out.println("删除Mango后: " + fruits); // [Apple, Grape, Orange]
        fruits.remove(0); // 删除索引为0的元素
        System.out.println("删除索引0的元素后: " + fruits); // [Grape, Orange]
        // 9. 遍历列表 (多种方式)
        System.out.println("--- 遍历列表 ---");
        // 方式一:使用 for-each 循环 (推荐)
        for (String fruit : fruits) {
            System.out.println(fruit);
        }
        // 输出: Grape, Orange
        // 方式二:使用迭代器
        // System.out.println("--- 使用迭代器遍历 ---");
        // Iterator<String> iterator = fruits.iterator();
        // while (iterator.hasNext()) {
        //     System.out.println(iterator.next());
        // }
        // 方式三:使用索引 (对于 ArrayList 来说效率很高)
        // for (int i = 0; i < fruits.size(); i++) {
        //     System.out.println(fruits.get(i));
        // }
        // 10. 清空列表
        fruits.clear();
        System.out.println("清空后的列表: " + fruits); // []
        System.out.println("列表是否为空? " + fruits.isEmpty()); // true
    }
}

ArrayList vs. LinkedList (快速对比)

特性 ArrayList LinkedList
数据结构 动态数组 双向链表
随机访问 get(i) O(1) 非常快 O(n) 需要从头或尾遍历
中间插入 add(i, e) O(n) 需要移动元素 O(1) 只需修改前后节点的指针
中间删除 remove(i) O(n) 需要移动元素 O(1) 只需修改前后节点的指针
内存占用 较低(只有数组) 较高(每个节点都存储前后节点的引用)
适用场景 频繁查询,少量增删 频繁增删,少量查询

希望这份详细的讲解能帮助你完全理解 Java 中的 ArrayList

ArrayList在Java中如何使用?-图3
(图片来源网络,侵删)
分享:
扫描分享到社交APP
上一篇
下一篇