ArrayList简单介绍

简介: 用过数组的小伙伴都知道,数组可以用来存储一定容量的数据从而方便我们对数据的操作。但是在使用数组时必须事先定义好一个数组的初始容量并且在这之后该数组的容量是不可变的,而且数组这个数据类型它其中的内置方法非常稀少,如果业务中要对一个集合的数据进行多种操作那么数组这种类型结构的数据将不能满足我们的需求,所以ArrayList闪亮登场。


一、集合大家族


image.png


从图中,我们可以看出集合家族是非常庞大的,它几乎支撑着我们开发过程中所有的数据存储和数据操作功能,可想而知其重要性。


而我们今天要了解的ArrayList就是属于集合框架中的 List 体系中,它实现了 List 接口而 List又继承了 Collection 接口从而使ArrayList的功能更加丰富。


二、ArrayList前奏


在使用之前我们先来看看它的类继承和实现结构图:


image.png


  • 实线是继承关系
  • 虚线是实现关系


那么我们就先来看看它的顶层父接口 Collection的相关方法:


image.png


可以看出它定义了一些集合中最基本的抽象方法,其子类可以根据自身的需求定制化的实现,下面我们来简单的分析一下这些方法功能:


添加功能

boolean add(E e); // 添加一个元素
boolean addAll(Collection<? extends E> c); // 添加一个集合的元素


删除功能

boolean remove(Object o); // 删除一个指定元素
boolean removeAll(Collection<?> c); // 移除一个集合的元素,只要一个元素被移除,就返回true
void clear(); // 删除所有元素


判断功能

boolean contains(Object o); // 判断集合中是否存在对于元素
boolean containsAll(Collection<?> c); // 判断集合中是否包含指定的集合元素,只有包含所有的元素,才叫包含
boolean isEmpty(); // 判断集合是否为空


获取功能

Iterator<E> iterator(); // 迭代器



长度功能

int size(); // 元素个数


交集功能

boolean retainAll(Collection<?> c); // 移除此 Collection 中未包含在指定Collection中的所有元素
// 集合 A 和集合 B 做交集,最终的结果保存在集合 A 返回值表示的是A是否发生过变化


接下来就是List这个接口的介绍了,我们先来看图了解一下其中的定义的抽象方法:


image.png


我们可以看到,它的方法比Collection接口的方法多了一点,具体如下:


boolean equals(Object o); // 比较两个元素的值是否相等
E get(int index); // 根据对应的下边获取值
int hashCode(); // 获取对象的哈希码
int indexOf(Object o); // 获取第一个查找的对象下标
int lastIndexOf(Object o); // 获取最后一个查找对象的下标
List<E> subList(int fromIndex, int toIndex); // 获取从 fromIndex下标到toIndex下标结尾的范围内元素
Object[] toArray(); // 将对应的list类型数据变成数组类型数据


经过那么多的铺垫,我们正式来认识认识ArrayList吧!


三、ArrayList使用


我们先要有个概念就是,ArrayList底层就是一个数组,只不过它能动态扩容而已。

// 存储元素的数组,transient的作用为序列化时不序列化它修饰的实属性
transient Object[] elementData;


上才艺


@Test
public void demoTest01(){
    // 创建一个ArrayList对象,不带初始容量
    List<String> stringList = new ArrayList<>();
    // 创建一个ArrayList对象,传入初始容量
    List<String> stringListSize = new ArrayList<>(10);
    // 向list中添加元素
    stringList.add("J3 - 白起");
    stringList.add("关注他,认识这个 18 岁的年轻小伙");
    stringListSize.add("^v^");
    stringListSize.add("记得关注!");
    stringListSize.add("记得点赞!");
    // 设置对应下标的元素
    stringList.set(0,"白起");
    // 获取对应下标元素
    System.out.println(stringList.get(0));
    // 移除对应下标元素
    stringListSize.remove(2);
    // 输出stringList的元素个数
    System.out.println(stringList.size());
    // 输出list中的所有元素
    System.out.println(stringList.toString());
    System.out.println(stringListSize.toString());
}


上面我们使用了ArrayList的一些基本方法,来完成了一个简单的案例。


对于这些如果我们就只认识到这种地步的话那就只是一个非常低级的API调用工程师了,我们不能仅仅的局限于此。都知道使用起来方便,但去进一步的了解每个方法的内部才是我们学习的一个目标,所以我们下面就去瞅瞅这些基本方法在ArrayList中是如何实现的(简单的瞅瞅)。


四、方法解析


4.1 构造方法


从刚刚我写的案例来看,构造方法可以分带参和不带参两种具体如下:


// 创建一个ArrayList对象,不带初始容量
List<String> stringList = new ArrayList<>();
// 创建一个ArrayList对象,传入初始容量
List<String> stringListSize = new ArrayList<>(10);


那我们点进去看看这两个构造器方法


