ArrayList 全面详解

简介: 关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。本文详细解析了Java集合框架中的ArrayList,包括其定义、特点、成员变量、构造函数、API、主要方法和扩容机制等。欢迎留言交流。

关注△mikechen的互联网架构△,10年+BAT架构经验倾囊相授

image.png

大家好,我是 mikechen | 陈睿 。

image.png

ArrayList的定义

ArrayList 是 java 集合框架中比较常用的数据结构了,实现了 List 接口。

image.png

ArrayList相当于动态数组,与Java中的数组相比,它的容量能动态增长。

ArrayList的特点

  • 允许插入的元素重复
  • 插入的元素是有序的
  • 动态扩容
  • 非线程安全
  • 基于动态数组的数据结构
  • 擅长随机访问(get set)

ArrayList的成员变量

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable {
   


// 实际元素个数

private int size; 


// 存放的元素集合

transient Object[] elementData; 


// 默认初始大小10

private  static  final  int DEFAULT_CAPACITY = 10; 





//记录对List操作的次数,主要使用在Iterator中,防止迭代过程中集合被修改

protected  transient  int modCount = 0; 


// 下面两个变量用在构造函数中,判断第一次添加元素时知道从空的构造函数还是有参构造函数被初始化的

private static final Object[] EMPTY_ELEMENTDATA = {
   };

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {
   };

    // ...

}

ArrayList的构造函数

image.png

1. 无参数构造

//无参数的构造方法

    public ArrayList() {

        //此时 elementData为{}

        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

    }

构造一个初始容量为10的空的list集合,但构造函数只是给elementData赋值了一个空的数组,其实是在第一次添加元素时扩大至10。

2.有参构造函数

/**

* Constructs an empty list with the specified initial capacity.

*

* @param  initialCapacity  the initial capacity of the list

* @throws IllegalArgumentException if the specified initial capacity

*         is negative

*/

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);

    }

}

只要保证传递参数值 不 < o,即可,小于 0,会抛异常。

3.集合参数构造函数

 public ArrayList(Collection<? extends E> c) {

        elementData = c.toArray(); //转为数组

        if ((size = elementData.length) != 0) { //集合大小不等于0

            // c.toArray might (incorrectly) not return Object[] (see 6260652)

            if (elementData.getClass() != Object[].class)//集合类型不是Object类型

                elementData = Arrays.copyOf(elementData, size, Object[].class);//复制

        } else {//集合大小为0,指定一个空数组

            // replace with empty array.

            this.elementData = EMPTY_ELEMENTDATA;

        }

    }

ArrayList的API

// Collection中定义的API

boolean             add(E object)

boolean             addAll(Collection<? extends E> collection)

void                clear()

boolean             contains(Object object)

boolean             containsAll(Collection<?> collection)

boolean             equals(Object object)

int                 hashCode()

boolean             isEmpty()

Iterator<E>         iterator()

boolean             remove(Object object)

boolean             removeAll(Collection<?> collection)

boolean             retainAll(Collection<?> collection)

int                 size()

<T> T[]             toArray(T[] array)

Object[]            toArray()

// AbstractCollection中定义的API

void                add(int location, E object)

boolean             addAll(int location, Collection<? extends E> collection)

E                   get(int location)

int                 indexOf(Object object)

int                 lastIndexOf(Object object)

ListIterator<E>     listIterator(int location)

ListIterator<E>     listIterator()

E                   remove(int location)

E                   set(int location, E object)

List<E>             subList(int start, int end)

// ArrayList新增的API

Object               clone()

void                 ensureCapacity(int minimumCapacity)

void                 trimToSize()

void                 removeRange(int fromIndex, int toIndex)

ArrayList主要方法解析

image.png

