Java 集合框架 - ArrayList详解

ArrayList 集合介绍


Java ArrayList 是 Java 标准库中的一个重要类,它属于 Java 集合框架的一部分,用于存储和操作元素的动态数组。

ArrayList 是 Java 1.2 版本引入的,它提供了一种动态数组的实现方式,可以根据需要自动扩展数组的大小。

ArrayList 的特点

  • 动态数组:ArrayList 是一个动态数组,它可以根据需要自动扩展或缩小,而不需要手动处理数组的大小。
  • 元素的存储:ArrayList 可以存储各种数据类型的元素,包括基本数据类型和对象。
  • 索引访问:ArrayList 中的元素可以使用索引进行访问,就像普通数组一样。
  • 自动扩展:当您向 ArrayList 中添加元素时,它会自动增加容量,以确保足够的空间存储新元素。通常,当当前容量不足时,ArrayList 会增加其容量,通常是当前容量的一半。
  • 允许重复元素:ArrayList 允许存储重复元素。
  • 泛型支持:ArrayList 支持泛型,允许在编译时指定要存储的元素类型,从而提供类型安全性。
  • 常见操作:ArrayList 提供了一系列方法,包括添加元素、获取元素、更新元素、删除元素、遍历元素以及其他常见操作。
  • 非线程安全:ArrayList 不是线程安全的,这意味着在多线程环境中,如果多个线程同时访问一个 ArrayList 并且至少有一个线程执行修改操作,需要采取额外的措施来确保线程安全性。

创建和初始化 ArrayList


1.导入 ArrayList 类
在 Java 中,ArrayList 位于 java.util 包中,因此首先需要导入它。可以在代码的顶部添加以下导入语句:

import java.util.ArrayList;

2.声明 ArrayList 变量
声明一个变量来保存 ArrayList 的引用。这是一个引用类型的变量,它将指向分配给它的 ArrayList 对象。例如:

ArrayList<String> myArrayList;

这里我们声明了一个名为 myArrayList 的 ArrayList 变量,它将存储字符串类型的元素。

3.初始化 ArrayList
有几种方式可以初始化 ArrayList:

使用无参构造函数
您可以使用无参构造函数创建一个空的 ArrayList:

myArrayList = new ArrayList<String>();

使用带初始容量的构造函数
如果您知道大致需要多少元素,您可以在初始化时指定初始容量,以避免不必要的自动扩展:

myArrayList = new ArrayList<String>(10); // 初始化容量为10

使用集合初始化
您还可以使用集合初始化语法来初始化 ArrayList,将一组元素添加到 ArrayList 中:

ArrayList<String> myArrayList = new ArrayList<>(Arrays.asList("元素1", "元素2", "元素3"));

从现有集合或另一个 ArrayList 初始化
如果您有一个已经存在的集合或 ArrayList,您可以将它们传递给 ArrayList 构造函数来创建一个新的 ArrayList,包含现有集合的元素。

ArrayList<String> existingList = new ArrayList<>();
ArrayList<String> myArrayList = new ArrayList<>(existingList);

ArrayList 基本操作


添加元素

boolean add(E e) 将指定的元素追加到此列表的末尾。

void add(int index,E element) 在此列表中的指定位置插入指定的元素。

boolean addAll(Collection<? extends E> c) 按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾。

boolean addAll(int index, Collection<? extends E> c) 将指定集合中的所有元素插入到此列表中,从指定的位置开始。

List<String> list = new ArrayList<>();
// add(E e)
list.add("a");
list.add("c");
// add(int index,E element)
list.add(1,"b");

list.forEach(s -> System.out.println(s)); // a b c

List<String> list1 = new ArrayList<>();
list1.add("d");
// addAll(Collection<? extends E> c)
list1.addAll(list);
list1.forEach(s-> System.out.println(s));// d a b c
// addAll(int index,  Collection<? extends E> c)
list1.addAll(0,list);
list1.forEach(s-> System.out.println(s));// a b c d a b c

删除元素

void clear() 从列表中删除所有元素。

E remove(int index) 删除该列表中指定位置的元素并返回。

boolean remove(Object o) 从列表中删除指定元素的第一个出现(如果存在)。

boolean removeAll(Collection<?> c) 从此列表中删除指定集合中包含的所有元素。

