当前位置:   article > 正文

Java 集合框架 - ArrayList详解_java arraylist

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;
  • 1

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

ArrayList<String> myArrayList;
  • 1

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

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

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

myArrayList = new ArrayList<String>();
  • 1

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

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

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

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

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

ArrayList<String> existingList = new ArrayList<>();
ArrayList<String> myArrayList = new ArrayList<>(existingList);
  • 1
  • 2

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

删除元素

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

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]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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

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

获取元素

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

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

更新元素

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

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

其他操作

集合转为数组 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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

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写法不一样但原理差不多。

在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Guff_9hys/article/detail/766491
推荐阅读
相关标签
  

闽ICP备14008679号