1.add增加

 public boolean add(E e) {//添加一个元素

        ensureCapacityInternal(size + 1);  // Increments modCount!!

        elementData[size++] = e; //长度+1

        return true; //返回boolean 类型

    }



    public void add(int index, E element) {//指定索引位置添加一个元素

        rangeCheckForAdd(index);//检查集合长度级索引大小









        ensureCapacityInternal(size + 1);  // Increments modCount!!

        System.arraycopy(elementData, index, elementData, index + 1,

                         size - index);//复制

        elementData[index] = element; //元素放到集合指定索引的位置

        size++;//长度+1

    }









    private void ensureCapacityInternal(int minCapacity) {

        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));//默认长度10

    }







    private void ensureExplicitCapacity(int minCapacity) {

        modCount++;//记录修改次数





           //

        if (minCapacity - elementData.length > 0)//传入长度大于当前长度,扩容

            grow(minCapacity);

    }

    private void grow(int minCapacity) {

        // overflow-conscious code

        int oldCapacity = elementData.length;//老容量

        int newCapacity = oldCapacity + (oldCapacity >> 1);//老容量+ 老容量/2

        if (newCapacity - minCapacity < 0)// 新容量 小于参数传入容量,修改新容量

            newCapacity = minCapacity;

        if (newCapacity - MAX_ARRAY_SIZE > 0) //新容量大于最大容量

            newCapacity = hugeCapacity(minCapacity);

        // minCapacity is usually close to size, so this is a win:

        elementData = Arrays.copyOf(elementData, newCapacity);//扩容拷贝

    }







    private static int hugeCapacity(int minCapacity) {

        if (minCapacity < 0) // overflow 传入容量 < 0 抛异常

            throw new OutOfMemoryError();

        return (minCapacity > MAX_ARRAY_SIZE) ?

            Integer.MAX_VALUE :

            MAX_ARRAY_SIZE;

    }

添加元素时,会指定默认为10的容量,当添加元素导致集合容量大于10,触发扩容机制,扩容为原来的1.5倍。

2.remove移除

 public E remove(int index) {

        //校验删除位置是否合理

        rangeCheck(index);

      // 同add理由一样

        modCount++;

       // 保存待删除元素

        E oldValue = elementData(index);

        // 判断删除位置:如果>0不在尾部需要调用System.arraycopy,否则在尾部直接删除

        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;

    }

当我们调用 remove(int index) 时,首先会检查 index 是否合法,然后再判断要删除的元素是否位于数组的最后一个位置。

如果 index 不是最后一个,就再次调用 System.arraycopy() 方法拷贝数组。

如果 index 是最后一个元素那么就直接将数组的最后一个位置空,size – 1即可。

3.get获取

 public E get(int index) { //获取指定索引的值

        rangeCheck(index);//是否越界



        return elementData(index);//返回指定下标的值

    }

    private void rangeCheck(int index) {

        if (index >= size) //索引大于 集合长度,抛异常

            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    }

由于ArrayList底层是基于数组实现的,所以获取元素就相当简单了,直接调用数组随机访问即可。

4.set修改

public class ArrayList<E> extends AbstractList<E>

        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{

    public E set(int index, E element) {

        rangeCheck(index);



        E oldValue = elementData(index);

        elementData[index] = element;

        return oldValue;

    }}

修改指定位置的元素为新元素,首先需要校验给定index的值,index必须大于等于0,小于size,然后将新元素保存到index位置,并将旧元素返回。

5.扩容操作

public void ensureCapacity(int minCapacity) {

        //修改计时器

        modCount++;

        //ArrayList容量大小

        int oldCapacity = elementData.length;

        /*

         * 若当前需要的长度大于当前数组的长度时,进行扩容操作

         */

        if (minCapacity > oldCapacity) {

            Object oldData[] = elementData;

            //计算新的容量大小,为当前容量的1.5倍

            int newCapacity = (oldCapacity * 3) / 2 + 1;

            if (newCapacity < minCapacity)

                newCapacity = minCapacity;

            //数组拷贝,生成新的数组

            elementData = Arrays.copyOf(elementData, newCapacity);

        }

    }

ensureCapacityInternal方法的目的是确保给定的参数指定的容量值。

真正的扩容逻辑位于grow方法中:

public class ArrayList<E> extends AbstractList<E>

        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{

    private void grow(int minCapacity) {

        // overflow-conscious code

        int oldCapacity = elementData.length;

        int newCapacity = oldCapacity + (oldCapacity >> 1);// 扩容为原容量的1.5倍

        if (newCapacity - minCapacity < 0)

            newCapacity = minCapacity;

        // 如果最后决定扩容的容量比允许的最大数组容量值要大,那么则进行超限处理

        if (newCapacity - MAX_ARRAY_SIZE > 0)

            newCapacity = hugeCapacity(minCapacity);

        // minCapacity is usually close to size, so this is a win:

        elementData = Arrays.copyOf(elementData, newCapacity);

    }

    // 处理超限问题

    // 如果给定的minCapacity为负数(首位为1)则抛出异常错误OutOfMemoryError

    // 如果给定容量大于数组最大容量,则取整数的最大值为容量,否则使用数组的最大容量作为扩容容量

    private static int hugeCapacity(int minCapacity) {

        if (minCapacity < 0) // overflow

            throw new OutOfMemoryError();

        return (minCapacity > MAX_ARRAY_SIZE) ?

            Integer.MAX_VALUE :

            MAX_ARRAY_SIZE;

    }}

ensureCapacity(),该方法就是ArrayList的扩容方法,每次扩容处理会是1.5倍。

1.5倍的扩容是最好的倍数,因为一次性扩容太大(例如2.5倍)可能会浪费更多的内存(1.5倍最多浪费33%,而2.5被最多会浪费60%,3.5倍则会浪费71%……)。

但是一次性扩容太小,需要多次对数组重新分配内存,对性能消耗比较严重。

所以1.5倍刚刚好,既能满足性能需求,也不会造成很大的内存消耗。

Fail-Fast机制

在我们每次操作集合的时候,都会记录一个修改次数。

modCount++ 其实他就是fail-fast机制,它是Java集合的一种错误检测机制。

当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast机制。记住是有可能,而不是一定。

例如:

假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容).

那么,这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制。

ArrayList总结

最后做一下总结,知识点归纳:

ArrayList底层采用数组实现,拥有快速随机访问能力,但是非线程安全的集合。

ArrayList默认容量为10,扩容规则为当要保存的新元素所需的容量不足时触发。

扩容机制为首先扩容为原始容量的 1.5 倍。

扩容之后是通过数组的拷贝来确保元素的准确性的,所以尽可能减少扩容操作。

如果在遍历的时候发生结构性变化,会触发ConcurrentModificationException异常。

结构性变化包括:添加新元素,删除元素。

ArrayList支持序列化功能,支持克隆(浅拷贝)功能,排序功能等。

以上,是 ArrayList 详细解析,欢迎评论区留言交流或拓展。

我是 mikechen | 陈睿 ,关注【mikechen的互联网架构】,10年+BAT架构技术倾囊相授。

本文已同步我的技术博客 www.mikechen.cc,更新至我原创的《30W+字大厂架构技术合集》中。

