Java集合学习:ArrayList源码详解

简介: Java集合学习:ArrayList源码详解

前言

ArrayList可谓在工作中使用很频繁的容器了,其底层采用数组作为存储结构,其特点取值速度快,下面通过源码来了解下它的原理。

正文

重要属性结构

下面来看下ArrayList的一些重要属性

  //默认数组初始容量
    private static final int DEFAULT_CAPACITY = 10;
  //默认初始化空数组
    private static final Object[] EMPTY_ELEMENTDATA = {};
  //默认初始化空数组
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
  //用于存储数据的数组
    transient Object[] elementData; // non-private to simplify nested class access
  //记录当前已存储的数据个数
   private int size;

构造函数

方法1:ArrayList(int initialCapacity)

    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
  1. 如果有指定容量,则创建指定容量大小的数组;
  2. 如果没有指定容量,则创建默认大小数组,默认为8;

方法2:ArrayList

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

默认初始化一个空数组,当添加第一个元素时,会进行扩容。

方法3:ArrayList(Collection<? extends E> c)

    public ArrayList(Collection<? extends E> c) {
      //将传进来的集合转为数组
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            //如果数组不是object类型的,则创建一个新的数组并将内容拷贝进去
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            //创建默认空数组
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
  1. 将传递进来集合转为数组。
  2. 如果传递进来的集合容量为0,则将数组引用赋为默认空数组。
  3. 如果数组不是Object类型数据,则创建一个新的Object数据,并将内容进行拷贝。

扩容机制

  private void ensureCapacityInternal(int minCapacity) {
  //minCapacity:当前已插入数据个数+要插入值个数的总和,代表当前需要多少容量才够存储
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
      //如果当前数组是空的,则取默认值跟最小容量值最大者值
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // overflow-conscious code
        //如果数组空间不够,则进行扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    private void grow(int minCapacity) {
        // overflow-conscious code
        //获取旧数组的容量
        int oldCapacity = elementData.length;
        //将容量扩容1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //如果扩容后还不够,则将容量赋到需要的数量
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
        //如果容量大于Integer最大值-8时,再判断是否大于Integer最大值,如果大于赋值为         Integer,小于则赋值为Integer最大值-8
        //
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        //新建扩容后大小的数组,并把旧容量拷贝过去
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
  1. 判断目前存储的容量值(包含即将插入的值)是否大于默认值8,是则返回容量值,否则返回默认值8
  2. 判断当前所需容量是否大于数组容量大小,这里主要判断一下够不够用
  3. 如果不够用则进行扩容
  4. 将原来的数量扩容为1.5倍
  5. 创建一个新的数组,并将旧数据拷贝到新数组中

添加数据

方法:add(E e)

    public boolean add(E e) {
      //看是否需要扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //通过下标赋值
        //size用于记录当前存储数据的个数
        elementData[size++] = e;
        return true;
    }
  1. 先判断是否需要扩容
  2. 先在当前数组位置插入数据,插入成功后再将个数+1

方法:add(int index, E element)

    public void add(int index, E element) {
      //检查插入下标是否超出范围
        rangeCheckForAdd(index);
    //扩容检查
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //这里相当于将index位置的数据往后移动一个位置
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        //在index处插入
        elementData[index] = element;
        size++;
    }
  1. 检查下标是否在范围内
  2. 扩容检查
  3. 将要插入下标及其后面数组内容向后迁移一位
  4. 在Index位置插入新数据

查询数据

方法:get(int index)

    public E get(int index) {
    //检查范围
        rangeCheck(index);
  //获取下标值
        return elementData(index);
    }
  1. 检查范围
  2. 直接取出数组下标位置的值

方法:indexOf(Object o)

    public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

这里将null值分开逻辑编写,再遍历比较值是否相等

删除数据

方法:remove(int index)

    public E remove(int index) {
        rangeCheck(index);
        modCount++;
        //保存旧值
        E oldValue = elementData(index);
        int numMoved = size - index - 1;
        //将数据往前移动一位
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //删除最后一个位置的值
        elementData[--size] = null; // clear to let GC do its work
    //返回移除的值
        return oldValue;
    }
  1. 将下标对应的旧只取出
  2. 将移除下标后面的数组数据都往前移动1位
  3. 因为是采用拷贝的方式向前移动的,所以数组最后位置的值还是存在,所以需要将其置为null

方法:clear()

    public void clear() {
        modCount++;
        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;
        size = 0;
    }

循环将所有数据都置为null

总结

根据源码我们可以看到ArrayList在取数据非常简单,直接根据下标即可获取值,但是在删除数据、添加数据时,如果下标位于头尾之间,则需要移动部分数据位置,这在插入时导致效率没有LinkedList高;

所以我们在使用集合时,需要根据业务场景考虑,来选择最佳集合容器。

目录
相关文章
|
21天前
|
存储 安全 Java
Java 集合框架中的老炮与新秀:HashTable 和 HashMap 谁更胜一筹?
嗨,大家好,我是技术伙伴小米。今天通过讲故事的方式,详细介绍 Java 中 HashMap 和 HashTable 的区别。从版本、线程安全、null 值支持、性能及迭代器行为等方面对比,帮助你轻松应对面试中的经典问题。HashMap 更高效灵活,适合单线程或需手动处理线程安全的场景;HashTable 较古老,线程安全但性能不佳。现代项目推荐使用 ConcurrentHashMap。关注我的公众号“软件求生”,获取更多技术干货!
39 3
|
1月前
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
71 7
|
2月前
|
数据采集 人工智能 Java
Java产科专科电子病历系统源码
产科专科电子病历系统,全结构化设计,实现产科专科电子病历与院内HIS、LIS、PACS信息系统、区域妇幼信息平台的三级互联互通,系统由门诊系统、住院系统、数据统计模块三部分组成,它管理了孕妇从怀孕开始到生产结束42天一系列医院保健服务信息。
46 4
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
109 2
|
12天前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
|
1月前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
129 13
|
1月前
|
存储 缓存 安全
Java 集合江湖:底层数据结构的大揭秘!
小米是一位热爱技术分享的程序员,本文详细解析了Java面试中常见的List、Set、Map的区别。不仅介绍了它们的基本特性和实现类,还深入探讨了各自的使用场景和面试技巧,帮助读者更好地理解和应对相关问题。
49 5
|
2月前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
65 12
|
1月前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
2月前
|
存储 缓存 安全
Java 集合框架优化:从基础到高级应用
《Java集合框架优化:从基础到高级应用》深入解析Java集合框架的核心原理与优化技巧,涵盖列表、集合、映射等常用数据结构,结合实际案例,指导开发者高效使用和优化Java集合。
56 4