Java ArrayList

devtools/2025/1/21 6:02:35/

Java ArrayList

从名字就可以看得出来,ArrayList 实现了 List 接口,并且是基于数组实现的。

有人就会问了 那ArrayList和数组有什么区别呢

数组的大小是固定的,一旦创建的时候指定了大小,就不能再调整了。也就是说,如果数组满了,就不能再添加任何元素了。ArrayList 在数组的基础上实现了自动扩容,并且提供了比数组更丰富的预定义方法(各种增删改查),非常灵活。

初始化ArrayList

java">ArrayList<String> alist = new ArrayList<String>();

这就是一个标准的初始化 当然 也有简化版本 比如

java">List<String> alist = new ArrayList<>();

由于 ArrayList 实现了 List 接口,所以 alist 变量的类型可以是 List 类型;new 关键字声明后的尖括号中可以不再指定元素的类型,因为编译器可以通过前面尖括号中的类型进行智能推断。

如果非常确定 ArrayList 中元素的个数,在创建的时候还可以指定初始大小。

java">List<String> alist = new ArrayList<>(20);

这样做的好处是,可以有效地避免在添加新的元素时进行不必要的扩容。

2. add()添加元素

java">public boolean add(E e) {ensureCapacityInternal(size + 1);  // 确保容量足够elementData[size++] = e;  // 将元素 e 添加到数组中,并更新 sizereturn true;
}

2.1 ensureCapacityInternal(size + 1)

这个方法用于确保 ArrayList 在添加新元素时有足够的容量。size + 1 表示在现有的元素基础上,还要为新元素腾出空间。

java">private void ensureCapacityInternal(int minCapacity) {if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);}if (minCapacity - elementData.length > 0)grow(minCapacity);
}
  • 首先,ensureCapacityInternal 方法检查 elementData 是否是默认的空数组。如果是空数组(即 DEFAULTCAPACITY_EMPTY_ELEMENTDATA),它将把最小容量设置为 DEFAULT_CAPACITY(通常为 10),以确保最小的容量。
  • 然后,判断 minCapacity(所需的最小容量)是否大于当前 elementData 的长度。如果是,就调用 grow(minCapacity) 来扩展数组。

2.2 grow(minCapacity)

当容量不足时,ArrayList 会调用 grow 方法来增加数组的容量。grow 方法的源码如下:

java">private void grow(int minCapacity) {int oldCapacity = elementData.length;int newCapacity = oldCapacity + (oldCapacity >> 1);  // 增加 50% 的容量// 如果扩展后的容量小于所需的最小容量,直接设置为所需容量if (newCapacity - minCapacity < 0)newCapacity = minCapacity;if (newCapacity > MAX_ARRAY_SIZE)newCapacity = hugeCapacity(minCapacity);// 扩展数组elementData = Arrays.copyOf(elementData, newCapacity);
}
  • grow 方法通过增加现有数组容量的 50% 来扩展数组。如果扩展后的容量不够 minCapacity,则直接将容量设置为 minCapacity
  • 如果扩展后的容量大于 MAX_ARRAY_SIZEgrow 会使用 hugeCapacity 方法来返回一个合适的容量。
  • 最后,使用 Arrays.copyOf 方法将旧数组的内容复制到新的扩展数组中。

2.3 elementData[size++] = e

这行代码将新元素 e 添加到 ArrayList 中,并将 size 增加 1。sizeArrayList 中元素的当前数量,elementData 是存储元素的数组。

  • 通过 size 作为索引,将新元素 e 存储在 elementData 数组中。
  • size++ 是自增操作,表示在将元素添加到数组之后,size 变量增加 1。
java">public boolean add(E e) {ensureCapacityInternal(size + 1);  // 确保容量足够elementData[size++] = e;  // 将元素 e 添加到数组中,并更新 sizereturn true;
}

2.1 ensureCapacityInternal(size + 1)

这个方法用于确保 ArrayList 在添加新元素时有足够的容量。size + 1 表示在现有的元素基础上,还要为新元素腾出空间。

java">
private void ensureCapacityInternal(int minCapacity) {if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);}if (minCapacity - elementData.length > 0)grow(minCapacity);
}
  • 首先,ensureCapacityInternal 方法检查 elementData 是否是默认的空数组。如果是空数组(即 DEFAULTCAPACITY_EMPTY_ELEMENTDATA),它将把最小容量设置为 DEFAULT_CAPACITY(通常为 10),以确保最小的容量。
  • 然后,判断 minCapacity(所需的最小容量)是否大于当前 elementData 的长度。如果是,就调用 grow(minCapacity) 来扩展数组。

2.2 grow(minCapacity)

当容量不足时,ArrayList 会调用 grow 方法来增加数组的容量。grow 方法的源码如下:

