Java中的ArrayList(扩容机制)详解

 更新时间:2024年11月04日 09:13:13   作者:小乔努力变强  
ArrayList作为Java中广泛使用的动态数组,其扩容机制是保证性能和内存使用平衡的关键,默认初始容量为10,扩容因子为1.5,旨在减少频繁的内存分配和数据迁移代价,同时建议使用预估计的初始化容量以减少扩容次数

背景介绍

对于ArrayList集合可能大家并不陌生,但ArrayList集合的扩容机制大家是否了解呢?我们今天着重来看看

适用于什么场景?

检索比较多的场景

ArrayList特点

  • 1、ArrayList集合底层采用了数据这种数据结构,是Object类型
  • 2、ArrayList的默认初始容量为10,扩容因子为1.5
  • 3、建议给定一个预估计的初始化容量,减少数组扩容的次数,这是ArrayList集合比较重要的优化策略.因为在在扩容的同时需要将原来数组中的数据复制到新数组里,但如果要插入大量数据时,赋值数组的形式效率很低,所以大多数情况下会使用带参构造函数,传入一个预估计容量,提前定义好容量。
  • 4、ArrayList是非线程安全的

实战演练

import java.util.ArrayList;
import java.util.List;

public class ListTest {
public static void main(String[] args) {
    List<String> list = new ArrayList<String>();
    list.add("b");//第一个,索引下标0
    list.add("d");
    list.add("c");
    list.add("a");
    list.add("d"); //允许使用重复元素

    System.out.println(list);  //输出结果:[b, d, c, a, d]
    System.out.println(list.get(2));  //输出指定下标的元素,输出结果:c

    list.add(1,"f");//在指定索引下标位置添加元素
    System.out.println(list); //输出结果:[b, f, d, c, a, d],原来下标为1和1之后的下标索引位置的元素自动向后移动

    List<String> a = new ArrayList<String>();
    a.add("123");
    a.add("456");
    list.addAll(2,a);  //在指定索引下标的位置插入集合
    System.out.println(list);//输出结果:[b, f, 123, 456, d, c, a, d]

    //获取指定元素在集合中第一次出现的索引下标
    System.out.println(list.indexOf("d")); //输出结果:4
    //获取指定元素在集合中最后一次出现的索引下标
    System.out.println(list.lastIndexOf("d"));//输出结果:7

    list.remove(2);  //根据指定的索引下标移除元素
    System.out.println(list);  //输出结果:[b, f, 456, d, c, a, d]

    list.set(1,"ff"); //根据指定的索引下标修改元素
    System.out.println(list); //输出结果:[b, ff, 456, d, c, a, d]

    //根据索引下标的起始位置截取一段元素形成一个新的集合,截取的时候,包含开始的索引不包含结束时的索引
    List<String> sublist= list.subList(2,4);
    System.out.println(sublist);//输出结果:[456, d]

    System.out.println(list.size());//输出结果7
    }
}
import java.util.LinkedList;
import java.util.List;

public class ListTest {
public static void main(String[] args){
    List l1 = new LinkedList();
    for(int i = 0;i<=5;i++){
    l1.add("a"+i);
    }

    System.out.print(l1);
    l1.add(3,"a100");
    System.out.println(l1);
    l1.set(6,"a200");
    System.out.println(l1);
    System.out.print((String)l1.get(2)+" ");
    System.out.println(l1.indexOf("a3"));
    l1.remove(1);
    System.out.println(l1);
    }
}

输出结果:

[a0,a1,a2,a3,a4,a5]

[a0,a1,a2,a100,a3,a4,a5]

[a0,a1,a2,a100,a3,a4,a200]

a2 4

[a0,a2,a100,a3,a4,a200]

ArrayList扩容机制

ArrayList的使用前不需要像数组一样提前定义大小空间,容量是随着使用时自动增长的,那为什么在使用ArrayList的add方法添加元素的时候底层还需要判断集合的容量是否能够放下要添加的元素呢?

又没有定义固定大小直接放进去不就好了吗?

add方法添加分为三步:

  • ①、判断集合容量是否满足添加的元素
  • ②、添加元素
  • ③、集合长度+1

解答:

用户不需要提前定义大小,那是因为底层默认已经定义好了大小。

其实是有一个边界值的,并不是无限增长的。

使用时增加,是因为底层有扩展因子(扩容因子是1.5),当数量达到数组的百分之多少的时候就会扩容。ArrayList默认的初始大小是10

问题:大家可以思考思考为什么ArrayList底层扩容因子是1.5?为什么不是1.32.4……

ArrayList的底层扩容因子是1.5,而不是其他数字,是为了在平衡内存使用和性能之间找到一个合适的折中方案。