boolean removeIf(Predicate<? super E> filter) 删除满足给定谓词的此集合的所有元素。

list // a b c
list1 // a b c d a b c

// clear()
list1.clear();
list1.forEach(s-> System.out.println(s));// 无输出
// remove(int index)
System.out.println(list1.remove(1));// b
// remove(Object o)
list1.remove("a");
list1.forEach(s-> System.out.println(s));// b c d a b c
//removeAll(Collection<?> c)
list1.removeAll(list);
list1.forEach(s-> System.out.println(s));// d

removeIf(Predicate<? super E> filter) 用于根据指定的条件(谓词或 Predicate)删除集合中的元素。这个方法是在 Java 8 中引入的,它允许更便捷地根据特定的条件删除元素,而不必手动编写循环。

  • 参数:
    • filter:一个函数式接口 Predicate,用于定义删除元素的条件。该谓词接受一个参数,该参数表示集合中的元素,返回一个布尔值来指示是否应删除该元素。
  • 返回值:
    • removeIf 方法返回一个布尔值,表示是否在执行过程中修改了集合。如果有一个或多个元素被删除,它将返回 true,否则返回 false
  • 功能: removeIf 方法会遍历集合中的每个元素,并应用指定的谓词(Predicate)来检查每个元素。对于谓词返回 true 的元素,将从集合中删除。这个方法会修改调用它的 ArrayList。
  • 示例: 下面是一个使用 removeIf 方法的示例,假设我们有一个 ArrayList 存储整数,并且要删除所有偶数元素:
ArrayList<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6));

boolean result = numbers.removeIf(n -> n % 2 == 0);

System.out.println("是否有元素被删除: " + result);
System.out.println("更新后的 ArrayList: " + numbers);
// 输出
是否有元素被删除: true
更新后的 ArrayList: [1, 3, 5]

在这个示例中,removeIf 方法使用 lambda 表达式作为谓词,检查每个元素是否为偶数(n % 2 == 0)。符合条件的元素被删除,方法返回 true,并且 ArrayList 更新为 [1, 3, 5]

使用 removeIf 方法可以大大简化从 ArrayList 中删除满足特定条件的元素的操作,使代码更加简洁和可读。

获取元素

E get(int index) 返回此列表中指定位置的元素。

System.out.println(list1.get(0));// a

更新元素

E set(int index, E element) 用指定的元素替换此列表中指定位置的元素,并返回老值。

System.out.println(list1.set(0, "g"));// a
System.out.println(list1.get(0));// g

其他操作

集合转为数组 toArray()

object[] toArray() 返回一个包含此列表中所有元素的对象数组。

<T> T[] toArray(T[] a) 方法返回一个数组,该数组包含了 ArrayList 中的元素。这个数组的类型是由传入的目标数组类型决定的。

toArray 方法将 ArrayList 中的元素复制到目标数组中,并返回该数组。如果传入的目标数组足够大以容纳所有元素,那么元素将存储在目标数组中。如果目标数组过小,将会创建一个新的数组,并将元素复制到新数组中。如果目标数组大于 ArrayList 的大小,多余的数组元素将被设置为 null。如果目标数组足够大,但是 ArrayList 包含的元素数量小于数组的大小,数组的部分元素将保持不变。

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
// Integer[] arr = new Integer[list.size()];
// list.toArray(arr);
// 5 6 行可以简写为
Integer[] arr = list.toArray(new Integer[list.size()]);
System.out.println(arr.getClass().getSimpleName());// Integer[]
for (Integer i : arr) System.out.println(i);// 1 2 3

trimToSize()

trimToSize() 是 Java ArrayList 类的一个方法,用于优化 ArrayList 的存储空间。当 ArrayList 的实际元素数量远小于其容量时,trimToSize() 方法可以减小 ArrayList 的内部数组的大小,以节省内存。这个方法在某些情况下可以用来提高性能和节省资源。

ArrayList 扩容机制


调用 ArrayList 公共方法 add(E e)
在这里插入图片描述

modCount 是集合的一个字段,它记录了对集合的结构性修改的次数。每当集合发生添加、删除或其他结构性修改时,modCount 值都会增加。modCount 主要用于迭代器(如 IteratorListIterator)来检测在迭代期间是否对集合进行了结构性修改。当迭代器初始化时,它会记录当前的 modCount 值,然后在每次迭代操作(如 next()hasNext())之前检查 modCount 是否与初始值相同。如果它们不同,迭代器会抛出 ConcurrentModificationException 异常,以指示在迭代期间发生了结构性修改。(与扩容无关不做详细解释)