java">private void grow(int minCapacity) {int oldCapacity = elementData.length;int newCapacity = oldCapacity + (oldCapacity >> 1);  // 增加 50% 的容量// 如果扩展后的容量小于所需的最小容量,直接设置为所需容量if (newCapacity - minCapacity < 0)newCapacity = minCapacity;if (newCapacity > MAX_ARRAY_SIZE)newCapacity = hugeCapacity(minCapacity);// 扩展数组elementData = Arrays.copyOf(elementData, newCapacity);
}
  • grow 方法通过增加现有数组容量的 50% 来扩展数组。如果扩展后的容量不够 minCapacity,则直接将容量设置为 minCapacity
  • 如果扩展后的容量大于 MAX_ARRAY_SIZEgrow 会使用 hugeCapacity 方法来返回一个合适的容量。
  • 最后,使用 Arrays.copyOf 方法将旧数组的内容复制到新的扩展数组中。

2.3 elementData[size++] = e

这行代码将新元素 e 添加到 ArrayList 中,并将 size 增加 1。sizeArrayList 中元素的当前数量,elementData 是存储元素的数组。

  • 通过 size 作为索引,将新元素 e 存储在 elementData 数组中。
  • size++ 是自增操作,表示在将元素添加到数组之后,size 变量增加 1。

3. add(int index, E element)

向指定位置插入元素 源码如下

java">/*** 在指定位置插入一个元素。** @param index   要插入元素的位置* @param element 要插入的元素* @throws IndexOutOfBoundsException 如果索引超出范围,则抛出此异常*/
public void add(int index, E element) {rangeCheckForAdd(index); // 检查索引是否越界ensureCapacityInternal(size + 1);  // 确保容量足够,如果需要扩容就扩容System.arraycopy(elementData, index, elementData, index + 1,size - index); // 将 index 及其后面的元素向后移动一位elementData[index] = element; // 将元素插入到指定位置size++; // 元素个数加一
}

其中有一个方法 arraycopy 我们具体讲讲这个函数的应用

在 ArrayList.add(int index, E element) 方法中,具体用法如下:

java">System.arraycopy(elementData, index, elementData, index + 1, size - index);
  • elementData:表示要复制的源数组,即 ArrayList 中的元素数组。
  • index:表示源数组中要复制的起始位置,即需要将 index 及其后面的元素向后移动一位。
  • elementData:表示要复制到的目标数组,即 ArrayList 中的元素数组。
  • index + 1:表示目标数组中复制的起始位置,即将 index 及其后面的元素向后移动一位后,应该插入到的位置。
  • size - index:表示要复制的元素个数,即需要将 index 及其后面的元素向后移动一位,需要移动的元素个数为 size - index。

4. 更新元素 set(索引,更新值)

与数组无差别 源码也非常简单 我就不细讲了

判断索引越界+查找索引元素+替换索引+返回arraylist

5. 删除元素

remove()两种实现方式 第一种是根据索引删除 第二种是根据所需要的值来删除

先来看 remove(int index) 方法的源码:

java">/*** 删除指定位置的元素。** @param index 要删除的元素的索引* @return 先前在指定位置的元素* @throws IndexOutOfBoundsException 如果索引超出范围,则抛出此异常*/
public E remove(int index) {rangeCheck(index); // 检查索引是否越界E oldValue = elementData(index); // 获取要删除的元素int numMoved = size - index - 1; // 计算需要移动的元素个数if (numMoved > 0) // 如果需要移动元素,就用 System.arraycopy 方法实现System.arraycopy(elementData, index+1, elementData, index,numMoved);elementData[--size] = null; // 将数组末尾的元素置为 null,让 GC 回收该元素占用的空间return oldValue; // 返回被删除的元素
}

需要注意的是,在 ArrayList 中,删除元素时,需要将删除位置后面的元素向前移动一位,以填补删除位置留下的空缺。如果需要移动元素,则需要使用 System.arraycopy 方法将删除位置后面的元素向前移动一位。最后,将数组末尾的元素置为 null,以便让垃圾回收机制回收该元素占用的空间

再来看 remove(Object o) 方法的源码:

java">
/*** 删除列表中第一次出现的指定元素(如果存在)。** @param o 要删除的元素* @return 如果列表包含指定元素,则返回 true;否则返回 false*/
public boolean remove(Object o) {if (o == null) { // 如果要删除的元素是 nullfor (int index = 0; index < size; index++) // 遍历列表if (elementData[index] == null) { // 如果找到了 null 元素fastRemove(index); // 调用 fastRemove 方法快速删除元素return true; // 返回 true,表示成功删除元素}} else { // 如果要删除的元素不是 nullfor (int index = 0; index < size; index++) // 遍历列表if (o.equals(elementData[index])) { // 如果找到了要删除的元素fastRemove(index); // 调用 fastRemove 方法快速删除元素return true; // 返回 true,表示成功删除元素}}return false; // 如果找不到要删除的元素,则返回 false
}

注意到 在删除字符串时 我们区分了null与其他 原因是什么呢

这里我们讲一下equals and ==的差别

== 和 equals 是 Java 中两种不同的比较方式:

== 运算符

  • 对于基本数据类型(int, char, boolean等),== 比较的是值是否相等
  • 对于引用类型(如String, Integer等对象),== 比较的是引用地址是否相同(即是否指向同一个对象)

equals() 方法

  • equals() 是Object类的方法,默认实现与 == 相同
  • 很多类(如String, Integer等)重写了equals()方法,用于比较对象的内容是否相同
  • 使用equals()方法时要注意防止空指针异常,这就是为什么ArrayList的remove方法要单独处理null的情况
java">String str1 = new String("Hello");
String str2 = new String("Hello");System.out.println(str1 == str2);      // 输出false,因为是不同对象
System.out.println(str1.equals(str2));  // 输出true,因为内容相同

这就解释了为什么在ArrayList的remove方法中,对null值要特殊处理:因为不能对null调用equals()方法(会抛出NullPointerException),所以必须使用==来判断。

来看一下 fastRemove() 方法:

java">
/*** 快速删除指定位置的元素。** @param index 要删除的元素的索引*/
private void fastRemove(int index) {int numMoved = size - index - 1; // 计算需要移动的元素个数if (numMoved > 0) // 如果需要移动元素,就用 System.arraycopy 方法实现System.arraycopy(elementData, index+1, elementData, index,numMoved);elementData[--size] = null; // 将数组末尾的元素置为 null,让 GC 回收该元素占用的空间
}

同样是调用 System.arraycopy() 方法对数组进行复制和移动。


http://www.ppmy.cn/devtools/152280.html

相关文章

1166 Summit (25)

A summit (峰会) is a meeting of heads of state or government. Arranging the rest areas for the summit is not a simple job. The ideal arrangement of one area is to invite those heads so that everyone is a direct friend of everyone. Now given a set of tenta…

【js进阶】设计模式之单例模式的几种声明方式

单例模式&#xff0c;简言之就是一个类无论实例化多少次&#xff0c;最终都是同一个对象 原生js的几个辅助方式的实现 手写forEch,map,filter Array.prototype.MyForEach function (callback) {for (let i 0; i < this.length; i) {callback(this[i], i, this);} };con…

Jenkins-pipeline语法说明

一. 简述&#xff1a; Jenkins Pipeline 是一种持续集成和持续交付&#xff08;CI/CD&#xff09;工具&#xff0c;它允许用户通过代码定义构建、测试和部署流程。 二. 关于jenkinsfile&#xff1a; 1. Sections部分&#xff1a; Pipeline里的Sections通常包含一个或多个Direc…

[LeetCode] 哈希表 I — 242#有效的字母异位词 | 349#两个数组的交集 | 202#快乐数 | 1#两数之和

哈希表 基础知识常见的哈希结构数组242# 有效的字母异位词 Set基础语句349# 两个数组的交集202# 快乐数 Map基础语句1# 两数之和 基础知识 哈希表常用于快速判断一个元素是否在集合中&#xff0c;空间换时间 哈希表是根据key&#xff08;如数组的索引下标&#xff09;直接进行…

算法随笔_12:最短无序子数组

上一篇: 算法随笔_11: 字符串的排列-CSDN博客 题目描述如下: 给你一个整数数组 nums &#xff0c;你需要找出一个 连续子数组 &#xff0c;如果对这个子数组进行升序排序&#xff0c;那么整个数组都会变为升序排序。请你找出符合题意的最短子数组&#xff0c;并输出它的长度。…

Cursor的composer和chat的区别

Cursor 提供了两种人机对话方式。一种是 Chat&#xff0c;它与 ChatGPT 之类的工具差别不大。另一种则是强大的 Compose。 一、长文本及程序文件处理方面 Composer 在处理长文本时表现较为稳定&#xff0c;可以对长文进行更改而不会出现内容丢失的情况。而 Chat 在更改长的程…

设计模式-结构型-装饰器模式

装饰器模式&#xff08;Decorator Pattern&#xff09;是结构型设计模式中的一种&#xff0c;它允许你通过将对象封装在一个新的对象中&#xff0c;来动态地添加新的功能&#xff0c;而无需改变原对象的结构。装饰器模式的核心思想是“将功能附加到对象上”&#xff0c;它是一种…

解决npm install安装出现packages are looking for funding run `npm fund` for details问题

当我们运行npm install时&#xff0c;可能会收到类似以下的提示信息&#xff1a;“x packages are looking for funding.” 这并不是错误提示&#xff0c;也不会影响项目的正常运行。其实实在提醒有一些软件包正在寻求资金支持。 根据提示输入npm fund可以查看详细的信息&#…