杰瑞科技汇

Java ArrayList包的核心功能是什么?

  1. 什么是 ArrayList
  2. 如何使用 ArrayList(核心方法)
  3. ArrayList 的内部工作原理(扩容机制)
  4. ArrayList 与数组的区别
  5. ArrayListLinkedList 的区别
  6. ArrayList 的线程安全问题

什么是 ArrayList

ArrayList 是 Java 集合框架中的一部分,位于 java.util 包下,它的全称是 "Array List",即 “数组列表”

Java ArrayList包的核心功能是什么?-图1
(图片来源网络,侵删)

它的核心特点是:

  • 动态大小:它是一个可以动态改变长度的数组,普通 Java 数组在创建时大小就固定了,而 ArrayList 可以根据需要自动增长或缩小。
  • 基于数组:尽管它叫“列表”,但其底层实现是一个动态数组
  • 有序:它保持元素的插入顺序。
  • 允许重复元素:可以存储多个相同的对象。
  • 允许 null 元素:可以存储 null 值。

你可以把它想象成一个“超级数组”,它既有数组快速随机访问的优点,又解决了数组大小固定的缺点。


如何使用 ArrayList(核心方法)

在使用 ArrayList 之前,你需要导入它。

import java.util.ArrayList;

1 创建 ArrayList

ArrayList 是一个泛型类,所以在创建时需要指定它可以存储的对象类型。

Java ArrayList包的核心功能是什么?-图2
(图片来源网络,侵删)
// 1. 创建一个可以存储 String 类型的 ArrayList
ArrayList<String> stringList = new ArrayList<>();
// 2. 创建一个可以存储 Integer 类型的 ArrayList
ArrayList<Integer> numberList = new ArrayList<>();
// 3. 创建时指定初始容量 (可选)
// 如果大致知道要存多少元素,指定初始容量可以提高性能,避免多次扩容
ArrayList<Double> doubleList = new ArrayList<>(20); 

2 常用方法

方法 描述 示例
add(E element) 在列表末尾添加一个元素。 list.add("Apple");
add(int index, E element) 在指定位置插入一个元素。 list.add(0, "Banana");
get(int index) 获取指定位置的元素。 String fruit = list.get(0);
set(int index, E element) 替换指定位置的元素。 list.set(0, "Orange");
remove(int index) 移除指定位置的元素。 list.remove(0);
remove(Object o) 移除第一个匹配的元素。 list.remove("Orange");
size() 返回列表中的元素数量。 int count = list.size();
isEmpty() 如果列表为空,返回 true boolean empty = list.isEmpty();
contains(Object o) 如果列表包含指定元素,返回 true boolean hasApple = list.contains("Apple");
clear() 清空列表中的所有元素。 list.clear();
toArray() 将列表转换为数组。 String[] array = list.toArray(new String[0]);

3 完整示例

import java.util.ArrayList;
public class ArrayListExample {
    public static void main(String[] args) {
        // 1. 创建 ArrayList
        ArrayList<String> fruits = new ArrayList<>();
        // 2. 添加元素
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Cherry");
        System.out.println("初始列表: " + fruits); // 输出: [Apple, Banana, Cherry]
        // 3. 在指定位置插入
        fruits.add(1, "Blueberry");
        System.out.println("插入后: " + fruits); // 输出: [Apple, Blueberry, Banana, Cherry]
        // 4. 获取元素
        String firstFruit = fruits.get(0);
        System.out.println("第一个水果是: " + firstFruit); // 输出: Apple
        // 5. 替换元素
        fruits.set(2, "Blackberry");
        System.out.println("替换后: " + fruits); // 输出: [Apple, Blueberry, Blackberry, Cherry]
        // 6. 移除元素 (按索引)
        fruits.remove(0);
        System.out.println("移除第一个后: " + fruits); // 输出: [Blueberry, Blackberry, Cherry]
        // 7. 移除元素 (按对象)
        fruits.remove("Cherry");
        System.out.println("移除Cherry后: " + fruits); // 输出: [Blueberry, Blackberry]
        // 8. 获取大小
        System.out.println("列表大小: " + fruits.size()); // 输出: 2
        // 9. 检查是否包含某个元素
        System.out.println("列表是否包含'Blueberry'? " + fruits.contains("Blueberry")); // 输出: true
        // 10. 清空列表
        fruits.clear();
        System.out.println("清空后的列表: " + fruits); // 输出: []
        System.out.println("列表是否为空? " + fruits.isEmpty()); // 输出: true
    }
}

ArrayList 的内部工作原理(扩容机制)