// 带参构造,initialCapacity:初始数组容量
public ArrayList(int initialCapacity) {
    // 如果初始容量大于 0 直接创建对应容量的数组
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) { // 如果初始容量为 0 直接赋一个空的数组
        this.elementData = EMPTY_ELEMENTDATA;
    } else { // 初始容量不合规,报错
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}
// 不带参构造
public ArrayList() {
    // 直接赋值空数组
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}


4.2 add方法


add⽅法可以说是ArrayList⽐较重要的⽅法了,在这里我们介绍两个常用的:

// 向list中直接添加元素
public boolean add(E e);
// 向list中的指定下标添加元素
public void add(int index, E element)


4.2.1 add(E e)


// 向list中添加元素
public boolean add(E e) {
    // 判断list再添加一个元素时,是否需要扩容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 在数组后面直接赋值
    elementData[size++] = e;
    // 返回添加成功
    return true;
}


通过代码可以看出这个方法向list中添加元素分为两步:


判断是否需要扩容,如果是就是进行相应的扩容操作

插入元素

这个判断是否需要扩容的方法内容较多,就不带大家进去看了,知道它的功能就行,如果你们想要了解的话可以自己动手进去看看😀。


4.2.2 add(int index, E element)


// 向list中的指定下标添加元素
public void add(int index, E element) {
    // 检查下标是否合理
    rangeCheckForAdd(index);
  // 判断list再添加一个元素时,是否需要扩容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 将index下标以后的元素往后移动
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    // 给index的下标的位置赋值
    elementData[index] = element;
    // list的元素个数加一
    size++;
}


方法步骤:


  1. 检查下标是否合理
  2. 判断是否需要扩容,如果是就是进行相应的扩容操作
  3. 将对应下标后面的元素后移
  4. 在指定下标上赋值
  5. 元素个数加一


在这里又涉及到了一个方法System.arraycopy()它的底层是 native 修饰的(非Java所写),所以在这里我就不过多的介绍它了。


4.3 get方法


// 根据下标获取元素值
public E get(int index) {
    // 检查下标是否大于list中存储的元素个数
    rangeCheck(index);
  // 返回对应下标的值
    return elementData(index);
}


方法步骤:

  1. 检查下标
  2. 返回对应的值


这个方法是不是很简单那呢!我觉得是,我们想简单一点,对应下标有值就返回给我们,没有就抛错(😝)


4.4 set方法

// 将对应下标的位置设置成指定值
public E set(int index, E element) {
    // 检查下标是否大于list中存储的元素个数
    rangeCheck(index);
  // 获取下标的原始值
    E oldValue = elementData(index);
    // 给对应下标设置新值
    elementData[index] = element;
    // 返回对应下标的原始值
    return oldValue;
}


方法步骤:


  1. 检查下标
  2. 获取下标下的原始值
  3. 给对应下标设置新值
  4. 返回原始值


没有什么可以细讲的了,检查的方法前面都已经说过了,我们往下看吧!


4.5 remove方法

移除方法我们同样的介绍聊个一个是根据下标移除,一个是根据元素移除


// 移除对应下标的元素
public E remove(int index);
// 移除对应的元素值
public boolean remove(Object o);


4.5.1 remove(int index)


// 移除对应下标的元素
public E remove(int index) {
    // 检查下标是否大于list中存储的元素个数
    rangeCheck(index);
  // 修改次数加一。modCount描述List被修改过的次数
    modCount++;
    // 获取下标的值
    E oldValue = elementData(index);
  // 计算开始往前移动的下标
    int numMoved = size - index - 1;
    if (numMoved > 0)
        // 开始将元素从对应的位置往前移动
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    // 将数组的最后一个元素置为null,方便回收
    elementData[--size] = null; // clear to let GC do its work
  // 返回被移除的值
    return oldValue;
}


方法步骤:

  1. 检查⻆标
  2. 获取旧的值
  3. 计算移动下标并移动
  4. 将最后的位置置为null,方便回收
  5. 返回被移除的值


4.5.2 remove(Object o)

// 移除对应的元素值
public boolean remove(Object o) {
    // 移除的值为null
    if (o == null) {
        // 遍历数组,移除null
        for (int index = 0; index < size; index++)
            // 如果对应位置为nukk
            if (elementData[index] == null) {
                // 移除
                fastRemove(index);
                // 结束返回
                return true;
            }
    } else {
        // 遍历数组,移除对应的值
        for (int index = 0; index < size; index++)
            // 如果两个值相等就移除
            if (o.equals(elementData[index])) {
                // 移除
                fastRemove(index);
                // 结束返回
                return true;
            }
    }
    // 返回
    return false;
}


方法步骤:


  1. 判断移除的是null值还是非null值
  2. 遍历元素数组
  3. 判断数组中的值是否与移除的值相等
  4. 是则移除并结束遍历返回,否者继续遍历,直到退出


这里我们可以发现根据对象移除只会移除第一个与移除对象相同的值,如果数组中有多个相同的值,只会移除一个。


五、总结


