Java ArrayList

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 添加到数组中,并更新 size
    return 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 添加到数组中,并更新 size
    return 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) { // 如果要删除的元素是 null
        for (int index = 0; index < size; index++) // 遍历列表
            if (elementData[index] == null) { // 如果找到了 null 元素
                fastRemove(index); // 调用 fastRemove 方法快速删除元素
                return true; // 返回 true,表示成功删除元素
            }
    } else { // 如果要删除的元素不是 null
        for (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() 方法对数组进行复制和移动。

相关推荐
skywalk81631 小时前
基于 Python 的财经数据接口库:AKShare
开发语言·python
胡尔摩斯.1 小时前
LoadBalancer负载均衡服务调用
java·后端·spring cloud·loadbalancer
冰茶_2 小时前
C#中进程和线程的区别--17
开发语言·学习·c#
编程|诗人2 小时前
T-SQL语言的数据库交互
开发语言·后端·golang
LIZHUOLONG12 小时前
linux 双向链表
linux·windows·链表
m0_748254473 小时前
springai结合ollama
java
27669582923 小时前
boss直聘 __zp_stoken__ 逆向分析
java·python·node.js·go·boss·boss直聘·__zp_stoken__
m0_748237153 小时前
【Java报错已解决】org.springframework.beans.factory.BeanCreationException
java·开发语言
BingoXing3 小时前
Gateway与WebFlux的整合
java·spring boot·spring cloud·gateway·webflux
coding侠客3 小时前
线上工单引发的思考:Spring Boot 中 @Autowired 与 @Resource 的区别
java·spring boot·后端