这是理解 ArrayList 性能的关键。

ArrayList 内部维护一个数组,我们称之为 elementData,当你创建一个 ArrayList 时,这个 elementData 就被初始化了。

  • 初始容量:如果你没有指定初始容量(new ArrayList<>()),默认的初始容量是 10
  • 扩容:当你添加的元素数量超过了 elementData 的当前长度时,ArrayList 就需要扩容,它会创建一个新的、更大的数组,然后将旧数组中的所有元素复制到新数组中,最后在新数组中添加新元素。

扩容的大小是多少? 在 Java 8 及更高版本中,当 ArrayList 需要扩容时,新的容量是 旧容量的 1.5 倍oldCapacity + (oldCapacity >> 1)),一个容量为 10 的数组在扩容后会变成一个容量为 15 的数组。

为什么扩容会影响性能? 因为扩容操作(创建新数组 + 复制所有旧元素)是一个比较耗时的操作,时间复杂度是 O(n),如果你能预估 ArrayList 的大致大小,在创建时指定一个合适的初始容量,可以有效避免频繁扩容,从而提高性能。

Java ArrayList包的核心功能是什么?-图3
(图片来源网络,侵删)

ArrayList 与数组的区别

特性 数组 (String[]) ArrayList<String>
大小 固定,创建后不能改变。 动态,可以自动增长和缩小。
性能 随机访问快 (O(1)),增删慢 (需要移动元素)。 随机访问快 (O(1)),末尾增删快,中间增删慢 (需要移动元素)。
功能 功能简单,只有 length 属性。 功能丰富,提供大量方法 (add, remove, size 等)。
类型 可以存储基本类型 (int[], char[]) 和对象。 只能存储对象,对于基本类型,需要使用其包装类 (Integer, Character)。
语法 String[] arr = new String[5]; ArrayList<String> list = new ArrayList<>();

ArrayListLinkedList 的区别

LinkedListArrayList 的另一个常见实现,它们都实现了 List 接口,但底层结构和性能特点完全不同。

特性 ArrayList LinkedList
底层结构 动态数组 双向链表,每个节点存储数据、前驱节点和后继节点的引用。
随机访问 非常快 (O(1)),直接通过索引计算内存地址。 慢 (O(n)),需要从头或尾开始遍历链表。
中间增删 慢 (O(n)),需要移动被删除元素之后的所有元素。 快 (O(1)),只需要修改目标节点及其前后节点的引用即可。
内存占用 较低,只有数组本身的开销。 较高,每个元素除了数据外,还需要存储两个引用(前后节点)。
使用场景 读多写少的场景,存储大量数据后需要频繁查询。 写多读少的场景,需要频繁在中间插入或删除元素。

ArrayList 的线程安全问题

ArrayList 是非线程安全的。

这意味着,如果在多线程环境下,多个线程同时修改同一个 ArrayList 实例,可能会导致数据不一致或损坏。

如何解决?

  1. 使用 VectorVectorArrayList 的一个线程安全的“老版本”,它的所有方法都使用了 synchronized 关键字,性能较差,不推荐在新代码中使用。
  2. 使用 Collections.synchronizedList():可以将一个 ArrayList 包装成线程安全的列表。
    List<String> list = Collections.synchronizedList(new ArrayList<>());

    在遍历时,仍然需要手动同步,否则可能抛出 ConcurrentModificationException

    synchronized (list) {
        for (String item : list) {
            // ...
        }
    }
  3. 使用 CopyOnWriteArrayList:这是 Java 并发包 (java.util.concurrent) 中的一个类,它是一种“写时复制”的列表,适用于读远多于写的场景,它的读操作不加锁,性能很高;写操作时,会复制整个底层数组,在副本上进行修改,然后替换掉旧数组,这保证了线程安全,但写操作的开销很大。
  4. 使用 ConcurrentHashMapKeySet:如果只是需要一个线程安全的、不重复的元素集合,可以考虑 new ConcurrentHashMap<>().newKeySet()

  • ArrayList 是什么? 一个基于动态数组的、大小可变的列表实现。
  • 核心优势? 随机访问速度快(O(1)),末尾增删效率高。
  • 核心劣势? 中间增删效率低(O(n)),非线程安全。
  • 何时使用?
    • 当你需要一个可以动态调整大小的列表时。
    • 当你的应用场景是读多写少时。
    • 当你不需要线程安全,或者会自己处理线程同步时。
  • 关键点? 了解其扩容机制,以便在性能敏感的场景下预估容量。
分享:
扫描分享到社交APP
上一篇
下一篇