经过上面的介绍我们已经了解了ArrayList的一些基本的知识点了,那么我来简单的概括一下上面所讲的内容:


  1. ArrayList是Collection集合框架中List体系的一员
  2. 底层基于数组实现,可动态扩容并且移除元素后容量不会减少
  3. 有带参和无参两种构造方法
  4. 在添加和删除元素的时候都要移动元素
  5. 存放的值可以重复,可以存放null值
  6. 没有做同步限制,多线程不安全


上面的内容有几个点没有进行深入的讲解,比如初始化后的容量问题,初始化空数组问题,数组扩容问题,元素移动问题等这些打算后期再出一篇进行讲解。


好了,今天的内容到这里就结束了,关注我,我们下期见


^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

目录
相关文章
【java】poi 设置允许西文在单词中间换行
【java】poi 设置允许西文在单词中间换行
|
3月前
|
监控 Unix API
改变 PHP 未来的 RFC Polling API
PHP 8.6 将引入原生 Polling API(Io\Poll),终结困扰异步 PHP 20 年的 `stream_select` 瓶颈。它基于 epoll/kqueue,突破 1024 文件描述符限制,实现 O(1) 性能与真事件驱动,无需 PECL 扩展即可在任意服务器上原生支持 C10K。Fibers + Polling 构成完整异步基石——静默通过、影响深远。(239字)
515 135
|
2月前
|
安全 网络协议 网络虚拟化
阿里云 SASE 零信任架构实战:从传统VPN到零信任的完整迁移路径
远程办公3年后,传统VPN已经变成企业内网最大的安全黑洞——某员工笔记本中木马后通过VPN横向渗透内网,3台服务器被勒索加密,业务中断14小时。本文完整记录从传统VPN迁移到阿里云SASE零信任架构的实战过程:SASE架构解析、身份源对接、设备信任评估、应用访问策略、数据审计DLP配置、三阶段迁移实施(并行运行→核心系统迁移→VPN下线),以及5个真实踩坑案例和最佳实践。迁移后攻击面缩减92%、横向移动风险降为零、审计覆盖率从30%提升至100%、设备合规率从45%提升至98%。
|
10月前
|
缓存 Java 数据库连接
我们来说一下 Mybatis 的缓存机制
我是小假 期待与你的下一次相遇 ~
601 6
|
4月前
|
SQL 关系型数据库 MySQL
批量操作性能飙升:从30秒到1秒的三种实战方法
业务系统中经常需要批量导入或更新大量数据(如Excel上传、定时同步)。许多开发人员采用循环单条执行的方式,导致1万条数据耗时30秒以上,严重影响用户体验。本文从数据库IO、事务开销、锁竞争三个角度分析单条操作的性能瓶颈,并给出三种优化方案:批量INSERT、LOAD DATA文件导入、批量UPDATE用临时表。每种方案均附实测数据对比与适用场景说明,帮助读者在1万\~100万行级别批量操作中选择最优策略。
|
12月前
|
人工智能 自然语言处理 供应链
风险控制规则引擎:从敏捷开发工具到管理逻辑的承载者
Together规则引擎助力国产低代码平台升级,打造管理逻辑数字化核心。通过封装审批、风控、定价等业务规则,实现流程智能化、决策可追溯,填补国产系统在管理逻辑标准化与治理能力上的短板,推动低代码平台从“界面工具”向“企业决策中枢”跃升,迈向国际先进水平。
|
10月前
|
人工智能
阿里云产品十月刊来啦
阿里云百炼9款 Qwen3-VL系列新模型发布,人工智能平台 PAI 模型蒸馏功能发布,PolarDB-X v2.4.2开源生态适配升级
510 156
|
7月前
|
JSON API 数据格式
​​​​​​​使用 DMM Web API 获取搜索列表数据
本文详解DMM搜索列表API调用方法:介绍注册申请API ID与Affiliate ID、构造含关键词/分页/排序等参数的GET请求URL,提供Python示例代码,并解析JSON响应结构(如items、affiliateURL、价格等字段),强调URL编码、分页及错误处理要点。(239字)
1030 1
|
SQL 关系型数据库 MySQL
菜鸟之路Day30一一MySQL之DML&DQL
本文介绍了MySQL中DML(数据操作语言)和DQL(数据查询语言)的核心用法。DML主要包括插入(insert)、更新(update)和删除(delete)语句,通过具体示例演示了如何对表数据进行增删改操作。DQL则聚焦于数据查询,涵盖基本查询、条件查询、聚合函数、分组查询、排序查询和分页查询等内容。文章通过丰富的SQL语句实例,帮助读者掌握如何高效查询和操作数据库中的数据,适合初学者学习和实践。
708 12
|
12月前
|
安全 架构师 Java
90% Java新手踩坑!彻底搞懂这4个权限修饰符
Java权限修饰符看似简单,却暗藏致命风险:`public`暴露数据、`protected`跨包失控、默认权限成地雷。本文通过3大真实案例+1张神图,深度解析`private`、`default`、`protected`、`public`的作用域与内存可见性,揭示Spring Bean、继承陷阱、包级漏洞的根源,并奉上防御性编程5大原则,助你筑牢代码第一道防线。
443 1