在增加修改次数后调用了私有的add方法
在这里插入图片描述

参数解释:

  • elementData:ArrayList 内部使用 elementData 数组来存储元素

在这里插入图片描述

  • s :集合大小(实际元素数量)

  • e : 我们添加的数据

如果当前集合大小和elementData 容量相等时需要调用grow()对集合进行扩容

grow() 方法:

  • 这个方法是 grow(int minCapacity) 方法的一个包装方法,通常在需要添加新元素时调用。
  • 它调用 grow(size + 1) 来确定新的容量,其中 size 表示当前 ArrayList 中的元素数量。
  • size + 1 传递给 grow(int minCapacity) 表示需要添加一个元素,以此大小计算新容量。

在这里插入图片描述

grow(int minCapacity) 方法:

  • 这个方法用于在需要扩展 ArrayList 的内部数组时计算新的容量,并执行扩展操作。
  • minCapacity 参数表示所需的最小容量,通常是当前元素数量与要添加的元素数量之和。它用来确定新数组的大小。
  • 首先,方法获取当前数组的容量 oldCapacity
  • 然后,它检查 elementData 数组是否为空或是否等于 DEFAULTCAPACITY_EMPTY_ELEMENTDATADEFAULTCAPACITY_EMPTY_ELEMENTDATA 是一个常量,表示一个空 ArrayList 的默认空数组。
  • 如果数组不为空且不等于 DEFAULTCAPACITY_EMPTY_ELEMENTDATA,它计算一个新的容量 newCapacity,以确保能够容纳足够的元素。newLength 方法通常用于计算新的容量,它考虑了最小增长(minCapacity - oldCapacity)和首选增长(oldCapacity >> 1)。
  • 最后,它使用 Arrays.copyOf 方法创建一个新的数组,将旧数组中的元素复制到新数组中,并返回新数组。

这里可以看到 如果创建ArrayList时没有指定大小,在第一次add的时候会初始化elementData的容量为DEFAULT_CAPACITY也就是10

在这里插入图片描述

新容量的计算函数newLength(int oldLength, int minGrowth, int prefGrowth)

  • oldCapacity:表示当前 ArrayList 内部数组的容量,也就是当前数组可以容纳的元素数量。
  • minCapacity - oldCapacity:表示需要增加的最小容量,即要添加的元素数量与当前容量之间的差值。这是确保新容量足够大以容纳新元素的最小需求。
  • oldCapacity >> 1:表示当前容量的一半,这是一种常见的扩容策略,以确保新容量足够大,同时避免过度分配内存。将当前容量除以 2 可以使新容量大致是当前容量的 1.5 倍。
  • prefLength 计算:首先,方法计算 prefLength,这是 oldLength 加上 minGrowthprefGrowth 中的最大值。这个值表示所需容量的一种估计,可能会超出所需的容量。
  • 溢出检查:接下来,方法执行了一个溢出检查。如果 prefLength 大于 0 且小于或等于 SOFT_MAX_ARRAY_LENGTH,则返回 prefLength。这是为了确保新容量在合理的范围内,同时防止溢出。
  • 如果 prefLength 超出合理范围,将调用 hugeLength 方法来确定新容量。这个部分的代码通常是在冷路径(不常执行的代码路径)中的,因为大多数情况下不会触发这个分支。

这个方法的目的是计算新容量,以便在需要扩展 ArrayList 内部数组时分配足够的空间来容纳更多元素。它尝试根据 minGrowthprefGrowth 来估算新容量,同时确保不会出现溢出情况。如果估算的容量在合理范围内,就返回这个容量值,否则将调用 hugeLength 方法来决定新容量。

请注意,SOFT_MAX_ARRAY_LENGTH 是一个常数,表示 ArrayList 内部数组的最大允许容量,大小为 Integer.MAX_VALUE - 8

在这里插入图片描述

可以看出一般情况下ArrayList的扩容因子是1.5,特殊情况是(1 + minGrowth/oldLength)

ps: 演示代码的JDK版本是17,JDK8写法不一样但原理差不多。