相关文章
|
17天前
|
存储 弹性计算 人工智能
阿里云Alex Chen:普惠计算服务,助力企业创新
本文整理自阿里云弹性计算产品线、存储产品线产品负责人陈起鲲(Alex Chen)在2024云栖大会「弹性计算专场-普惠计算服务,助力企业创新」中的分享。在演讲中,他分享了阿里云弹性计算,如何帮助千行百业的客户在多样化的业务环境和不同的计算能力需求下,实现了成本降低和效率提升的实际案例。同时,基于全面升级的CIPU2.0技术,弹性计算全线产品的性能、稳定性等关键指标得到了全面升级。此外,他还宣布了弹性计算包括:通用计算、加速计算和容器计算的全新产品家族,旨在加速AI与云计算的融合,推动客户的业务创新。
|
24天前
|
存储 人工智能 弹性计算
产品技术能力飞跃,阿里云E-HPC荣获“CCF 产品创新奖”!
9月24日,在中国计算机学会举办的“2024 CCF 全国高性能计算学术年会”中,阿里云弹性高性能计算(E-HPC)荣获「 CCF HPC China 2024 产品创新奖」。这也是继 2022 年之后,阿里云E-HPC 再次荣获此奖项,代表着阿里云在云超算领域的持续创新结果,其产品能力和技术成果得到了业界的一致认可。
|
7天前
|
SQL 人工智能 安全
【灵码助力安全1】——利用通义灵码辅助快速代码审计的最佳实践
本文介绍了作者在数据安全比赛中遇到的一个开源框架的代码审计过程。作者使用了多种工具,特别是“通义灵码”,帮助发现了多个高危漏洞,包括路径遍历、文件上传、目录删除、SQL注入和XSS漏洞。文章详细描述了如何利用这些工具进行漏洞定位和验证,并分享了使用“通义灵码”的心得和体验。最后,作者总结了AI在代码审计中的优势和不足,并展望了未来的发展方向。
|
3天前
|
负载均衡 算法 网络安全
阿里云WoSign SSL证书申请指南_沃通SSL技术文档
阿里云平台WoSign品牌SSL证书是由阿里云合作伙伴沃通CA提供,上线阿里云平台以来,成为阿里云平台热销的国产品牌证书产品,用户在阿里云平台https://www.aliyun.com/product/cas 可直接下单购买WoSign SSL证书,快捷部署到阿里云产品中。
1843 6
阿里云WoSign SSL证书申请指南_沃通SSL技术文档
|
1天前
|
存储 安全 Oracle
【灵码助力安全3】——利用通义灵码辅助智能合约漏洞检测的尝试
本文探讨了智能合约的安全性问题,特别是重入攻击、预言机操纵、整数溢出和时间戳依赖性等常见漏洞。文章通过实例详细分析了重入攻击的原理和防范措施,展示了如何利用通义灵码辅助检测和修复这些漏洞。此外,文章还介绍了最新的研究成果,如GPTScan工具,该工具通过结合大模型和静态分析技术,提高了智能合约漏洞检测的准确性和效率。最后,文章总结了灵码在智能合约安全领域的应用前景,指出尽管存在一些局限性,但其在检测和预防逻辑漏洞方面仍展现出巨大潜力。
|
6天前
|
Web App开发 算法 安全
什么是阿里云WoSign SSL证书?_沃通SSL技术文档
WoSign品牌SSL证书由阿里云平台SSL证书合作伙伴沃通CA提供,上线阿里云平台以来,成为阿里云平台热销的国产品牌证书产品。
1778 2
|
15天前
|
编解码 Java 程序员
写代码还有专业的编程显示器?
写代码已经十个年头了, 一直都是习惯直接用一台Mac电脑写代码 偶尔接一个显示器, 但是可能因为公司配的显示器不怎么样, 还要接转接头 搞得桌面杂乱无章,分辨率也低,感觉屏幕还是Mac自带的看着舒服
|
22天前
|
存储 人工智能 缓存
AI助理直击要害,从繁复中提炼精华——使用CDN加速访问OSS存储的图片
本案例介绍如何利用AI助理快速实现OSS存储的图片接入CDN,以加速图片访问。通过AI助理提炼关键操作步骤,避免在复杂文档中寻找解决方案。主要步骤包括开通CDN、添加加速域名、配置CNAME等。实测显示,接入CDN后图片加载时间显著缩短,验证了加速效果。此方法大幅提高了操作效率,降低了学习成本。
5029 15
|
9天前
|
人工智能 关系型数据库 Serverless
1024,致开发者们——希望和你一起用技术人独有的方式,庆祝你的主场
阿里云开发者社区推出“1024·云上见”程序员节专题活动,包括云上实操、开发者测评和征文三个分会场,提供14个实操活动、3个解决方案、3 个产品方案的测评及征文比赛,旨在帮助开发者提升技能、分享经验,共筑技术梦想。
1018 147
|
17天前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1582 12