深度解析ArrayList工作原理

引言

在 Java 编程领域,集合框架是不可或缺的一部分,它为开发者提供了强大的数据存储和操作工具。ArrayList 作为集合框架中的重要成员,在实际开发中被广泛使用。深入了解 ArrayList 的原理,有助于我们更好地使用它,避免潜在的性能问题和错误。本文将详细剖析 ArrayList 的底层原理,并结合代码示例进行说明。

ArrayList 概述

ArrayList 是 Java 集合框架中 List 接口的一个动态数组实现类。它允许存储重复的元素,并且元素是有序的,即元素的插入顺序和访问顺序一致。ArrayList 类继承自 AbstractList 类,并实现了 ListRandomAccessCloneablejava.io.Serializable 接口。其主要特点包括:支持随机访问、元素可重复、插入和删除操作可能会导致性能开销等。

ArrayList 的底层数据结构

ArrayList 基于数组实现,数组是一种连续的内存空间,用于存储元素。数组的优点是支持随机访问,通过索引可以快速定位到元素。在 ArrayList 中,使用一个数组来存储元素,当元素数量超过数组的容量时,会进行扩容操作。

ArrayList 的核心属性

  • elementData :这是一个 Object 类型的数组,用于存储 ArrayList 中的元素。
  • size :表示当前 ArrayList 中实际存储的元素数量。
  • DEFAULT_CAPACITY:默认初始容量,值为 10。

以下是 ArrayList 部分核心属性的代码示例:

java 复制代码
import java.util.ArrayList;

public class ArrayListCoreAttributes {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        // 由于 elementData 是私有属性,无法直接访问
        // 这里只是示意
        // Object[] elementData = list.elementData; 
        int size = list.size();
        System.out.println("Initial size: " + size);
    }
}

ArrayList 的构造方法

无参构造方法

使用无参构造方法创建 ArrayList 时,初始容量为默认值 10。

java 复制代码
import java.util.ArrayList;

public class ArrayListNoArgConstructor {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        System.out.println("Initial size: " + list.size());
    }
}
指定初始容量的构造方法

可以通过指定初始容量来创建 ArrayList,这样可以在一定程度上提高性能,避免频繁的扩容操作。

java 复制代码
import java.util.ArrayList;

public class ArrayListWithCapacityConstructor {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>(20);
        System.out.println("Initial size: " + list.size());
    }
}
从其他集合创建 ArrayList 的构造方法

可以使用包含另一个集合的构造函数来创建 ArrayList,将另一个集合的元素复制到新的 ArrayList 中。

java 复制代码
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class ArrayListFromCollectionConstructor {
    public static void main(String[] args) {
        List<String> originalList = Arrays.asList("apple", "banana", "cherry");
        ArrayList<String> newList = new ArrayList<>(originalList);
        System.out.println(newList);
    }
}

ArrayList 的常用方法原理

添加元素
  • add(E e) 方法原理:该方法将元素添加到列表的末尾。如果当前数组容量不足,会触发扩容操作。
java 复制代码
import java.util.ArrayList;

public class ArrayListAddElement {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("element1");
        list.add("element2");
        System.out.println(list);
    }
}
  • add(int index, E element) 方法原理:该方法将元素插入到指定位置。需要将指定位置及之后的元素向后移动一位,然后将元素插入到指定位置。如果当前数组容量不足,也会触发扩容操作。
java 复制代码
import java.util.ArrayList;

public class ArrayListAddElementAtIndex {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("element1");
        list.add("element2");
        list.add(1, "newElement");
        System.out.println(list);
    }
}
访问元素
  • get(int index) 方法原理:该方法通过索引直接访问数组中的元素,由于数组支持随机访问,因此时间复杂度为 O(1)。
java 复制代码
import java.util.ArrayList;

public class ArrayListGetElement {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("element1");
        list.add("element2");
        String element = list.get(0);
        System.out.println(element);
    }
}
修改元素
  • set(int index, E element) 方法原理:该方法将指定位置的元素替换为新元素,直接通过索引修改数组中的元素,时间复杂度为 O(1)。
java 复制代码
import java.util.ArrayList;

public class ArrayListSetElement {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("oldElement");
        list.set(0, "newElement");
        System.out.println(list);
    }
}
删除元素
  • remove(int index) 方法原理:该方法删除指定位置的元素,需要将指定位置之后的元素向前移动一位。时间复杂度为 O(n)。
java 复制代码
import java.util.ArrayList;

public class ArrayListRemoveElementByIndex {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("element1");
        list.add("element2");
        list.remove(0);
        System.out.println(list);
    }
}
  • remove(Object o) 方法原理:该方法删除第一个匹配的元素。需要遍历数组找到匹配的元素,然后将该元素之后的元素向前移动一位。时间复杂度为 O(n)。