在这里插入图片描述

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: ArrayListJava中的一个类,它可以动态地存储一组元素,可以根据需要动态增加或减少元素的数量,是一种非常方便的数据结构ArrayList的基本用法包括创建ArrayList对象、添加元素、获取元素、修改元素、删除元素等。首先,可以使用如下代码创建一个空的ArrayList对象: ```java ArrayList<String> list = new ArrayList<String>(); ``` 上面代码创建了一个类型为String的ArrayList对象,该对象初始为空。然后,可以使用add()方法向ArrayList中添加元素,例如: ```java list.add("apple"); list.add("banana"); list.add("orange"); ``` 上述代码向list中添加了三个字符串元素。可以使用get()方法获取ArrayList中的元素,例如: ```java String first = list.get(0); // 获取第一个元素 ``` 可以使用set()方法修改ArrayList中的元素,例如: ```java list.set(1, "pear"); // 将第2个元素改为"pear" ``` 可以使用remove()方法删除ArrayList中的元素,例如: ```java list.remove(2); // 删除第3个元素 ``` 以上就是ArrayList的基本用法。需要注意的是,ArrayList中的索引从0开始。 ### 回答2: ArrayListJava中非常常用的数据结构。它提供了一个可以动态添加、删除、修改的可变长度的序列。 使用ArrayList时,首先需要引入它的包:java.util。然后可以使用如下语法创建一个ArrayList对象: ```java ArrayList<String> list = new ArrayList<String>(); ``` 这里的`<String>`说明了这个ArrayList中的元素类型是String。当然,也可以使用其他类型作为元素类型。例如: ```java ArrayList<Integer> numbers = new ArrayList<Integer>(); ArrayList<Double> prices = new ArrayList<Double>(); ``` 可以使用`add()`方法来向ArrayList中添加元素: ```java list.add("apple"); list.add("orange"); list.add("banana"); ``` 可以使用`get()`方法来获取指定位置的元素: ```java String fruit = list.get(1); //获取第二个元素,即"orange" ``` 可以使用`size()`方法来获取ArrayList中元素的个数: ```java int size = list.size(); //获取ArrayList中元素的个数 ``` 可以使用`set()`方法来修改指定位置的元素: ```java list.set(1, "pear"); //将第二个元素修改为"pear" ``` 可以使用`remove()`方法来删除指定位置的元素: ```java list.remove(2); //删除第三个元素,即"banana" ``` 需要注意的是,ArrayList中的元素是有序的,且可以重复。因此,可以使用循环来遍历ArrayList中的元素: ```java for(int i=0; i<list.size(); i++){ String fruit = list.get(i); System.out.println(fruit); } ``` 或者使用增强型循环(foreach): ```java for(String fruit : list){ System.out.println(fruit); } ``` 总之,使用ArrayList可以方便地处理可变长度的序列。在实际开发中,它有着广泛的应用场景,例如处理文件或数据库中的数据,实现算法或数据结构等。 ### 回答3: ArrayListJava中一个非常常用的容器类。他的优点是可以存储任意类型的对象,可以动态扩容,因此在使用上非常的方便。 使用ArrayList需要在代码中首先调用import java.util.ArrayList进行导入,随后可以通过ArrayList<类型> name = new ArrayList<类型>()这个语句声明一个ArrayList,并将其命名为name,同时指定ArrayList中存储的对象类型为类型。当我们需要添加元素时,可以通过name.add(element)将元素添加到ArrayList中。我们也可以通过name.get(index)方法获取ArrayList中指定位置的元素,通过name.set(index,value)方法将ArrayList中某个位置的元素替换为新的元素。同时,我们也可以调用name.size()方法获取ArrayList中元素的数量。 值得注意的是,ArrayList中的元素是以索引的方式存储的,这意味着我们可以根据元素的位置进行添加、修改、删除等操作。而且,由于ArrayList的容量是可变的,因此其内部必须动态地管理数据的内存,这会影响到ArrayList的性能。当然,这个影响是很小的,不会对代码的运行产生显著的影响。 总之,ArrayListJava中非常常用的容器类,其可以存储任意类型的对象,同时调用也非常方便。但在使用时需要注意其操作的复杂度,以及不能存储基本数据类型。如果需要在ArrayList中存储基本数据类型,需要借助Boxing和Unboxing机制将其转换为对应的包装类。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值