下面是一些原因:

  1. 内存分配的效率:扩容因子的选择会影响内存分配的效率。如果扩容因子过小,每次扩容都只增加少量的容量,这会导致频繁的内存分配操作,增加了时间和空间的开销。而如果扩容因子过大,每次扩容都会增加大量的容量,这可能会导致浪费过多的内存。1.5是一个相对较小的扩容因子,可以在一定程度上平衡内存使用和性能。
  2. 数据迁移的代价:当ArrayList需要扩容时,需要将原有数据迁移到新的更大的数组中。扩容因子的选择会影响数据迁移的频率和代价。较小的扩容因子会导致更频繁的数据迁移,而较大的扩容因子会减少数据迁移的次数。1.5作为一个相对较小的扩容因子,可以在一定程度上减少数据迁移的代价。
  3. 性能和空间的平衡:ArrayList旨在提供高效的随机访问和动态增长的能力。选择1.5作为扩容因子可以在一定程度上平衡性能和空间的需求。较小的扩容因子可以减少内存的浪费,而较大的扩容因子可以减少内存分配的频率。

综上所述,ArrayList的特点如下

  • 1、ArrayList集合底层采用了数据这种数据结构,是Object类型
  • 2、ArrayList的默认初始容量为10,扩容因子为1.5
  • 3、建议给定一个预估计的初始化容量,减少数组扩容的次数,这是ArrayList集合比较重要的优化策略.因为在在扩容的同时需要将原来数组中的数据复制到新数组里,但如果要插入大量数据时,赋值数组的形式效率很低,所以大多数情况下会使用带参构造函数,传入一个预估计容量,提前定义好容量。
  • 4、ArrayList是非线程安全的

总结

需要注意的是,扩容因子的选择并不是一个固定的标准,可以根据具体的应用场景和性能需求进行调整。

在实际使用中,可以根据经验和性能测试来选择合适的扩容因子,以满足应用的需求。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • SpringBoot集成PostgreSQL表级备份与恢复的实战指南

    SpringBoot集成PostgreSQL表级备份与恢复的实战指南

    本文介绍了在企业级应用中使用SpringBoot+PostgreSQL实现数据备份与恢复的方法,主要包括使用pg_dump导出数据、pg_restore恢复数据及ProcessBuilder调用系统命令,并详细解释了ProcessBuilder、pg_dump与pg_restore的用法、参数配置及常见问题解决方法
    2026-04-04
  • response文件流输出文件名中文不显示的解决

    response文件流输出文件名中文不显示的解决

    这篇文章主要介绍了response文件流输出文件名中文不显示的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01
  • SpringBoot结合JWT实现用户登录、注册、鉴权

    SpringBoot结合JWT实现用户登录、注册、鉴权

    用户登录、注册及鉴权是我们基本所有系统必备的,也是很核心重要的一块,本文主要介绍了SpringBoot结合JWT实现用户登录、注册、鉴权,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2023-05-05
  • Spring Boot Actuator执行器运行原理详解

    Spring Boot Actuator执行器运行原理详解

    这篇文章主要介绍了Spring Boot Actuator执行器运行原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-03-03
  • Java使用FFmpeg提取音频的详细指南

    Java使用FFmpeg提取音频的详细指南

    FFmpeg 是一个开源的多媒体处理工具,支持视频、音频的编码、解码、转换等多种功能,本文将详细讲解如何使用 FFmpeg 提取音频,包括常见的音频格式提取、音频质量调整、高级处理操作等,内容浅显易懂,适合初学者快速掌握,需要的朋友可以参考下
    2024-11-11
  • java 中的volatile关键字

    java 中的volatile关键字

    这篇文章主要介绍了java 中的volatile关键字,volatile在多处理器开发中保证共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另一个一个线程立马可以读到这个修改的值。下面我们来看看文章的具体介绍内容吧

    2021-12-12
  • idea数据库驱动下载失败的问题及解决

    idea数据库驱动下载失败的问题及解决

    这篇文章主要介绍了idea数据库驱动下载失败的问题及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01
  • Spring实现资源的动态加载和卸载的方法小结

    Spring实现资源的动态加载和卸载的方法小结

    这篇文章主要介绍了Spring实现资源的动态加载和卸载的方法小结,文中通过代码示例讲解的非常详细,对大家的学习或工作有一定的帮助,需要的朋友可以参考下
    2024-06-06
  • springBoot整合rabbitmq测试常用模型小结

    springBoot整合rabbitmq测试常用模型小结

    这篇文章主要介绍了springBoot整合rabbitmq并测试五种常用模型,本文主要针对前五种常用模型,在spirngboot框架的基础上整合rabbitmq并进行测试使用,需要的朋友可以参考下
    2022-01-01
  • 前端与Spring Boot后端无感Token 刷新的完整实例代码

    前端与Spring Boot后端无感Token 刷新的完整实例代码

    本文详细介绍无感Token刷新的核心原理,以及前端Axios拦截器与后端 Spring Boot + MyBatis-Plus + Redis 的完整示例代码,通过双 Token、Redis 校验与拦截重试,你可以在保证安全性的同时,给用户带来 无感登录过期刷新的体验,感兴趣的朋友跟随小编一起看看吧
    2025-06-06

最新评论