java 复制代码
import java.util.ArrayList;

public class ArrayListRemoveElementByObject {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("element1");
        list.add("element2");
        list.remove("element1");
        System.out.println(list);
    }
}
获取元素数量
  • size() 方法原理 :该方法直接返回 size 属性的值,时间复杂度为 O(1)。
java 复制代码
import java.util.ArrayList;

public class ArrayListGetSize {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("element1");
        list.add("element2");
        int size = list.size();
        System.out.println("Size: " + size);
    }
}

ArrayList 的扩容机制原理

扩容的触发条件

当添加元素时,如果当前数组的容量不足,即 size + 1 > elementData.length,会触发扩容操作。

扩容的具体步骤
  1. 计算新的容量:新容量为旧容量的 1.5 倍(oldCapacity + (oldCapacity >> 1))。
  2. 创建新数组:根据新容量创建一个新的 Object 数组。
  3. 复制元素:将旧数组中的元素复制到新数组中。
  4. 更新引用:将 elementData 引用指向新数组。

以下是一个简单的示例,模拟 ArrayList 的扩容过程:

java 复制代码
import java.util.Arrays;

public class ArrayListResizeSimulation {
    private Object[] elementData;
    private int size;
    private static final int DEFAULT_CAPACITY = 10;

    public ArrayListResizeSimulation() {
        this.elementData = new Object[DEFAULT_CAPACITY];
        this.size = 0;
    }

    public void add(Object element) {
        if (size + 1 > elementData.length) {
            resize();
        }
        elementData[size++] = element;
    }

    private void resize() {
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        Object[] newElementData = new Object[newCapacity];
        System.arraycopy(elementData, 0, newElementData, 0, size);
        elementData = newElementData;
    }

    public static void main(String[] args) {
        ArrayListResizeSimulation list = new ArrayListResizeSimulation();
        for (int i = 0; i < 15; i++) {
            list.add(i);
        }
    }
}
扩容的性能影响

扩容操作涉及到数组的复制,时间复杂度为 O(n),因此频繁的扩容会影响性能。在已知元素数量的情况下,可以通过指定初始容量来避免不必要的扩容。

ArrayList 的线程安全问题

线程不安全的原因分析

ArrayList 不是线程安全的,因为它的方法没有进行同步处理。在多线程环境下,如果多个线程同时对 ArrayList 进行读写操作,可能会导致数据不一致的问题,例如数组越界、元素丢失等。

解决方案
  • 使用 Collections.synchronizedList() :可以将 ArrayList 转换为线程安全的列表。
java 复制代码
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ArrayListThreadSafety {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        List<String> synchronizedList = Collections.synchronizedList(list);
    }
}
  • 使用 CopyOnWriteArrayList:这是一个线程安全的列表实现,它在进行写操作时会复制一份数组,避免了多线程竞争的问题。
java 复制代码
import java.util.concurrent.CopyOnWriteArrayList;

public class CopyOnWriteArrayListExample {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        list.add("element1");
        list.add("element2");
        System.out.println(list);
    }
}

总结

本文详细介绍了 ArrayList 的底层原理,包括其底层数据结构、核心属性、构造方法、常用方法原理、扩容机制和线程安全问题。ArrayList 基于数组实现,支持随机访问,但插入和删除操作可能会导致性能开销。扩容机制在容量不足时会自动增加数组的容量,但频繁的扩容会影响性能。在多线程环境下,需要使用线程安全的解决方案。深入理解 ArrayList 的原理,有助于我们在实际开发中合理使用它,提高程序的性能和稳定性。

相关推荐
跟着珅聪学java28 分钟前
spring boot +Elment UI 上传文件教程
java·spring boot·后端·ui·elementui·vue
我命由我1234533 分钟前
Spring Boot 自定义日志打印(日志级别、logback-spring.xml 文件、自定义日志打印解读)
java·开发语言·jvm·spring boot·spring·java-ee·logback
lilye6634 分钟前
程序化广告行业(55/89):DMP与DSP对接及数据统计原理剖析
java·服务器·前端
战族狼魂4 小时前
CSGO 皮肤交易平台后端 (Spring Boot) 代码结构与示例
java·spring boot·后端
xyliiiiiL5 小时前
ZGC初步了解
java·jvm·算法
杉之6 小时前
常见前端GET请求以及对应的Spring后端接收接口写法
java·前端·后端·spring·vue
hycccccch6 小时前
Canal+RabbitMQ实现MySQL数据增量同步
java·数据库·后端·rabbitmq
每次的天空7 小时前
Android学习总结之算法篇四(字符串)
android·学习·算法
天天向上杰7 小时前
面基JavaEE银行金融业务逻辑层处理金融数据类型BigDecimal
java·bigdecimal
请来次降维打击!!!7 小时前
优选算法系列(5.位运算)
java·前端·c++·算法